.navigationTitle aways creates an autolayout-constraint conflict - swiftui

Environment: Xcode Version 12.3 (12C33)
Version 12.3 (12C33)
I notice that the NavigationTitle breaks the autolayout after updating to Xcode 12.3 RC.
Here's a simple example (revised to modern syntax per comment):
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
VStack(alignment: .center, spacing: 10) {
Text("Hello, world!")
.padding()
}.navigationTitle(Text("Greetings World!"))
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Here's what I get upon running this:
2020-12-09 11:42:19.994389-0800 UICheck[26092:799713] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x60000170e1c0 'BIB_Trailing_CB_Leading' H:[_UIModernBarButton:0x7feb5ac096e0]-(6)-[_UIModernBarButton:0x7feb5ac06db0'Greetings World!'] (active)>",
"<NSLayoutConstraint:0x60000170e210 'CB_Trailing_Trailing' _UIModernBarButton:0x7feb5ac06db0'Greetings World!'.trailing <= _UIButtonBarButton:0x7feb5ac066a0.trailing (active)>",
"<NSLayoutConstraint:0x60000170ef80 'UINav_static_button_horiz_position' _UIModernBarButton:0x7feb5ac096e0.leading == UILayoutGuide:0x600000d5db20'UIViewLayoutMarginsGuide'.leading (active)>",
"<NSLayoutConstraint:0x60000170efd0 'UINavItemContentGuide-leading' H:[_UIButtonBarButton:0x7feb5ac066a0]-(0)-[UILayoutGuide:0x600000d5da40'UINavigationBarItemContentLayoutGuide'] (active)>",
"<NSLayoutConstraint:0x60000170ae90 'UINavItemContentGuide-trailing' UILayoutGuide:0x600000d5da40'UINavigationBarItemContentLayoutGuide'.trailing == _UINavigationBarContentView:0x7feb5ae185f0.trailing (active)>",
"<NSLayoutConstraint:0x60000170f750 'UIView-Encapsulated-Layout-Width' _UINavigationBarContentView:0x7feb5ae185f0.width == 0 (active)>",
"<NSLayoutConstraint:0x60000170b250 'UIView-leftMargin-guide-constraint' H:|-(0)-[UILayoutGuide:0x600000d5db20'UIViewLayoutMarginsGuide'](LTR) (active, names: '|':_UINavigationBarContentView:0x7feb5ae185f0 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x60000170e1c0 'BIB_Trailing_CB_Leading' H:[_UIModernBarButton:0x7feb5ac096e0]-(6)-[_UIModernBarButton:0x7feb5ac06db0'Greetings World!'] (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
This is the source of the problem:
I suspect this to be another Apple Bug.
Being that I'm using the supplied .navigationBarTitle view, how can I fix this?

Updated for Xcode 13
To solve the LayoutConstraints error on SwiftUI NavigationView, make it conform to this style:
.navigationViewStyle(StackNavigationViewStyle()) on Xcode 12
.navigationViewStyle(.stack) on Xcode 13
struct ContentView: View {
var body: some View {
NavigationView {
VStack(alignment: .center, spacing: 10) {
Text("Hello world!")
.padding()
}
.navigationTitle("Greetings World!")
.navigationBarTitleDisplayMode(.inline)
}
.navigationViewStyle(.stack)
}
}

Related

SwiftUI: Conditional Context Menu Shown Unexpectedly

In the following SwiftUI view, why does the conditional .contextMenu not work correctly?
Steps:
Long press the list item
Tap Edit
Long press the list item again
On the second long press the context menu should not appear because editMode?.wrappedValue is .active. But it does appear. How to fix that?
struct ContentView: View {
#Environment(\.editMode) private var editMode
var body: some View {
let contextMenu = ContextMenu {
Button("Do nothing", action: {})
}
VStack(alignment: .leading, spacing: 12) {
EditButton().padding()
List {
ForEach(1..<2) {i in
Text("Long press me. Editing: \((editMode?.wrappedValue == .active).description)")
.contextMenu(editMode?.wrappedValue == .active ? nil : contextMenu)
}
.onDelete(perform: { _ in })
.onMove(perform: { _, _ in })
}
Spacer()
}
}
}
Works fine with Xcode 14 / iOS 16
Here is possible workaround for older versions (it is possible to try different places for .id modifier to have appropriate, acceptable, UI feedback)
Tested with Xcode 13.4 / iOS 15.5
Text("Long press me. Editing: \((editMode?.wrappedValue == .active).description)")
.contextMenu(editMode?.wrappedValue == .active ? nil : contextMenu)
.id(editMode?.wrappedValue) // << this !!

