I am building a macOS SwiftUI app in which I need to perform navigation from my View A to View B. Here is a small screenshot.
Here is my implementation:
struct ViewA: View {
var body: some View {
VStack {
Text("VIEW A")
Button("Go to View B") {
// I want to go to View B and also pass some data to View B
}
}
}
}
struct ViewB: View {
let data: String
var body: some View {
VStack {
Text("VIEW B")
}
}
}
struct ContentView: View {
#EnvironmentObject var appState: AppState
var body: some View {
NavigationView {
SideBar()
ViewA()
}
}
}
In iOS I would use NavigationLink, but for macOS apps NavigationLink is not working as expected. Any ideas?
Related
Hi I am pretty new to using SwiftUI. I have been having trouble with adding a tap gesture to my welcome page that will allow the user to move on to another view with drop down question boxes. I currently have this:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "Globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Welcome to WOD planner!")
.font(.title)
.multilineTextAlignment(.center)
.onTapGesture{ action:{
//go to question page
}
}
}
.padding()
}
}
I made another view labeled QuestionPage. Im just confused how to code this gesture to make it change and isolate the two different views.
Thanks!
I solve this by having a State variable that will track the View that I want to show. Within the WindowGroup in my App, I now present the View that the viewState refers to.
This sample code should do the trick, it will change Views if you tap on the text in the middle of the screen. I personally would rather use a button, but it can be done with onTapGesture:
enum ViewState {
case welcomeView
case questionView
}
#main
struct MyApp: App {
#State var currentView: ViewState = .welcomeView
var body: some Scene {
WindowGroup {
switch (currentView) {
case .welcomeView:
WelcomeView(viewState: $currentView)
case .questionView:
QuestionView(viewState: $currentView)
}
}
}
}
struct WelcomeView: View {
#Binding var viewState: ViewState
var body: some View {
// your view here
Text("Welcome")
.onTapGesture { viewState = .questionView }
}
}
struct QuestionView: View {
#Binding var viewState: ViewState
var body: some View {
// your view here
Text("Some questions")
.onTapGesture { viewState = .welcomeView }
}
}
The view navigation hierarchy of my code is as follows:
ColorsView
WarmColorsView
RedView
CoolColorsView
Which is ColorsView can navigate directly to WarmColorsView and CoolColorView, and WarmColorsView can navigate directly to RedView.
Here is code (very simple):
import SwiftUI
class Model: ObservableObject {
#Published var tagNavToWarmOrCool: String?
#Published var isNavToRed = false
}
struct RedView: View {
#EnvironmentObject var model: Model
var body: some View {
VStack {
Button("to cool colors"){
model.isNavToRed = false
model.tagNavToWarmOrCool = "cool"
}
}
}
}
struct CoolColorsView: View {
var body: some View {
VStack {
}
.navigationTitle("Cool Colors")
}
}
struct WarmColorsView: View {
#EnvironmentObject var model: Model
var body: some View {
NavigationLink("red", destination: RedView(), isActive: $model.isNavToRed)
.navigationTitle("Warm Colors")
}
}
struct ColorsView: View {
#EnvironmentObject var model: Model
var body: some View {
VStack {
NavigationLink("to warm colors", destination: WarmColorsView(), tag: "warm", selection: $model.tagNavToWarmOrCool)
NavigationLink("to cool colors", destination: CoolColorsView(), tag: "cool", selection: $model.tagNavToWarmOrCool)
}
.navigationTitle("Colors")
}
}
struct ContentView: View {
#StateObject var model = Model()
var body: some View {
NavigationView {
ColorsView()
}
.navigationViewStyle(StackNavigationViewStyle())
.environmentObject(model)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(Model())
}
}
My intention is to go to the RedView, and then click the button to navigate to the CoolColorsView.
Running in iOS 14.2, however it ends up navigating to the ColorsView, I tried to change NavigationView's style to default, but it didn't work.
There is no such problem in iOS 15.4.1!
So how to navigate from RedView to CoolColorsView by click button in RedView in iOS 14.2? Thanks a lot! :)
Coming from Android and working on a very complex application , i would like to use NavigationView as much as possible. Having one view and make all elements appear and disappear on this view seems impossible to handle for me .
I was using navigationView to navigate bewteen views with navigationBar hidden .
This way navigating or making view appear is transparent for the user
After some tests , i encounter limitations : at the 13th or 14 th level of navigation everything disappear and app basically crashes .
Once more , this is a direct navigation between 2 content views , no HOMESCREEN
import SwiftUI
struct test4: View {
#State private var intent3: Bool = false
var body: some View {
NavigationView{
VStack{
NavigationLink(destination : test3() , isActive : $intent3) { }
Text("ver 4")
.onTapGesture {
intent3 = true }
Spacer()
}
}
.navigationBarHidden(true)
}
}
import SwiftUI
struct test3: View {
#State private var intent4: Bool = false
var body: some View {
NavigationView{
VStack{
NavigationLink(destination : test4() , isActive : $intent4) { }
Text("ver 3")
.onTapGesture {
intent4 = true }
Spacer()
}
}.navigationBarHidden(true) }
}
Here a basic example of navigation directly between 2 contents views . Crashes after 14/15 clicks. I encounter the same issue with about any navigation link.
Update:
With your added code, I can see the initial crash was a result of adding a new NavigationView each time. This solves it:
struct ContentView: View {
var body: some View {
NavigationView {
Test3()
}
}
}
struct Test4: View {
#State private var intent3: Bool = false
var body: some View {
VStack{
NavigationLink(destination : Test3() , isActive : $intent3) { }
Text("ver 4")
.onTapGesture {
intent3 = true
}
Spacer()
}
.navigationBarHidden(true)
}
}
struct Test3: View {
#State private var intent4: Bool = false
var body: some View {
VStack{
NavigationLink(destination : Test4() , isActive : $intent4) { }
Text("ver 3")
.onTapGesture {
intent4 = true }
Spacer()
}
.navigationBarHidden(true)
}
}
Original answer:
However, there are solutions to pop to the top of a navigation hierarchy.
One way is to use isActive to manage whether or not a given NavigationLink is presenting its view. That might look like this:
class NavigationReset : ObservableObject {
#Published var rootIsActive = false
func popToTop() {
rootIsActive = false
}
}
struct ContentView: View {
#StateObject private var navReset = NavigationReset()
var body: some View {
NavigationView {
NavigationLink(destination: DetailView(title: "First"), isActive: $navReset.rootIsActive) {
Text("Root nav")
}
}.environmentObject(navReset)
}
}
struct DetailView : View {
var title : String
#EnvironmentObject private var navReset : NavigationReset
var body: some View {
VStack {
NavigationLink(destination: DetailView(title: "\(Date())")) {
Text("Navigate (\(title))")
}
Button("Reset nav") {
navReset.popToTop()
}
}
}
}
Another trick you could use is changing an id on a NavigationLink -- as soon as that happens, it re-renders and becomes inactive.
class NavigationReset : ObservableObject {
#Published var id = UUID()
func popToTop() {
id = UUID()
}
}
struct ContentView: View {
#StateObject private var navReset = NavigationReset()
var body: some View {
NavigationView {
NavigationLink(destination: DetailView(title: "First")) {
Text("Root nav")
}
.id(navReset.id)
}.environmentObject(navReset)
}
}
struct DetailView : View {
var title : String
#EnvironmentObject private var navReset : NavigationReset
var body: some View {
VStack {
NavigationLink(destination: DetailView(title: "\(Date())")) {
Text("Navigate (\(title))")
}
Button("Reset nav") {
navReset.popToTop()
}
}
}
}
It works by marking the first NavigationLink (ie the one on the Home Screen) with an id. As soon as that id is changed, the NavigationLink is recreated, popping all of the views off of the stack.
I've created a SwiftUI "multiplatform" (iOS and macOS) app from the Xcode 12 beta 6 (12A8189n) app template.
My issue is that one of my views, AnotherView, is displaying incorrectly. Here's a gif showing the problem. Notice that AnotherView displays with the navigation stack already pushed to a non-existent view. Tapping the back button reveals the expected screen, however it is displayed only partially filling the expected area.
Here's the code:
TestNavigationApp.swift
import SwiftUI
#main
struct TestNavigationApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
#State private var presentingFirstView = false
var body: some View {
Button(action: { self.presentingFirstView = true }) {
Text("Present First View")
}
.sheet(isPresented: $presentingFirstView) {
FirstView(isPresented: $presentingFirstView)
}
}
}
FirstView.swift
import SwiftUI
struct FirstView: View {
#Binding var isPresented: Bool
var body: some View {
NavigationView {
EmbeddedView()
.navigationBarTitle("First View", displayMode: .large)
}
}
}
EmbeddedView.swift
import SwiftUI
struct EmbeddedView: View {
#State private var presentingAnotherView = false
var body: some View {
VStack {
Text("Embedded View")
Button(action: { self.presentingAnotherView = true }) {
Text("Present Another View")
}
}
.sheet(isPresented: $presentingAnotherView) {
AnotherView(isPresented: $presentingAnotherView)
}
}
}
AnotherView.swift
import SwiftUI
struct AnotherView: View {
#Binding var isPresented: Bool
var body: some View {
NavigationView {
Text("Another View")
.navigationBarTitle("Another View", displayMode: .large)
}
}
}
Anyway, not really sure what's happening here. Any suggestions appreciated.
Try to use navigation view style explicitly
var body: some View {
NavigationView {
Text("Another View")
.navigationBarTitle("Another View", displayMode: .large)
}.navigationViewStyle(StackNavigationViewStyle())
}
Given this simple NavigationView:
struct ContentView : View {
var body: some View {
NavigationView {
VStack {
NavigationLink("Push Me", destination: Text("PUSHED VIEW"))
}
}
}
}
Did anyone find a way of disabling the NavigationView animation when a destination view is pushed/popped into/from the stack?
This has been possible in UIKit since iOS2.0! I think it is not too much to ask from the framework. I tried all sorts of modifiers on all views (i.e., the NavigationView container, the destination view, the NavigationLink, etc)
These are some of the modifiers I tried:
.animation(nil)
.transition(.identity)
.transaction { t in t.disablesAnimations = true }
.transaction { t in t.animation = nil }
None made a difference. I did not find anything useful in the EnvironmentValues either :-(
Am I missing something very obvious, or is the functionality just not there yet?
Xcode 11.3:
Right now there is no modifier to disable NavigationView animations.
You can use your struct init() to disable animations, as below:
struct ContentView : View {
init(){
UINavigationBar.setAnimationsEnabled(false)
}
var body: some View {
NavigationView {
VStack {
NavigationLink("Push Me", destination: Text("PUSHED VIEW"))
}
}
}
}
First you need state for the NavigationLink to respond to, then set that state inside a transaction with animations disabled, as follows:
struct ContentView : View {
#State var isActive = false
var body: some View {
NavigationView {
VStack {
NavigationLink(isActive: $isActive, destination: {
Text("PUSHED VIEW")}) {
Text("Push Me")
}
Button("Navigate Without Animation") {
var transaction = Transaction()
transaction.disablesAnimations = true
withTransaction(transaction) {
isActive = true
}
}
}
}
}
}
I recently created an open source project called swiftui-navigation-stack (https://github.com/biobeats/swiftui-navigation-stack) that contains the NavigationStackView, a view that mimics the navigation behaviours of the standard NavigationView adding some useful features. For example, you could use the NavigationStackView and disable the transition animations as requested by Kontiki in the question. When you create the NavigationStackView just specify .none as transitionType:
struct ContentView : View {
var body: some View {
NavigationStackView(transitionType: .none) {
ZStack {
Color.yellow.edgesIgnoringSafeArea(.all)
PushView(destination: View2()) {
Text("PUSH")
}
}
}
}
}
struct View2: View {
var body: some View {
ZStack {
Color.green.edgesIgnoringSafeArea(.all)
PopView {
Text("POP")
}
}
}
}
PushView and PopView are two views that allow you push and pop views (similar to the SwiftUI NavigationLink). Here is the complete example:
import SwiftUI
import NavigationStack
struct ContentView : View {
var body: some View {
NavigationStackView(transitionType: .none) {
ZStack {
Color.yellow.edgesIgnoringSafeArea(.all)
PushView(destination: View2()) {
Text("PUSH")
}
}
}
}
}
struct View2: View {
var body: some View {
ZStack {
Color.green.edgesIgnoringSafeArea(.all)
PopView {
Text("POP")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The result is:
It would be great if you guys joined me in improving this open source project.