SwiftUi - Navigation View - How to show two views on iPad - ipad

I would like the iPad to show my first view1 list in the side bar and a second view alongside, populated by whatever is selected in view1. Then, when selecting something from the view2 list. go to the third view in full screen.
In my simple example, on loading I just get a blank screen with "< Back". On click, I get a side bar with my view1, but nothing else.
Obviously doing something wrong. Grateful for help.
My example:
struct firstView: View {
let myFirstArray = [
"First",
"Second",
"Third"
]
var body: some View {
NavigationView {
List(myFirstArray, id: \.self) { value in
NavigationLink (destination: SecondView()) {
Text("FirstView \(value)")
}
.navigationBarTitle("First View", displayMode: .inline)
}
}
}
}
struct SecondView: View {
let mySecondArray = [
"One",
"Two",
"Three"
]
var body: some View {
List (mySecondArray, id: \.self) { value in
NavigationLink (destination: ThirdView()) {
Text("Second View \(value)")
}
}
}
}
struct ThirdView: View {
var body: some View {
Text("Hello World")
}
}

You need to provide default detail view for double column navigation view style (which you see as default for iPad - different sidebar visibility for portrait/landscape orientation)
var body: some View {
NavigationView {
List(myFirstArray, id: \.self) { value in
NavigationLink (destination: SecondView()) {
Text("FirstView \(value)")
}
.navigationBarTitle("First View", displayMode: .inline)
}
SecondView() // << here !!
}
}

Related

Multiple back buttons created when navigates to root screen in SwiftUI

say I create 3 screens in SwiftUI which contains a NavigationLink to the next screen. like, first screen navigates to 2nd screen. 2nd screen navigates to third. and the third screen navigates to the first screen. In this case even if I use NavigationView only once(in the first screen). I encountered that there's a back button forming when I navigate to 1st screen from the third screen. And it keeps adding up when I start to navigate from then on. I have tried to use .navigationBarBackButtonHidden(true). It hides it but the space taken by back button was still there.
My code is similar to this:
struct FirstScreen: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: SecondScreen()) {
Text("Go to Second Screen")
}
}
}
}
}
struct SecondScreen: View {
var body: some View {
VStack {
NavigationLink(destination: ThirdScreen()) {
Text("Go to Third Screen")
}
}
}
}
struct ThirdScreen: View {
var body: some View {
VStack {
NavigationLink(destination: FirstScreen()) {
Text("Go to First Screen")
}
}
}
}
this is the image
You're pushing the FirstScreen onto your navigation stack, but FirstScreen contains its own NavigationView. If you really want to keep pushing them on the stack, then move the NavigationView outside of FirstScreen.
struct ContentView: View {
var body: some View {
NavigationStack { // Use NavigationStack for iOS 16
FirstScreen()
}
}
}
struct FirstScreen: View {
var body: some View {
VStack {
NavigationLink(destination: SecondScreen()) {
Text("Go to Second Screen")
}
}
}
}
If you actually want to pop all the views off the stack and go back to FirstScreen you should use
init(path: Binding<NavigationPath>, etc)
Here's a simple example passing the path down the stack and resetting it to pop back to the root…
enum Screen {
case two, three
}
struct ContentView: View {
#State var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
VStack {
// value gets appended to path
NavigationLink("Go to Second Screen", value: Screen.two)
}
// Decides which screen to show for values in path
.navigationDestination(for: Screen.self) { index in
switch index {
case .two:
SecondScreen(path: $path)
case .three:
ThirdScreen(path: $path)
}
}
}
}
}
struct SecondScreen: View {
#Binding var path: NavigationPath
var body: some View {
VStack {
NavigationLink("Go to Third Screen", value: Screen.three)
}
}
}
struct ThirdScreen: View {
#Binding var path: NavigationPath
var body: some View {
VStack {
Button("Go to First Screen") {
// reset the path to pop to root
path = NavigationPath()
}
}
}
}

SwiftUI NavigationView: Keeping back button while removing whitespace

