I have a SwiftUI watchOS app that uses NavigationLink
s to route between a few views. I'm using NavigationLink
's tag
and selection
parameters to be able to control which view is visible. In my real app, I want to be able to navigate back to the view at index 0 when the user launches the app from a complication. However, whenever the $selection
value is anything other than nil or 0 and $selection
is set to 0 from my view model, the view at index 0 displays briefly, then $selection
is reset to nil
by SwiftUI (twice) without any user interaction causing it.
I've created a sample app that demonstrates this behavior, with a timer approximating the user launching from a complication. I made everything other than the selection value a constant to try to eliminate diffing as a possible source.
Here is a gif demonstrating what happens:
import SwiftUI
struct Item: Identifiable {
let id: Int
let text: String
}
class ContentViewModel: ObservableObject {
@Published public var selectedIndex: Int? {
didSet {
print("ContentViewModel selectedIndex = \(String(describing: selectedIndex))")
}
}
public let items: [Item] = [
Item(id: 0, text: "Zero"),
Item(id: 1, text: "One"),
Item(id: 2, text: "Two"),
Item(id: 3, text: "Three"),
Item(id: 4, text: "Four")
]
private var timer: Timer?
init() {
self.timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { [unowned self] _ in
print("ContentViewModel timer fired")
self.selectedIndex = 0
}
}
}
struct ContentView: View {
@ObservedObject private var viewModel = ContentViewModel()
var body: some View {
List(self.viewModel.items) { (item: Item) in
NavigationLink(destination: Text(item.text), tag: item.id, selection: self.$viewModel.selectedIndex) {
Text(item.text)
}
}
}
}
Here's what is logged in the console:
ContentViewModel selectedIndex = Optional(2)
ContentViewModel timer fired
ContentViewModel selectedIndex = Optional(0)
ContentViewModel selectedIndex = nil
ContentViewModel selectedIndex = nil
ContentViewModel selectedIndex = Optional(3)
ContentViewModel timer fired
ContentViewModel selectedIndex = Optional(0)
ContentViewModel selectedIndex = nil
ContentViewModel selectedIndex = nil
I'd love any help or pointers y'all can provide!
Note: I am running the latest versions of everything, Xcode 11.4, Swift 5.2, watchOS 6.2.
It looks like watchOS-only issue, I don't observe such behaviour on iOS. Worth submitting feedback to Apple.
Here is possible temporary workaround. Tested with Xcode 11.4.
self.timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { [unowned self] _ in
print("ContentViewModel timer fired")
self.selectedIndex = nil // << reset
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // postpone !!
self.selectedIndex = 0 // << next
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With