SwiftUI Constraint Issue For NavigationTitle in iOS 15

I'm having an issue with Xcode throwing a constraint message in the console every time I use a NavigationTitle.
I'd like to say that I've read through the following post and have tried adding StackedNavigationViewStyle(). This worked for iOS 14 and Xcode 12.3. Since upgrading to iOS 15 and Xcode 13 the solution stops working.
SwiftUI NavigationView navigationBarTitle LayoutConstraints issue
The message is.
[LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one
you don't want. Try this: (1) look at each constraint and try to
figure out which you don't expect; (2) find the code that added the
unwanted constraint or constraints and fix it. (
"<NSLayoutConstraint:0x28189d540 UIView:0x155f53c50.trailing == _UIBackButtonMaskView:0x155f538f0.trailing (active)>",
"<NSLayoutConstraint:0x281890af0 'Mask_Trailing_Trailing' _UIBackButtonMaskView:0x155f538f0.trailing == _UIButtonBarButton:0x155f52d00.trailing (active)>",
"<NSLayoutConstraint:0x2818912c0 'MaskEV_Leading_BIB_Trailing' H:[_UIModernBarButton:0x155f53610]-(0)-[UIView:0x155f53c50]
(active)>",
"<NSLayoutConstraint:0x281890c30 'UINav_static_button_horiz_position'
_UIModernBarButton:0x155f53610.leading == UILayoutGuide:0x2802a5ce0'UIViewLayoutMarginsGuide'.leading
(active)>",
"<NSLayoutConstraint:0x281890640 'UINavItemContentGuide-leading' H:[_UIButtonBarButton:0x155f52d00]-(6)-[UILayoutGuide:0x2802a5dc0'UINavigationBarItemContentLayoutGuide']
(active)>",
"<NSLayoutConstraint:0x28189dd60 'UINavItemContentGuide-trailing' UILayoutGuide:0x2802a5dc0'UINavigationBarItemContentLayoutGuide'.trailing
== _UINavigationBarContentView:0x155f51f30.trailing (active)>",
"<NSLayoutConstraint:0x28189b340 'UIView-Encapsulated-Layout-Width'
_UINavigationBarContentView:0x155f51f30.width == 0 (active)>",
"<NSLayoutConstraint:0x28189e300 'UIView-leftMargin-guide-constraint'
H:|-(8)-UILayoutGuide:0x2802a5ce0'UIViewLayoutMarginsGuide'
(active, names: '|':_UINavigationBarContentView:0x155f51f30 )>" )
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x28189d540 UIView:0x155f53c50.trailing ==
_UIBackButtonMaskView:0x155f538f0.trailing (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints
to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView
listed in <UIKitCore/UIView.h> may also be helpful.
When adding StackedNavigationViewStyle to an iOS 15 app the Title and TabBar become inactive and the list moves underneath.
The code for this is below.
struct Tab1: View {
var body: some View {
// // Used for PacificBlue color.
UITableView.appearance().backgroundColor = .clear
// UITableView.appearance().separatorColor = UIColor(Color.white)
return NavigationView {
ZStack {
Color.pacificBlue
.edgesIgnoringSafeArea(.all)
List {
ForEach(1..<100) { index in
if #available(iOS 15.0, *) {
NavigationLink(destination: DetailView()) {
HStack {
Text("Row \(index)")
//
.listRowSeparatorTint(Color.pink)
}
.background(Color.pacificBlue)
} // NavigationLink
.listRowSeparatorTint(Color.pink)
} else {
// Fallback on earlier versions
NavigationLink(destination: DetailView()) {
Text("Row \(index)")
} // NavigationLink
} // if else
} // ForEach
.listRowBackground(Color.pacificBlue)
} // List
.listStyle(PlainListStyle())
} // ZStack
.navigationTitle("Title")
} // NavigationView
.navigationViewStyle(StackNavigationViewStyle())
} // View
}
If built on iOS 14 the expected behavior is seen.
Any help would be appreciated.
Recently, I was challenged by the same issue of NavigationBar and TabBar becoming transparent and unresponsive to scroll with IOS 15.0 exclusively, while IOS 14 and IOS 15.2 and above had normal behavior. After many hours of identifying the problem, I discovered that it caused by combination of .navigationViewStyle(.stack) and the ZStack that's used inside to provide Color as background. I have no explanation why it behaves like that, neither why the solutions work.
Option 1: Wrap the NavigationView with another ZStack
struct ContentView: View {
var body: some View {
UITableView.appearance().backgroundColor = .clear
return ZStack {
NavigationView {
ZStack {
Color.blue
.edgesIgnoringSafeArea(.all)
List {
ForEach(1...50, id:\.self) { index in
Text("Row \(index)")
}
}
}
.navigationTitle("Title")
}
.navigationViewStyle(.stack)
}
}
}
Option 2: Apply the ZStack only to IOS 15 with a ViewModifier
struct ContentView: View {
var body: some View {
UITableView.appearance().backgroundColor = .clear
return NavigationView {
ZStack {
Color.blue
.edgesIgnoringSafeArea(.all)
List {
ForEach(1...50, id:\.self) { index in
Text("Row \(index)")
}
}
}
.navigationTitle("Title")
}
.navigationViewStyle(.stack)
.modifier(NavigationModifier())
}
}
struct NavigationModifier: ViewModifier {
func body(content: Content) -> some View {
if #available(iOS 15.0, *) {
ZStack {
content
}
} else {
content
}
}
}
I had the same problem using NavigationView and TabView together. Until iOS 14 I used the code bellow and it worked without problem:
NavigationView{
TabView(selection: $selection) {
...
}
}
However, since iOS 15, the same code started having the same navigationTitle you're having.
After a lot of time searching for a solution I found that if we change the order it works perfectly:
struct ContentView: View {
var body: some View {
TabView(selection: $selection) {
NavigationView {
Text("Example 1")
.navigationTitle("One")
}
.tabItem {
Text("One")
}
.tag("One")
NavigationView {
Text("Example 2")
.navigationTitle("Two")
}
.tabItem {
Text("Two")
}
.tag("Two")
}
}
}

NavigationBarTitle Causes Errors inside TabView

I have begun getting an error message in my console whenever I have a NavigationView nested inside a TabView, I am getting the below error. Is there a fix for this?
struct ContentView: View {
var body: some View {
TabView{
NavigationView{
Text("Hello, world!")
.padding()
.navigationBarTitle("Hello World")
}
.tag(0)
.tabItem {
Text("Main")
}
NavigationView{
Text("Hello, world!")
.padding()
}
.tag(1)
.tabItem {
Text("Secondary")
}
}.edgesIgnoringSafeArea(.top)
}
}
My console spits out the below error:
2021-03-26 14:14:59.294828-0600 Playground[39119:2828740] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x600001ae5fe0 'BIB_Trailing_CB_Leading' H:[_UIModernBarButton:0x7fdded4274f0]-(6)-[_UIModernBarButton:0x7fdded428920'Hello World'] (active)>",
"<NSLayoutConstraint:0x600001ae6030 'CB_Trailing_Trailing' _UIModernBarButton:0x7fdded428920'Hello World'.trailing <= _UIButtonBarButton:0x7fdded7079f0.trailing (active)>",
"<NSLayoutConstraint:0x600001ae0230 'UINav_static_button_horiz_position' _UIModernBarButton:0x7fdded4274f0.leading == UILayoutGuide:0x6000000df1e0'UIViewLayoutMarginsGuide'.leading (active)>",
"<NSLayoutConstraint:0x600001ae94f0 'UINavItemContentGuide-leading' H:[_UIButtonBarButton:0x7fdded7079f0]-(0)-[UILayoutGuide:0x6000000df100'UINavigationBarItemContentLayoutGuide'] (active)>",
"<NSLayoutConstraint:0x600001aee800 'UINavItemContentGuide-trailing' UILayoutGuide:0x6000000df100'UINavigationBarItemContentLayoutGuide'.trailing == _UINavigationBarContentView:0x7fdded4253a0.trailing (active)>",
"<NSLayoutConstraint:0x600001ae9cc0 'UIView-Encapsulated-Layout-Width' _UINavigationBarContentView:0x7fdded4253a0.width == 0 (active)>",
"<NSLayoutConstraint:0x600001aeebc0 'UIView-leftMargin-guide-constraint' H:|-(0)-[UILayoutGuide:0x6000000df1e0'UIViewLayoutMarginsGuide'](LTR) (active, names: '|':_UINavigationBarContentView:0x7fdded4253a0 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600001ae5fe0 'BIB_Trailing_CB_Leading' H:[_UIModernBarButton:0x7fdded4274f0]-(6)-[_UIModernBarButton:0x7fdded428920'Hello World'] (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
I found the answer here:
SwiftUI NavigationView navigationBarTitle LayoutConstraints issue
I needed to add .navigationViewStyle(StackNavigationViewStyle())
When I updated the code to this, the error went away:
struct ContentView: View {
var body: some View {
TabView{
NavigationView{
Text("Hello, world!")
.padding()
.navigationTitle("Hello World")
}
.navigationViewStyle(StackNavigationViewStyle())
.tag(0)
.tabItem {
Text("Main")
}
NavigationView{
Text("Hello, world!")
.padding()
}
.tag(1)
.tabItem {
Text("Secondary")
}
}.edgesIgnoringSafeArea(.top)
}
}

SwiftUI iOS14 - Disable keyboard avoidance

Is there a way to disable the native keyboard avoidance on iOS14?
There is no keyboard avoidance in iOS13, so I want to implement my own, but when I do the native one on iOS14 is still active, so both my implementation and the native one run at the same time, which I don't want.
My deployment target is iOS13, so I need a solution for both iOS13 and iOS14.
You can use if #available(iOS 14.0, *) if you want to adapt the iOS 14 code so it compiles on iOS 13.
Here is an adapted version of this answer to work on both iOS 13 and iOS 14:
struct ContentView: View {
#State var text: String = ""
var body: some View {
if #available(iOS 14.0, *) {
VStack {
content
}
.ignoresSafeArea(.keyboard, edges: .bottom)
} else {
VStack {
content
}
}
}
#ViewBuilder
var content: some View {
Spacer()
TextField("asd", text: self.$text)
.textFieldStyle(RoundedBorderTextFieldStyle())
Spacer()
}
}

