Why does NavigationView have to be a top-level view? - ios

I am new to iOS and SwiftUI - why does NavigationView even exist? Why isn't there just NavigationLinks whenever we need to link somewhere?
And why does NavigationView have to be the top level view? Semantically it just doesn't make sense as the top level View should be VStack, Zstack etc
Currently:
struct ContentView : View {
var body: some View {
NavigationView {
VStack {
Text("Hello World")
NavigationLink(destination: DetailView()) {
Text("Do Something")
}
}
}
}
}
Should be:
struct ContentView : View {
var body: some View {
VStack {
Text("Hello World")
NavigationView {
NavigationLink(destination: DetailView()) {
Text("Do Something")
}
}
}
}
}
Any idea?

SwiftUI gives you a whole bunch of conveniences practically for free.
One of those is an interface building block called NavigationView.
That interface building block has some convenient features and abilities, such as giving you automatic navigation interface (like back buttons to the original View in the navigation bar of the view you followed via a NavigationLink, etc).
Most of those conveniences simply wouldn’t make sense if it wasn’t the top level of your view.
Is there something you wanted from NavigationView at a lower level? I can’t imagine what, but I suspect there’s another SwiftUI building block that will achieve it, made specifically for the context.

Related

SwiftUI NavigationView nested bar issue

I have a View with a list with navigationLinks, and show a list of items to DetailView, and DetailView can go to another View and so on. Therefore it will have a full navigation stack in here.
However, in our app, we have two ways to show this view, first, a presenting View to show this view. Second, by other navigationLink under another NavigationView to push to this view. In the first case, it is fine. however in second case, it will show nested navigation bar, which I don't really like it.
Is there any possible way to show the following view without any nested navigationBar, in the pushing and presenting(UIKit wording) way
var body: some View {
NavigationView {
List(items) { item in
NavigationLink(destination: DetailView(item)) {
ItemRow(item)
}
}
.navigationBarTitle("Item list")
}
}
thanks a lot!
As Asperi says, you must separate the View into two Views. You have double NavigationBar because in the case where you already enter with the previous NavigationView at the top level of the View hierarchy, having another NavigatioView makes it double the NavigationBar.
You must do something like this in the View before this one:
var body: some View {
VSTack {
Text("Hello Word")
Button(action: {
if isShowWithPresent { // Some Bool
NavigationView {
SomeView()
}
} else {
AnotherView() // Some View without the NavigationView in the code
}
}) {
Text("Hit Me!")
}
}
}

Does SwiftUI have a built-in, stack-based navigator that does not require a list layout?

I want a way to have stack-based navigation in SwiftUI. Whenever I try to look up how to do that, I get information about NavigationView. However, it looks like NavigationView is intended to be used to display a list where each entry navigates to a page when tapped. Is there way to have stack-based navigation like that of NavigationView without having to conform to a list structure?
So, NavigationView is used to enable navigation to other views. This can be used with any type of view. The following example will show two screens: the ContentView and the DetailView.
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Text("Main View")
NavigationLink("Go to Detail", destination: DetailView())
}
.navigationBarTitle("Content View")
}
}
}
struct DetailView: View {
var body: some View {
Text("Detail body")
.navigationBarTitle("Detail")
}
}
If you press on the 'Go to Detail' Navigation Link, then SwiftUI pushes the destination view onto the screen. Pressing the back button on the detail view will pop the current view and return to the ContentView. You can modify the NavigationView with titles and buttons using some modifiers, but note that the .navigationBarTitle() modifier has to be used on the inside of the NavigationView, not the outside.
In summary, the NavigationView can be used with any type of view and a List isn't required.

Why is SwiftUI picker in form repositioning after navigation?

