navigationBarHidden combined with inline display mode causes jump - swiftui

I have a parent view, in which I don't want any navigation bar, and a child view, where I want an inline navigation bar.
If I navigate to the child view, then back again. The top of the list will have a weird jump effect when scrolling upwards.
I'm sure this is a bug, but does anyone have a workaround? If it helps, I can get access to the underlying UIScrollView/UINavigationController components - but I'm not sure if any of the properties would help.
struct ContentView: View {
var body: some View {
NavigationView {
List( 0...50, id: \.self ) { i in
NavigationLink(destination: HelloView()) {
Text("\(i)")
}
}
.navigationBarHidden( true )
}
}
}
struct HelloView: View {
var body: some View {
Text("Hello")
.navigationBarTitle("Hello", displayMode: .inline)
}
}

I realize this is odd, but this can be alleviated by setting the navigationBarTitle property. In your desired case I would recommend the following:
struct ContentView: View {
var body: some View {
NavigationView {
List( 0...50, id: \.self ) { i in
NavigationLink(destination: HelloView()) {
Text("\(i)")
}
}
.navigationBarTitle("", displayMode: .inline) /// <<--- Insert this line
.navigationBarHidden( true )
}
}
}
By setting the title attribute to blank and using the inline display mode, it rids the view of the large title and actually hides the view correctly.

Related

Non-deprecated way to call NavigationLink on Buttons

This is the old way of calling NavigationLink on Buttons
struct ContentView: View {
#State private var selection: String? = nil
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: View1(), tag: "tag1", selection: $selection) {
EmptyView()
}
NavigationLink(destination: NotView1(), tag: "tag2", selection: $selection) {
EmptyView()
}
Button("Do work then go to View1") {
// do some work that takes about 1 second
mySleepFunctionToSleepOneSecond()
selection = "tag1"
}
Button("Instantly go to NotView1") {
selection = "tag2"
}
}
.navigationTitle("Navigation")
}
}
}
This code works perfectly. It can go to different View targets depending on which button is clicked. Not only that, it guarantees all work is done BEFORE navigating to the target view. However, the only issue is that 'init(destination:tag:selection:label:)' was deprecated in iOS 16.0: use NavigationLink(value:label:) inside a List within a NavigationStack or NavigationSplitView
I get NavigationStack is awesome and such. But how can I translate the code to use the new NavigationStack + NavigationLink. Especially, how can I make sure work is done Before navigation?
Using new NavigationStack and its path property you can do much more. Your example will be transformed to
struct ContentView: View {
#State private var path = [String]()
var body: some View {
NavigationStack(path: $path) {
VStack {
Button("Do work then go to View1") {
// do some work that takes about 1 second
mySleepFunctionToSleepOneSecond()
path.append("tag1")
}
Button("Instantly go to NotView1") {
path.append("tag2")
}
}
.navigationTitle("Navigation")
.navigationDestination(for: String.self) { route in
switch route {
case "tag1":
EmptyView()
case "tag2":
EmptyView()
default:
EmptyView()
}
}
}
}
}
Check this video. There you can find more use cases.
For using non deprecated and after doing some work if we want to go to next view or in anyview there is something called ".navigationDestination". Let's see that using simple example.
#State var bool : Bool = false
var body: some View {
NavigationStack {
VStack {
Text("Hello, world!")
Button {
//Code here before changing the bool value
bool = true
} label: {
Text("Navigate Button")
}
}.navigationDestination(isPresented: $bool) {
SwiftUIView()
}
}
}
In this code we change take bool value as false and change it to true when our work is done using button.
.navigationDestination(isPresented: Binding<Bool>, destination: () -> View)
In .navigationDestination pass the Binding bool and provide the view you want to navigate.
You can use .navigationDestination multiple times.
Hope you found this useful.

Getting onFocusChange callback for Buttons in SwiftUI (tvOS)

The onFocusChange closure in the focusable(_:onFocusChange:) modifier allows me to set properties for the parent view when child views are focused, like this:
struct ContentView: View {
#State var text: String
var body: some View {
VStack {
Text(text)
Text("top")
.padding()
.focusable(true, onFocusChange: { focused in
text = "top focus"
})
Text("bottom")
.padding()
.focusable(true, onFocusChange: { focused in
text = "bottom focus"
})
}
}
}
But in the 2020 WWDC video where focusable is introduced, it is clearly stated that this wrapper in not intended to be used with intrinsically focusable views such as Buttons and Lists. If I use Button in place of Text here the onFocusChange works, but the normal focus behaviour for the Buttons breaks:
struct ContentView: View {
#State var text: String
var body: some View {
VStack {
Text(text)
Button("top") {}
.padding()
.focusable(true, onFocusChange: { focused in
text = "top focus"
})
Button("bottom") {}
.padding()
.focusable(true, onFocusChange: { focused in
text = "bottom focus"
})
}
}
}
Is there any general way to get an onFocusChange closure to use with Buttons that doesn't break their normal focusable behaviour? Or is there some other way to accomplish this?
Try using #Environment(\.isFocused) and .onChange(of:perform:) in a ButtonStyle:
struct ContentView: View {
var body: some View {
Button("top") {
// button action
}
.buttonStyle(MyButtonStyle())
}
}
struct MyButtonStyle: ButtonStyle {
#Environment(\.isFocused) var focused: Bool
func makeBody(configuration: Configuration) -> some View {
configuration.label
.onChange(of: focused) { newValue in
// do whatever based on focus
}
}
}
IIRC using #Environment(\.isFocused) inside a ButtonStyle may only work on iOS 14.5+, but you could create a custom View instead of a ButtonStyle to support older versions.

