Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI NavigationLink push in onAppear immediately pops the view when using @ObservableObject

I want to programmatically be able to navigate to a link within a List of NavigationLinks when the view appears (building deep linking from push notification). I have a string -> Bool dictionary which is bound to a custom Binding<Bool> inside my view. When the view appears, I set the bool property, navigation happens, however, it immediately pops back. I followed the answer in SwiftUI NavigationLink immediately navigates back and made sure that each item in the List has a unique identifier, but the issue still persists.

Two questions:

  1. Is my binding logic here correct?
  2. How come the view pops back immediately?
import SwiftUI

class ContentViewModel: ObservableObject {
    @Published var isLinkActive:[String: Bool] = [:]
}

struct ContentViewTwo: View {
    @ObservedObject var contentViewModel = ContentViewModel()
    @State var data = ["1", "2", "3"]
    @State var shouldPushPage3: Bool = true

    var page3: some View {
        Text("Page 3")
            .onAppear() {
                print("Page 3 Appeared!")
        }
    }

    func binding(chatId: String) -> Binding<Bool> {
        return .init(get: { () -> Bool in
            return self.contentViewModel.isLinkActive[chatId, default: false]
        }) { (value) in
            self.contentViewModel.isLinkActive[chatId] = value
        }
    }

    var body: some View {
        return
            List(data, id: \.self) { data in
                NavigationLink(destination: self.page3, isActive: self.binding(chatId: data)) {
                    Text("Page 3 Link with Data: \(data)")
                }.onAppear() {
                    print("link appeared")
                }
            }.onAppear() {
                print ("ContentViewTwo Appeared")
                if (self.shouldPushPage3) {
                    self.shouldPushPage3 = false
                    self.contentViewModel.isLinkActive["3"] = true
                    print("Activating link to page 3")
                }
        }
    }
}

struct ContentView: View {
    var body: some View {
        return NavigationView() {
            VStack {
                Text("Page 1")
                NavigationLink(destination: ContentViewTwo()) {
                    Text("Page 2 Link")
                }
            }
        }
    }
}
like image 243
Zorayr Avatar asked Jun 09 '20 05:06

Zorayr


People also ask

How does NavigationLink work SwiftUI?

SwiftUI's NavigationLink has a second initializer that has an isActive parameter, allowing us to read or write whether the navigation link is currently active. In practical terms, this means we can programmatically trigger the activation of a navigation link by setting whatever state it's watching to true.


2 Answers

The error is due to the lifecycle of the ViewModel, and is a limitation with SwiftUI NavigationLink itself at the moment, will have to wait to see if Apple updates the pending issues in the next release.

Update for SwiftUI 2.0:

Change:

@ObservedObject var contentViewModel = ContentViewModel()

to:

@StateObject var contentViewModel = ContentViewModel()

@StateObject means that changes in the state of the view model do not trigger a redraw of the whole body.

You also need to store the shouldPushPage3 variable outside the View as the view will get recreated every time you pop back to the root View.

enum DeepLinking {
    static var shouldPushPage3 = true
}

And reference it as follows:

if (DeepLinking.shouldPushPage3) {
    DeepLinking.shouldPushPage3 = false
    self.contentViewModel.isLinkActive["3"] = true
    print("Activating link to page 3")
}

The bug got fixed with the latest SwiftUI release. But to use this code at the moment, you will need to use the beta version of Xcode and iOS 14 - it will be live in a month or so with the next GM Xcode release.

like image 123
Pranav Kasetti Avatar answered Oct 22 '22 14:10

Pranav Kasetti


I was coming up against this problem, with a standard (not using 'isActive') NavigationLink - for me the problem turned out to be the use of the view modifiers: .onAppear{code} and .onDisappear{code} in the destination view. I think it was causing a re-draw loop or something which caused the view to pop back to my list view (after approx 1 second). I solved it by moving the modifiers onto a part of the destination view that's not affected by the code in those modifiers.

like image 3
Phi7 Avatar answered Oct 22 '22 15:10

Phi7