Swiftui: Navigationdestinationlink Deprecated

SwiftUI: NavigationDestinationLink deprecated

After spending some time with NavigationLink(destination:isActive), I am liking it a lot more than the old NavigationDestinationLink. The old view was a little confusing, while the new approach seems much more elegant. And once I figure out how to push without animations, it would make state restoration at application launch very easy.

There is one problem though, a big and ugly bug. :-(

Sample Image

Pushing a view programatically works fine, and popping it programatically does too. The problem starts when we use the BACK button in the pushed view which behaves oddly every other time. The first time the view is popped, the view pops and pushes again immediately. The second time around it works fine. Then the third time it starts all over again.

I have created a bug report (number here). I recommend you do the same and reference my number too, to help Apple group them together and get more attention to the problem.

I designed a workaround, that basically consists of replacing the default Back button, with our own:

class Model: ObservableObject {
@Published var pushed = false
}

struct ContentView: View {
@EnvironmentObject var model: Model

var body: some View {
NavigationView {
VStack {
Button("Push") {
// view pushed programmatically
self.model.pushed = true
}

NavigationLink(destination: DetailView(), isActive: $model.pushed) { EmptyView() }
}
}
}
}

struct DetailView: View {
@EnvironmentObject var model: Model

var body: some View {
Button("Bring me Back (programatically)") {
// view popped programmatically
self.model.pushed = false
}
// workaround
.navigationBarBackButtonHidden(true) // not needed, but just in case
.navigationBarItems(leading: MyBackButton(label: "Back!") {
self.model.pushed = false
})
}
}

struct MyBackButton: View {
let label: String
let closure: () -> ()

var body: some View {
Button(action: { self.closure() }) {
HStack {
Image(systemName: "chevron.left")
Text(label)
}
}
}
}

Forcing the destination of a NavigationLink in a navigation bar item to the secondary/detail view

Note that you will see the same problem on a large iPhone, when in landscape orientation (https://stackoverflow.com/a/57345540/7786555)

The isDetailLink(true) should have work (in my opinion). It is probably a bug affecting the navigation bar. I have seen many weird behaviors with it. It seems whatever you put in the navigation bar, loses some kind of context.

The workaround I found so far, is using the action of your navigation bar to trigger something in the "main" view. In this case, The NavigationLink is now located in the main view (although invisible). Note that I put it inside .background() so it does not affect the layout of other views.

struct ContentView: View {
@State private var push = false

var body: some View {
NavigationView {

VStack {
Text("Master")
.background(NavigationLink(destination: DetailView(), isActive: $push) { EmptyView() })
}.navigationBarItems(trailing:
Button(action: { self.push = true }, label: {
Image(systemName: "plus")
})
).navigationBarTitle("Master List")

Text("")
}
}
}

struct DetailView: View {
var body: some View {
Text("Detail")
}
}

Finally, please note that there is a bug in the NavigationLink that will also affect you. Fortunately, there is a workaround for it too: SwiftUI: NavigationDestinationLink deprecated

.identified(by:) Deprecated?

.identified(by:) is deprecated. As you correctly stated, this is not noted in the release notes for Xcode beta, but in the release notes for iOS beta, which is why you couldn't find it. It's a little confusing because the changes relating to SwiftUI are scattered across the release notes for iOS 13 beta, Xcode 11 beta, and macOS Catalina beta.

https://developer.apple.com/documentation/ios_ipados_release_notes/ios_ipados_13_beta_5_release_notes

The identified(by:) method on the Collection protocol is deprecated in
favor of dedicated init(:id:selection:rowContent:) and
init(
:id:content:) initializers. (52976883, 52029393)

But the identified(by:) deprecation happened in beta 4, so the following also applies:

SwiftUI APIs deprecated in previous betas are now removed. (52587863)

This question is sort of a duplicate of SwiftUI ForEach 'identified(by:)' is deprecated. Use ForEach(_:id:) or List(_:id:), but the confusion around where the deprecation is mentioned in the release notes merits keeping it as a separate question.

How to reuse view model instance for destination view in a NavigationLink

Apologies, my code is adapted from yours as I've not updated to the latest beta yet, but this works for me. I've used the concept of "Lifting State Up" from React, and moved the model data into the Master view itself.

From a playground:

import SwiftUI
import PlaygroundSupport

final class ItemViewModel : BindableObject {
let willChange = PassthroughSubject<Void, Never>()

var name: String {
willSet { willChange.send() }
}
var counter: Int = 0 {
willSet { willChange.send() }
}

init(name: String) {
self.name = name
}
}

struct ContentView : View {
let items = [
ItemViewModel(name: "Item A"),
ItemViewModel(name: "Item B"),
ItemViewModel(name: "Item C")
]

@State var contentViewUpdater = 0

var body: some View {
NavigationView {
VStack {
Button("Update ContentView: \(contentViewUpdater)") {
self.contentViewUpdater += 1
}
List(items) { model in
NavigationLink(destination: DetailView(model: model)) {
Text(model.name)
}
}
}
}
}
}

struct DetailView : View {
@ObjectBinding var model: ItemViewModel

var body: some View {
let name = model.name
let counter = model.counter
return VStack {
Text("Counter for \(name): \(counter)")
Button("Increase counter") {
self.model.counter += 1
}
}
}
}

PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
PlaygroundPage.current.needsIndefiniteExecution = true

Screen recording from Playground



Related Topics



Leave a reply



Submit