macOS NavigationStack use push and pop animation - swiftui

how to set the Push / Pop animation in macOS for a NavigationStack ? as currently there is no animation and i want to push / pop the controller sliding right (sliding left), similar to AppStore app
enum Nav {
case a
}
struct MainView: View {
var body: some View {
NavigationStack {
NavigationLink(value: Nav.a) {
Text("Go to a")
}
.navigationDestination(for: Nav.self) { path in
switch path {
case .a:
Text("a")
}
}
}
}
}

Related

NavigationStack not affected by EnvironmentObject changes

I'm attempting to use #EnvironmentObject to pass an #Published navigation path into a SwiftUI NavigationStack using a simple wrapper ObservableObject, and the code builds without issue, but working with the #EnvironmentObject has no effect. Here's a simplified example that still exhibits the issue:
import SwiftUI
class NavigationCoordinator: ObservableObject {
#Published var path = NavigationPath()
func popToRoot() {
path.removeLast(path.count)
}
}
struct ContentView: View {
#StateObject var navigationCoordinator = NavigationCoordinator()
var body: some View {
NavigationStack(path: $navigationCoordinator.path, root: {
FirstView()
})
.environmentObject(navigationCoordinator)
}
}
struct FirstView: View {
var body: some View {
VStack {
NavigationLink(destination: SecondView()) {
Text("Go To SecondView")
}
}
.navigationTitle(Text("FirstView"))
}
}
struct SecondView: View {
var body: some View {
VStack {
NavigationLink(destination: ThirdView()) {
Text("Go To ThirdView")
}
}
.navigationTitle(Text("SecondView"))
}
}
struct ThirdView: View {
#EnvironmentObject var navigationCoordinator: NavigationCoordinator
var body: some View {
VStack {
Button("Pop to FirstView") {
navigationCoordinator.popToRoot()
}
}
.navigationTitle(Text("ThirdView"))
}
}
I am:
Passing the path into the NavigationStack path parameter
Sending the simple ObservableObject instance into the NavigationStack via the .environmentObject() modifier
Pushing a few simple child views onto the stack
Attempting to use the environment object in ThirdView
NOT crashing when attempting to use the environment object (e.g. "No ObservableObject of type NavigationCoordinator found")
Am I missing anything else that would prevent the deeply stacked view from using the EnvironmentObject to affect the NavigationStack's path? It seems like the NavigationStack just isn't respecting the bound path.
(iOS 16.0, Xcode 14.0)
The reason your code is not working is that you haven't added anything to your path, so your path is empty. You can simply verify this by adding print(path.count) in your popToRoot method it will print 0 in the console.
To work with NavigationPath you need to use navigationDestination(for:destination:) ViewModifier, So for your example, you can try something like this.
ContentView:- Change NavigationStack like this.
NavigationStack(path: $navigationCoordinator.path) {
VStack {
NavigationLink(value: 1) {
Text("Go To SecondView")
}
}
.navigationDestination(for: Int.self) { i in
if i == 1 {
SecondView()
}
else {
ThirdView()
}
}
}
SecondView:- Change NavigationLink like this.
NavigationLink(value: 2) {
Text("Go To ThirdView")
}
This workaround works with Int but is not a better approach, so my suggestion is to use a custom Array as a path. Like this.
enum AppView {
case second, third
}
class NavigationCoordinator: ObservableObject {
#Published var path = [AppView]()
}
NavigationStack(path: $navigationCoordinator.path) {
FirstView()
.navigationDestination(for: AppView.self) { path in
switch path {
case .second: SecondView()
case .third: ThirdView()
}
}
}
Now change NavigationLink in FirstView and SecondView like this.
NavigationLink(value: AppView.second) {
Text("Go To SecondView")
}
NavigationLink(value: AppView.third) {
Text("Go To ThirdView")
}
The benefit of the above is now you can use the button as well to push a new screen and just need to append in your path.
path.append(.second)
//OR
path.append(.third)
This will push a respected view.
For more details, you can read the Apple document of NavigationLink and NavigationPath.

SwiftUI NavigationLink destination in TabView will be dismissed after background scene phase

In the "Dest" view, after my app going to background mode and coming back to foreground mode, the navigation destination is dismissed automatically. How can I prevent this? Here is my code:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
TabView {
NavigationLink {Text("Dest")} label: {Text("go to dest")}.tabItem {Text("c1")}
Text("a2").tabItem {Text("c2")}
}
}
}
}
Put the NavigationView inside the TabView:
var body: some View {
TabView {
NavigationView { // here
NavigationLink() {
Text("Dest")
} label: {
Text("go to dest")
}
}
.tabItem {Text("c1")}
Text("a2")
.tabItem {Text("c2")}
}
}

SwiftUI: NavigationLink pops out immediately on WatchOS 8.1RC in Tabview