After clicking the picker it navigates to the select view. The item list is rendered too far from the top, but snaps up after the animation is finished. Why is this happening?
Demo: https://gfycat.com/idioticdizzyazurevase
I already created a minimal example to rule out navigation bar titles and buttons, form sections and other details:
import SwiftUI
struct NewProjectView: View {
#State var name = ""
var body: some View {
NavigationView {
Form {
Picker("Client", selection: $name) {
Text("Client 1")
Text("Client 2")
}
}
}
}
}
struct NewProjectView_Previews: PreviewProvider {
static var previews: some View {
NewProjectView()
}
}
This happens in preview mode, simulator and on device (Xcode 11.2, iOS 13.2 in simulator, 13.3 beta 1 on device).
The obviously buggy behavior can be worked around when forcing the navigation view style to stacked:
NavigationView {
…
}.navigationViewStyle(StackNavigationViewStyle())
This is a solution to my problem, but I won‘t mark this as accepted answer (yet).
It seems to be a bug, even if it may be triggered by special circumstances.
My solution won‘t work if you need another navigation view style.
Additionally, it won‘t fix the horizontal repositioning mentioned by DogCoffee in the comments.
In my opinion, it has something to do with the navigation bar. In default (no mention of .navigationBarTitle extension), the navigation display mode is set to .automatic, this should be amend to .inline. I came across another post similar to this and use their solution to combine with yours, by using .navigationBarTitle("", displayMode: .inline) should help.
import SwiftUI
struct NewProjectView: View {
#State var name = ""
var body: some View {
NavigationView {
Form {
Picker("Client", selection: $name) {
Text("Client 1")
Text("Client 2")
}
}
.navigationBarTitle("", displayMode: .inline)
}
}
}
struct NewProjectView_Previews: PreviewProvider {
static var previews: some View {
NewProjectView()
}
}
Until this bug is resolved another way to work around this issue while retaining the DoubleColumnNavigationViewStyle for iPads would be to conditionally set that style:
let navView = NavigationView {
…
}
if UIDevice.current.userInterfaceIdiom == .pad {
return AnyView(navView.navigationViewStyle(DoubleColumnNavigationViewStyle()))
} else {
return AnyView(navView.navigationViewStyle(StackNavigationViewStyle()))
}
Thanks for this thread everyone! Really helped me understand things more and get a hold of one of my problems. To share with others, I was having this problem to but I was also having this problem when I set a section to appear in a if/else statement set on a section with a toggle. When the toggle was activated it would shift the section header horizontally a few pixels.
The following is how I fixed it
Section(header: Text("Subject Identified").listRowInsets(EdgeInsets()).padding(.leading)) {
Picker(selection: $subIndex, label: Text("Test")) {
ForEach(0 ..< subIdentified.count) {
Text(self.subIdentified[$0]).tag($0)
}
}
.labelsHidden()
.pickerStyle(SegmentedPickerStyle())
I'm still having horizontal shift on my picker selection view and not sure how to fix. I created another thread to received input. Thanks again! SwiftUI Shift Picker Text Horizontal

Lifecycling in SwifUI: Running code when leaving a child view of a NavigationView hierarchy

With SwiftUI's NavigationView we benefit from simplicity with code construction. However, not exposed, at least in these early stages are the overrides. Further, the reduced focus on managing the LifeCycle of views makes it difficult to find out what and when to call something based on the state of the view.
I would like to run some code when a user chooses to go back up a NavigationView hierarchy (i.e. click the back button supplied by NavigationView).
I've tried onDisappear() {} and it's other variants and I cannot get it to work. It seems like it is not called. The onAppear() {} does work so I'm stumped.
Any help would be SUPER APPRECIATED!
I'm quite certain that at this stage, there is no method that can be override to catch the 'back' action of the navigation view.
However, I did figure out that I can hide the NavigationView's back button and add a custom one myself where I can call the code before dismissing the child.
import SwiftUI
struct SecondView: View {
var body: some View {
Text("Second View")
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: NavigationViewCustomBack())
}
}
==========================
import SwiftUI
struct NavigationViewCustomBack: View {
var body: some View {
HStack{
Image(systemName: "chevron.left").foregroundColor(.blue).font(Font.title.weight(.medium))
Text("Home").padding(.leading, -5)
}
}
}
struct NavigationViewCustomBack_Previews: PreviewProvider {
static var previews: some View {
NavigationViewCustomBack()
}
}

What's the equivalent of the UISplitViewController in SwiftUI

I need to implement an UI which close to the default Mail app in iPad and iPhone.
The App has two sections, typically, the master view will be displayed on the left side and detail view will be displayed in the right side in iPad.
In the phone, the master view will be displayed on whole screen, the detail view can be pushed as second screen.
How to implement it in the new SwiftUI
There is not really a SplitView in SwiftUI, but what you describe is automatically accomplished when you use the following code:
import SwiftUI
struct MyView: View {
var body: some View {
NavigationView {
// The first View inside the NavigationView is the Master
List(1 ... 5, id: \.self) { x in
NavigationLink(destination: SecondView(detail: x)) {
Text("Master\nYou can display a list for example")
}
}
.navigationBarTitle("Master")
// The second View is the Detail
Text("Detail placeholder\nHere you could display something when nothing from the list is selected")
.navigationBarTitle("Detail")
}
}
}
struct SecondView: View {
var detail: Int
var body: some View {
Text("Now you are seeing the real detail View! This is detail \(detail)")
}
}
This is also why the .navigationBarTitle() modifier should be applied on the view inside the NavigationView, instead of on the NavigationView itself.
Bonus: if you don't like the splitView navigationViewStyle, you can apply the .navigationViewStyle(StackNavigationViewStyle()) modifier on the NavigationView.
Edit: I discovered that the NavigationLink has an isDetailLink(Bool) modifier. The default value appears to be true, because by default the "destination" is presented in the detail view (on the right). But when you set this modifier to false, the destination is presented as a master (on the left).

Resources