Posts

Post not yet marked as solved
0 Replies
973 Views
I'm trying to manage state restoration in our application. I failed to get NavigationView/NavigationLink working in pre-iOS 16 SwiftUI. I'm now trying to get it working with NavigationStack/NavigationLink/navigationDestination with iOS 16. I've built a sample application that demonstrates the issue: import SwiftUI @main struct PushMultipleViews7App: App {     var body: some Scene {         WindowGroup {             ContentView()         }     } } enum Route: Hashable, Codable {     case view1     case view2     case view3     case view4 } struct ContentView: View {     @State     private var navigationPath: [Route]     @Environment(\.scenePhase)     private var scenePhase     init() {         guard let data = UserDefaults                   .standard                   .data(                       forKey: "NavigationPath"                   ),               let navigationPath = try? JSONDecoder()                   .decode(                       [Route].self,                       from: data                   )         else {             self._navigationPath = State(                 initialValue: []             )             print("Failed to read NavigationPath")             return         }         print("Read:",navigationPath)         self._navigationPath = State(             initialValue: navigationPath         )     }     var body: some View {         VStack {             NavigationStack(                 path: $navigationPath             )             {                 View1()                     .navigationDestination(                         for: Route.self                     )                 { route in                     switch route {                     case .view1:                         View1()                     case .view2:                         View2()                     case .view3:                         View3()                     case .view4:                         View4()                     }                 }             }         }             .onChange(                 of: scenePhase             )             { scenePhase in                 guard case .inactive = scenePhase                 else {                     return                 }                 guard let data = try? JSONEncoder()                           .encode(                               navigationPath                           )                 else {                     print("Failed to write NavigationPath")                     return                 }                 print("Writing:",navigationPath)                 UserDefaults                     .standard                     .setValue(                         data,                         forKey: "NavigationPath"                     )             }     } } struct View1: View {     var body: some View {         VStack {             Text("Hello View1!")             NavigationLink(                 value: Route.view2,                 label: {                     Text("Navigate to View2")                 }             )         }             .navigationTitle("View 1")     } } struct View2: View {     var body: some View {         VStack {             Text("Hello View2!")             NavigationLink(                 value: Route.view3,                 label: {                     Text("Navigate to View3")                 }             )         }             .navigationTitle("View 2")     } } struct View3: View {     var body: some View {         VStack {             Text("Hello View3!")             NavigationLink(                 value: Route.view4,                 label: {                     Text("Navigate to View4")                 }             )         }             .navigationTitle("View 3")     } } struct View4: View {     var body: some View {         Text("Hello, View4!")     } }``` To replicate the issue: from Xcode 14, run the app above in your favorite iOS 16 phone simulator. Tap on the NavigationLinks to navigate to View 4. Navigate to the Home Screen (put the app into the background). Wait a few seconds for UserDefaults to catch up. "Swipe up" to kill the app. Relaunch the app. View4 should be displaying...which is the expected behavior. Tap the "View 3" button (the "back" button) to display View3. As before, navigate to the Home Screen, wait a few seconds then "swipe up" to kill the app. Relaunch the app. Instead of displaying "View3", the app should be displaying View1. Evidence can be seen in the console when writing the navigation path to UserDefaults - it wrote "[]". That's just one variation among lots of strange behavior I've seen trying to restore a NavigationStack's navigation path. I've filed: FB10398702 I've tried several variations of this application: using @AppStorage and @SceneStorage instead of UserDefaults. I've also tried the sample code accompanying NavigationPath in Apple's documentation (though also based on UserDefaults). Am I doing something incorrectly or is this a bug? Thanks, --David
Posted
by djehrlich.
Last updated
.
Post not yet marked as solved
0 Replies
537 Views
We need to trigger a side effect on a publisher when the first subscriber subscribes and the last subscriber unsubscribes. We're looking for the Combine equivalent of "doOnSubscribe" and "doOnUnsubscribe" familiar to ReactiveX users. We've used ".handleEvents", but we've been informed that it should only be used for debugging as it may not be thread safe. Here's a solution for "doOnSubscribe" that utilizes "prepend". But to implement "doOnUnsubscribe", you cannot replace "prepend" with "append" as the upstream will never be cancelled without the side-effect - when the last subscriber unsubscribes, we need to trigger the side effect that ultimately tells the upstream publisher to complete. extension Publishers {     public struct OnSubscribePublisher<P: Publisher>: Publisher where P.Failure == Never {         public typealias Output = P.Output         public typealias Failure = P.Failure         private let _transform: AnyPublisher<Output,Failure>         fileprivate init(             publisher: P,             work: @escaping () -> Void         )         {             self._transform = Deferred{ publisher }                 .map { Optional($0) }                 .prepend(                     Deferred {                         Just(                             nil                         )                             .map { (_: Output?) in                                 work()                                 return nil                             }                     }                 )                 .compactMap { $0 }                 .eraseToAnyPublisher()         }         public func receive<S>(             subscriber: S         ) where             S: Subscriber,             S.Failure == Failure,             S.Input == Output         {             _transform                 .receive(                     subscriber: subscriber                 )         }     } }
Posted
by djehrlich.
Last updated
.