View presented twice in SwiftUI on iOS 14

天大地大妈咪最大 提交于 2021-01-01 06:52:05

问题


I need to dynamically present views based on a model so the decision of which view to present and the creation of the views needs to be external to the SwiftUI views. This is working well on iOS 13 but on iOS 14 the first view presents again instead of the second view, although the second view does get initialised as expected the first view is pushed onto the navigation stack a second time.

It seems like a iOS 14 bug but perhaps I'm doing something wrong so asking here before filing a bug report with Apple.

This is simplified version of what I'm trying to do, this code works in iOS 13 but not iOS 14.


/// Defines the interface for a Screen that is presented dynamically using the Coordinator & ScreenProvider
protocol Screen {
    
    var coordinator: Coordinator { get }
    var pushNext: Bool { get set }
    var sheetNext: Bool { get set }
    
    func screenIsComplete() throws
}


/// Creates Screens on request
struct ScreenProvider {

    /// Creates a Screen when receiving a `screenId` & wraps it in an AnyView
    func screen(for screenId: String, coordinator: Coordinator) -> AnyView {
        
        print("Getting screen(for screenId: \(screenId))")
        
        switch screenId {
        case "View1":
            return AnyView(View1(coordinator: coordinator))
        case "View2":
            return AnyView(View2(coordinator: coordinator))
        case "View3":
            return AnyView(View3(coordinator: coordinator))
        case "View4":
            return AnyView(View4(coordinator: coordinator))
        default:
            fatalError()
        }
    }
}

/// What should the current Screen do after it has completed
enum ScreenPresentationAction {
    case pushNext
    case sheetNext
    case doNothing
}

/// Determines what action should be taken after a screen has completed and serves the next Screen to the current Screen 
final class Coordinator {
    
    let screenProvider: ScreenProvider
    private var screenIndex: Int = 1
    
    init(screenProvider: ScreenProvider) {
        self.screenProvider = screenProvider
    }
    
    /// What should the current Screen do after it has completed
    func nextActionAfterScreenCompletion() throws -> ScreenPresentationAction {
        if screenIndex < 4 {
            screenIndex += 1
        }
        if screenIndex == 4 {
            return .sheetNext
        }
        return .pushNext
    }
    
    /// Get the next screen to be presented
    func nextScreen() -> some View {
    
        let screenId = "View\(screenIndex)"
        print("Getting nextScreen() for index \(screenIndex)")
        return screenProvider.screen(for: screenId, coordinator: self)
    }
}

There are four almost identical Views:


struct View1: View, Screen {
    
    //------------------------------------
    // MARK: Screen
    //------------------------------------
    var coordinator: Coordinator
    @State var pushNext: Bool = false
    @State var sheetNext: Bool = false

    //------------------------------------
    // MARK: Properties
    //------------------------------------
    
    // # Body
    var body: some View {
        
        VStack {
            
            Spacer()
            Text("View 1")
            Spacer()
            Button(action: {
                 try! self.screenIsComplete()
            }) {
                Text("Next")
            }
            Spacer()
            NavigationLink(destination: self.coordinator.nextScreen(), isActive: self.$pushNext) {
                EmptyView()
            }
        }
        .sheet(isPresented: self.$sheetNext) {
            self.coordinator.nextScreen()
        }
    }
}

extension View1 {
    
    func screenIsComplete() throws {
        
        let action = try coordinator.nextActionAfterScreenCompletion()
        switch action {
        case .pushNext:
            self.pushNext = true
        case .sheetNext:
            self.sheetNext = true
        case .doNothing:
            break
        }
    }
}


回答1:


You need to defer building navigation link destination view (otherwise it is created right on first refresh and you see your effect).

Tested with Xcode 12 / iOS 14

NavigationLink(destination: DeferView { self.coordinator.nextScreen() }, // << here !!
  isActive: self.$pushNext) {
    EmptyView()
}

The DeferView is taken from other my solution



来源:https://stackoverflow.com/questions/64062153/view-presented-twice-in-swiftui-on-ios-14

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!