SwiftUI View appear differently in device and preview compare to view hierarchy capture

I have a problem with SwiftUI
There was a issue
yello view don't appear at first launch view
move to home that makes app into background mode
move to app (foreground)
then yellow view appears
I captured view hierarchy in step 1.
(left is view hierarchy capture, right is simulator)
view hierarchy capture shows the yellow square but simulator didn't show yellow square
I checked the view breadscrum but both was same so I have no clue.
I'm sure this is not a networking problem.
There is two way to appear yellow square
background -> foreground
present alert -> dismiss alert
I'm not sure this is a framework bug or else.
Also, is there any API that I can print the swiftUI rendering request succeed or fail?
Thank you in advance and merry christmas!
(left is before yello square appear on simulator/ right is after yello square appear on simulator)
edit - add sample code
contentView
import SwiftUI
struct ContentView: View {
#ObservedObject var viewModel : viewModel
#ObservedObject var params : otherViewModel
var body: some View {
HorizontalScrollView(viewModel: viewModel, someParmas: params)
.padding(.leading, 24)
.frame(width: UIScreen.main.bounds.width, height:400)
.background(Color.red)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(viewModel: viewModel(homeAPI: HomeAPI()), params: otherViewModel())
}
}
import SwiftUI
struct HorizontalScrollView: View {
#ObservedObject var viewModel: viewModel
#ObservedObject var holder : otherViewModel
private var homeHightlightRange: Range<Int> {
return 0..<(viewModel.something?.somethingList?.count ?? 0)
}
init(viewModel: viewModel, someParmas: otherViewModel) {
self.viewModel = viewModel
self.holder = someParmas
}
var body: some View {
VStack(spacing: 20) {
HStack(spacing: 0) {
Text("Merry christmas")
.font(.system(size: 24))
.foregroundColor(.white)
.bold()
.onTapGesture {
viewModel.getHighlight()
}
Spacer()
}
ScrollView(.horizontal, showsIndicators: false, content: {
HStack(alignment:.bottom, spacing: 14) {
ForEach(homeHightlightRange, id: \.self) { index in
Color.yellow.frame(width:200, height:300)
}
}
})
}
.frame(height: 339)
}
}
struct HighlightView_Previews: PreviewProvider {
static var previews: some View {
HorizontalScrollView(viewModel: viewModel(homeAPI: HomeAPI()),someParmas: otherViewModel())
.previewLayout(.sizeThatFits)
}
}
homeHightlightRange is got from server via viewModel
I suppose that the solution is to add ScrollView conditionally on API results appear, like (not tested - typed here, so typos might be present)
if viewModel.something?.somethingList?.count != 0 {
ScrollView(.horizontal, showsIndicators: false, content: {
HStack(alignment:.bottom, spacing: 14) {
ForEach(homeHightlightRange, id: \.self) { index in
Color.yellow.frame(width:200, height:300)
}
}
})
}