How to use NavigationStack and WithPerceptionTracking on iOS 17? #2694
Replies: 4 comments 7 replies
-
Hi @mycroftcanner, can you provide a minimal project that demonstrates the behavior you are seeing? There isn't enough in this code for us to understand what is going on. And are you saying that it works on iOS 16 but doesn't work on iOS 17? |
Beta Was this translation helpful? Give feedback.
-
The problem persists with or without perception tracking on iOS 17. It seems like that the effect are not probably canceled when the view is dismissed. With perception tracking... if I dismiss the view right away ... it navigates back to it. Without perception tracking.. if I dismiss the view it navigates back to it and it dismisses it again. Removing the .animation() doesn't help. This was working fine before the observation beta. @stephencelis aren't the effect supposed to be canceled automatically when using a navigation stack or navigation stack store? |
Beta Was this translation helpful? Give feedback.
-
Maybe I can try to explain what happens... I have a Collection.State (home) which contains an identified array of Profiles.State for each area. Each Profiles.State has an identified array of Profile.State. When I tap on a profile:
the profile state is appended to the path: case let .home(.profiles(.element(_, .items(.element(_, .delegate(.select(profile))))))):
state.path.append(.profile(profile))
return .none when I dismiss the profile... .task is called for each of the Profiles.State to check if Profiles.State needs to be updated:
Often I will get this error: <decode: bad range for [%@] got [offs:310 len:1060 within:0]> I've just noticed that this shows up too:
When the decode error happen, the app will navigate right back to the profile. Sometimes it doesn't happen. The Profiles' task action doesn't do anything with the navigation stack and path. but if I disable it then the bug doesn't happen. Here is the Profiles' .task: case .view(.task):
guard state.shouldFetchProfiles else { return .none }
if state.taskCancelID == nil {
state.taskCancelID = self.uuid()
}
if state.skipFirstTask {
state.skipFirstTask.toggle()
return .none
}
let title = state.title
logger.info("\(title, privacy: .private): Fetching profiles")
return .run { [id=state.id, request=state.request] send in
await send(
.response(
TaskResult {
try await self.profiles(request)
}
)
)
}
.cancellable(id: state.taskCancelID, cancelInFlight: true)
.animation() and the response is handled this way: case let .response(.success(response)):
let target = response.profiles
.filter { $0.location.name != "" }
.sorted { $0.updatedTime.seconds > $1.updatedTime.seconds }
.map { Profile.State(message: $0) }
let stagedChangeset = StagedChangeset(source: state.items.elements, target: target)
for changeset in stagedChangeset {
if !changeset.elementDeleted.isEmpty {
let deleteOffsets = IndexSet(changeset.elementDeleted.map { $0.element })
state.items.remove(atOffsets: deleteOffsets)
}
if !changeset.elementInserted.isEmpty {
let insertedPaths = changeset.elementInserted.map(\.element)
zip(insertedPaths.map { changeset.data[$0] }, insertedPaths)
.forEach { state.items.insert($0, at: $1) }
}
if !changeset.elementUpdated.isEmpty {
changeset.elementUpdated.map(\.element)
.map { changeset.data[$0] }
.forEach {
guard state.items[id: $0.id] != nil else {
let id = $0.id
let title = state.title
logger.warning("missing \(id, privacy: .private) for \(title) ")
return
}
state.items[id: $0.id]! = $0
}
}
if !changeset.elementMoved.isEmpty {
changeset.elementMoved
.forEach {
state.items.move(
fromOffsets: IndexSet(integer: $0.element),
toOffset: $1.element
)
}
}
}
return .none |
Beta Was this translation helpful? Give feedback.
-
@mbrandonw I finally managed to create a minimal repro project and I will email it to you. I have been helping @mycroftcanner with this. In the repro when the bug is triggered we also get a runtime warning that a store was scoped on a non-main thread. this doesn't happen every time and when I put a breakpoint in the .scope case of the threadCheck... it shows that we are inside com.apple.SwiftUI.AsyncRenderer... I have no idea why this is happening. |
Beta Was this translation helpful? Give feedback.
-
In iOS 17, when WithPerceptionTracking is enabled, we're noticing an issue where the child task isn't properly canceled upon the user hitting the back button. This leads to a navigation back to the previously dismissed view. Intriguingly, this wasn't apparent in debug mode with .printChanges, but it's consistently occurring in the release version. Conversely, if we disable WithPerceptionTracking, this problem doesn't seem to arise.
Beta Was this translation helpful? Give feedback.
All reactions