Toolbar does not appear properly SwiftUI

In my GeneralView I have a NavigationView And a Tab View.
Inside each tabItem I navigate with some ZStack (using zIndex, hiding and showing items)
Randomly leading and trailing items are not shown properly and can't be clicked.
See below, on top of screen back button is not full. But I select same button to go on the "Coureur1View"
Info : I do not have any other problem with this navigation.
In My generalView :
.toolbar {
ToolbarItemGroup(placement: .principal) {
TitleBarView().environmentObject(objCourse)
}}
.navigationBarItems(leading: TitleBarLeadingView(),
trailing: TitleBarTrailingView())
I don't have problem with TitleBarView (principale) but with leading and trailing
In my TitleBarLeadingView :
struct TitleBarLeadingView: View {
#EnvironmentObject var objGroupe : GroupeActuel
#EnvironmentObject var objCourse : CourseActuelle
#EnvironmentObject var zindex : Zindex
var body: some View {
HStack {
if zindex.selectedTab > 0 {
if zindex.detailCoureurVisible {
Button{
zindex.detailCoureurVisible = false
} label : {
Image(systemName: "chevron.backward")
Text("Back")
}.foregroundColor(.orange)
}else{
EmptyView()
}else{
EmptyView()
}
}
}
Provided snapshots are not testable, so just idea - try to recreate navigation bar items forcefully. It can be on some known changed value (I see titled changed on gif), but also can be just by UUID():
.navigationBarItems(leading: TitleBarLeadingView().id(UUID()),
trailing: TitleBarTrailingView().id(UUID()))
Note: make .id(param) is preferable because by UUID it will be recreated by each refresh.

SwiftUI How to change the NavigationBarTitle and add a button from a child form

I have a mainview which has 2 tabs. I placed the navigationView at the master page so I can access to its functionality in the child views. However, the title is never successfully assigned to the master view.
The second objective is adding a button on the navigation button from the child view. However, the form doesn't render it.
Here is the code. Could you please help me with changing the title from the child view and adding a button to NavigationView from the child view?
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView{
MainView()
.navigationBarTitle(Text("App Title"), displayMode: .inline)
}
}
}
struct MainView: View {
var body: some View {
TabView(){
DashboardView()
.navigationBarTitle(Text("Dashboard"), displayMode: .inline)
.tabItem{
//Image(systemName: "list.dash")
Image(systemName: "chart.pie")
Text("Dashboard")
}.tag(0)
AssignmentView()
.navigationBarTitle(Text("Assignments"), displayMode: .inline)
.tabItem{
Image(systemName: "briefcase")
Text("Assignments")
}.tag(1)
}
}
}
struct DashboardView: View {
var body: some View {
Text("Dashboard View")
.navigationBarTitle(Text("Dashboard View Title"), displayMode: .inline)
}
}
struct AssignmentView: View {
var body: some View {
Text("Assignment View")
.navigationBarTitle(Text("Assignments View Title"), displayMode: .inline)
.navigationBarItems(trailing: Button(action: {
print("Dashboard button click")
}) {
Text("Submit")
})
}
}
For proper navigation features work there should be only one NavigationView in view stack. I assume you just need to remove first NavigationView, because your MainView is TabView container
struct ContentView: View {
var body: some View {
MainView()
}
}
At least for me, such variant gives native look&feel.
I removed the NavView from the MainView and added NavViews to the subviews with the help of new struct NavigationTab.
struct NavigationTab<Title, Content>: View where Title: StringProtocol, Content: View {
var title: Title
var content: () -> Content
var body: some View {
NavigationView {
content()
.navigationBarTitle(Text(title), displayMode: .inline)
}
}
}
The tab items now look like
NavigationTab(title: "Dashboard" ) {
DashboardView()
.navigationBarItems(leading: Button(action: {
self.isDrawerOpen.toggle()
}) {
Image(systemName: "sidebar.left")
})
}
.tabItem{
Image(systemName: "chart.pie")
Text("Dashboard")
}.tag(1)
I solved an initial problem of adding button on the NavView by replicating DrawerOpen Button in multiple tabs and assign the same "isDrawerOpen" variable.
But I still have a use case in the process where I need to add a button to the NavView.trailing for Submitting. Is there a way to add a button in a subview (for example DashboardView calls another view)?

How to make SwiftUI NavigationLink work in edit mode?

I have a list of items. Clicking on one should push a new view to the navigation stack. I notice the NavigationLink doesn't work if the list is in edit mode. Is there a way to control that? I need it to work in edit mode.
List {
ForEach(segments) { segment in
NavigationLink(destination: EditSegmentView(segment: segment)) {
Text(segment.title)
}
}.onDelete(perform: onDelete)
.onMove(perform: onMove)
}.environment(\.editMode, $alwaysTrue)
I now have this working the way I wanted. I used a different NavigationLink initializer, with the tag and selection arguments. It seems to work well, but I don't know if this is the intended use of that initializer, because the documentation is painfully sparse.
#State var segmentSelection: Segment.ID? = nil
var body: some View {
NavigationView {
...
List {
ForEach(workout.segments) { segment in
NavigationLink(destination: EditSegmentView(segment: segment),
tag: segment.id,
selection: self.$segmentSelection) {
Text(segment.title)
}
.onTapGesture(perform: { self.segmentSelection = segment.id })
}.onDelete(perform: onDelete)
.onMove(perform: onMove)
}.environment(\.editMode, Binding.constant(.active))
...
}
}