Screen shot of white space
I want to remove the empty space below the <Back button in the second navigation view. I know that this question has been asked several times before, but I have not been able to find a good solution that does this.
I have tried
.navigationBarTitle("")
.navigationBarHidden(true)
and
.navigationBarTitle("", displayMode: .inline)
without the desired result.
Any hints that could help me?
struct SecondNavView: View {
let item: String
var body: some View {
ZStack {
Color.red
Text(item)
}
}
}
struct FirstNavView: View {
let listItems = ["One", "Two", "Three"]
var body: some View {
NavigationView {
List(listItems, id: \.self) { item in
NavigationLink(destination: SecondNavView(item: item)) {
Text(item).font(.headline)
}
}
}
}
}
I assume it is do to place of applied modifiers.
The following works (tested with Xcode 13.4 / iOS 15.5)
struct SecondNavView: View {
let item: String
var body: some View {
ZStack {
Color.red
Text(item)
}
.navigationBarTitleDisplayMode(.inline) // << here !!
}
}
It seens like your parent View hasn't a title, to solve this you need to set .navigationTitle inside NavigationView on parent View like this:
NavigationView {
VStack {
//....
}
.navigationTitle(Text("Awesome View"))
.toolbar {
ToolbarItem(placement: .principal){
// Put any view (Text, Image, Stack...) you want here
}
}
}

SwiftUI: How to “deep-link” to the second/third level of a list in a navigation view

I recently encountered an issue with "deep-linking" to the third level of a list in a navigation view.
Here is some background:
My app has a settings menu that is based on SwiftUI lists/forms
As typical for a menu you can jump from one list item to the next list (one level "deeper") - this is realized NavigationLink
Because the list on the top level is embedded in a navigation view, SwiftUI automatically adds a "back" button to the child views on the lower levels to get back to the first menu page
So far, so easy. Now, here is what I attempt to do (please also refer to the attached illustration):
I want to jump from the first view (potentially also from other unrelated views) directly to as sub-menu; i.e. a list that is on the second or third level of my menu
What I did so far is to set a programmatic navigation link that links directly to what is named "View 3" in the illustration. However, this does not provide the possibility to jump back to "View 2" and then to "View 1" via the back buttons in the navigation view
Now my question is: Is it possible to jump to "View 3" (potentially from anywhere in my application) and still be able to go back to "View 2" and then to "View 1" via the back buttons in the navigation view?
This is view 1:
struct SwiftUIView1: View {
var body: some View {
NavigationView {
VStack {
List {
NavigationLink("Link", destination: SwiftUIView2())
Text("TBD")
Text("TBD")
}
.navigationTitle("View 1")
Button("Jump to view 3", action: XXX) // <-- What to put here?
}
}
}
}
This is view 2:
struct SwiftUIView2: View {
var body: some View {
List {
NavigationLink("Link", destination: SwiftUIView3())
Text("TBD")
Text("TBD")
}
.navigationTitle("View 2")
}
}
This is view 3:
struct SwiftUIView3: View {
var body: some View {
Text("Hello world")
.navigationTitle("View 3")
}
}
Here is a visualization of what I want to achieve:
I would appreciate any ideas!
Not the best solution, but if you want to achieve a deep link and still be able to go back to #2 and then #1, you might try this solution.
It automatically goes from #1 to #3, however through #2. Otherwise you will not be able to get a back button and go back to #2. That's not possible as it is the default Navigation behaviour...
class NavigationManager: ObservableObject {
#Published var goToThird: Bool = false
}
struct ContentView: View {
#State var isActive: Bool = false
var navigationManager = NavigationManager()
var body: some View {
NavigationView {
VStack {
List {
NavigationLink("Link", destination: SwiftUIView2(manager: navigationManager), isActive: $isActive)
Text("TBD")
Text("TBD")
}
.navigationTitle("View 1")
Button("Jump to view 3", action: {
isActive = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
navigationManager.goToThird = true
}
}) // <-- What to put here?
}
}
}
}
struct SwiftUIView2: View {
#ObservedObject var manager: NavigationManager
var body: some View {
List {
NavigationLink("Link", destination: SwiftUIView3(), isActive: $manager.goToThird)
Text("TBD")
Text("TBD")
}
.navigationTitle("View 2")
}
}
struct SwiftUIView3: View {
var body: some View {
Text("Hello world")
.navigationTitle("View 3")
}
}