I have discovered a regression in watchOS 8.1RC with NavigationLink triggered from a TabView.
It's immediately dismissed.
It was working in watchOS 8.0 or in Simulator (watchOS 8.0).
Do you know a workaround ?
Thanks
Sample code:
import SwiftUI
#main
struct TestNavigationApp: App {
var body: some Scene {
WindowGroup {
NavigationView {
ContentView()
}
}
}
}
struct ContentView: View {
var body: some View {
List {
NavigationLink(destination: ContentView1()) {
Text("To TabView")
}
}
}
}
struct ContentView1: View {
var body: some View {
TabView {
NavigationView {
NavigationLink(destination: ContentView2()) {
Text("To ContentView2")
}
}
VStack {
Text("Screen2")
}
}
}
}
struct ContentView2: View {
var body: some View {
Text("ContentView2")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I'm experiencing the same issue with watchOS 8.1 (and 8.3 beta) while it was working with previous watchOS versions.
We were able to get it working again by moving the NavigationView inside the TabView. This workaround isn't ideal at all but it does seem to work.
#State private var tabSelection = 1
var body: some Scene {
WindowGroup {
TabView(selection: $tabSelection) {
NavigationView {
// List goes here
}
.tag(1)
VStack(alignment: .center, spacing: 12, content: {
// content 2nd tab: we didn't have a list in the 2nd tab
})
.tag(2)
}
}
}
However, there are 2 things impacted with this fix:
I didn't get the navigationBarTitle working, so there won't be a title on top of the screen.
If you click on an item in the list, it will navigate to your page (as expected) but the TabView dots at the bottom of the screen will remain.

SwiftUI list does not scroll using 2-finger swipe on trackpad

I have been testing this simple SwiftUI code: It has a list in ContentView. When a cell of the list is tapped, it pushes a SecondList into the navigation stack. When this SwiftUI code was run on a real iPad with a trackpad, I used 2-finger swipe to scroll the list up and down. The list in ContentView scrolls smoothly. However, for SecondList, it may freeze randomly and thereafter not responding.
It also failed in a UIKit project, where I replaced a UINavigationView with this ContentView using UIHostingViewController.
It was testing it with iPadOS 14.5 on both Simulator and iPad Pro hardware with Magic Keyboard.
How do I fix this? Thank you.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
List {
ForEach(0..<30, id: \.self) { index in
NavigationLink(
destination: SecondList(),
label: {
Text(String(index))
})
}
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct SecondList: View {
var body: some View {
List {
ForEach(0..<30, id: \.self) { index in
Text(String(index))
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Use a scrollView
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
List {
ForEach(0..<30, id: \.self) { index in
NavigationLink(
destination: SecondList(),
label: {
Text(String(index))
})
}
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct SecondList: View {
var body: some View {
scrollView {
List {
ForEach(0..<30, id: \.self) { index in
Text(String(index))
}
}
}
}
}

Invisible transition removal animation

I've encountered an issue that I was not able to tinker to my full satisfaction.
I have a MasterView that changes environmentObject SelectionObject to show ZStack content* from Link enum. The issue is that the removal transition is almost invisible when there is a background in MasterView (Color.gray, when I set opacity, the animation is visible a little bit but unless it gets to low number, the overall opacity of FirstView or SecondView is detrimented. It works as expected without any background in MasterView
Here is my code:
class SelectionObject: ObservableObject {
#Published var selection: Link? = nil
}
struct MasterView: View {
#EnvironmentObject var selection: SelectionObject
var body: some View {
ZStack {
Color.gray
VStack {
ForEach(Link.allCases) { menu in
Button(action: {
selection.selection = menu
}, label: {
Label(menu.title, systemImage: menu.image).padding()
}
)
.tag(menu)
}
}
ForEach(Link.allCases) { menu in
if menu == selection.selection {
menu.contentView
.transition(AnyTransition.slide)
.animation(.spring())
}
}
}
}
}
struct Menu_Previews: PreviewProvider {
static var previews: some View {
MasterView().environmentObject(SelectionObject())
}
}
struct FirstView: View {
#EnvironmentObject var selection: SelectionObject
var body: some View {
ZStack {
Color.orange
VStack {
Text("First View content")
Button(action: {
selection.selection = nil
}, label: {
Text("Get back with a nice animation").padding().foregroundColor(.white)
}
)
}
}
}
}
struct SecondView: View {
#EnvironmentObject var selection: SelectionObject
var body: some View {
ZStack {
Color.orange
VStack {
Text("Second View content")
Button(action: {
selection.selection = nil
}, label: {
Text("Get back with a nice animation")
}
)
}
}
}
}
enum Link: Int, CaseIterable, Identifiable {
var id: Int {
return self.rawValue
}
case first
case second
var title: LocalizedStringKey {
switch self {
case .first: return "First"
case .second: return "Second"
}
}
var image: String {
switch self {
case .first: return "icloud"
case .second: return "display"
}
}
var contentView: AnyView {
switch self {
case .first: return AnyView ( FirstView() )
case .second: return AnyView ( SecondView() )
}
}
}
I've tried to use a zIndex way (mentioned here: Transition animation not working in SwiftUI ) but was unable to make it work as it worked only once and did not show the content on second click.
Can you help me find a way around the issue?
I use this because I can't use NavigationView as my MasterView is used in overlay in a different NavigationView and there is a frame, offset, and cornerRadius issue that prevents to click on anything unless I delete either the offset or cornerRadius.
Just add a zIndex to your menu.contentView and it will be always on top. Hence, you can see the back animation.
menu.contentView
.id(UUID()) // << add id here
.transition(AnyTransition.slide)
.animation(.spring())
.zIndex(50) //<< set higher zIndex here
Works multiple times aswell, after toggling view multiple times
Edit: Transition will fade in from leading edge and will dismiss to trailing edge. As the view stay there it will fade in back (once it is called again) from the trailing edge. With id(UUID() you create a new one which fades back from leading to trailing