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. :-(
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
Related Topics
How to Programmatically Add a Proxy to an Nsurlsession
Why Is Our Monotouch App Breaking in the Garbage Collector? It Is Not Out of Memory
iOS 7 - How to Disable the Swipe Back and Forward Functionality in Safari
Debug on Real Apple Watch: Application Verification Failed
Swift Performseguewithidentifier Not Working
Uisearchbar: Clear Background Color or Set Background Image
Https iOS with Self Signed Certificate
1St April Dates of 80S Failed to Parse in iOS 10.0
How to Use Pull to Refresh in Swift
Uibutton in Cell in Collection View Not Receiving Touch Up Inside Event
Which Is the Best Way to Estimate Measure of Photographed Things
iOS Autolayout: Two Buttons of Equal Width, Side by Side
Uiview Vertical Flip Animation
Uiview Backgroundcolor Disappears When Uitableviewcell Is Selected
Facebook Login - iOS 9 - Without Safari
Swift - How to Dismiss All of View Controllers to Go Back to Root