SwiftUI Show navigation bar title on the back button but not in the previous View

I have two views, one leads to the other. I want that the second view uses the title of the first view for the back button, which should then be: "<View1".
I don't want to show the title in the first view.
Problem: I can't hide navigation bar because it will also hide a custom button which is within it. Setting .navigationTitle("") hides the title in the first view, but also hides it from the back button in the second view.
What I have now:
What I would like to have:
Code:
struct ContentView: View {
#State var isLinkActive = false
var body: some View {
NavigationView {
VStack {
NavigationLink("go to the second view", destination: SecondView(), isActive: $isLinkActive).navigationTitle("View1")
.navigationBarItems(leading: Button(action: {
()
}, label: {
Text("custom button")
}))
}
}.navigationViewStyle(StackNavigationViewStyle())
}
private func btnPressed() {
isLinkActive = true
}
}
struct SecondView: View {
var body: some View {
Color.blue
}
}
You need to create custom back button for destination view as well,and you shouldn’t set navigation title for navigationLink, that’s why you are not able to hide “View1” correctly.
Check below code.
import SwiftUI
struct Test: View {
#State var isLinkActive = false
var body: some View {
NavigationView {
VStack {
NavigationLink("go to the second view", destination: SecondView()
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action: {
isLinkActive = false
}, label: {
HStack{
Image(systemName: "backward.frame.fill")
Text("View1")
}
})) ,
isActive: $isLinkActive)
}.navigationBarItems(leading: Button(action: {
()
}, label: {
Text("custom button")
}))
}.navigationViewStyle(StackNavigationViewStyle())
}
private func btnPressed() {
isLinkActive = true
}
}
struct SecondView: View {
var body: some View {
Color.blue
}
}
You can try and make navigationBar code as reusable component, because you might need to do this at multiple places.
Output-:
I achieved this by using two modifiers on my main view. Similar to your case, I didn't want a title on the first view, but I wanted the back button on the pushed view to read < Home, not < Back.
.navigationTitle("Home")
.toolbar {
ToolbarItem(placement: .principal) {
Text("")
}
}

How to display missing back button in Master-Detail1-Detail2 view in SwiftUI in landscape?

I'm using a NavigationView and NavigationLinks to move from a MasterView to a Detail1View and then further to a Detail2View.
On an iPhone in portrait mode, the back button is displayed for all detail views and allows to move back from Detail2View to Detail1View, and then further to MasterView.
But on an iPhone in landscape mode or on an iPad, when moving from Detail1View to Detail2View, there is no back button. And it is thus impossible to go back to Detail1View.
Adding a 2nd NavigationView allows to have a back button, but it's not really a desirable workaround, as the 2nd UINavigationViewController is shown below the 1st one.
struct MyMasterView: View {
private let names = ["Homer", "Marge", "Bart", "Lisa"]
var body: some View {
List() {
Section(header: Text("The Simpsons")) {
ForEach(names, id: \.self) { name in
NavigationLink(destination: MyDetail1View(name: name)) {
Text(name)
}
}
}
}
.navigationBarTitle(Text("My App"))
}
}
struct MyDetail1View: View {
var name: String
var body: some View {
//NavigationView {
List {
NavigationLink(destination: MyDetail2View(name: name)) {
Text("Hello \(name)")
}
}
.navigationBarTitle(Text("Details 1"))
//}
}
}
struct MyDetail2View: View {
var name: String
var body: some View {
HStack {
Text("\(name), I'm sorry but in Landscape there is no back button...")
}
.navigationBarTitle(Text("Details 2"))
}
}
struct ContentView : View {
var body: some View {
NavigationView {
MyMasterView()
Text("In Landscape, swipe to start...")
}
}
}
The answer turns out to be as simple as using isDetailLink()!
NavigationLink(destination: MyDetail2View(name: name)) {
Text("Hello \(name)")
}.isDetailLink(false)
Credits to https://stackoverflow.com/a/57400873/2893408

Resources