视图在 iOS 14 上的 SwiftUI 中呈现两次 [英] View presented twice in SwiftUI on iOS 14

查看:103
本文介绍了视图在 iOS 14 上的 SwiftUI 中呈现两次的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要基于模型动态呈现视图,因此需要在 SwiftUI 视图外部决定呈现哪个视图和创建视图.这在 iOS 13 上运行良好,但在 iOS 14 上,第一个视图再次显示而不是第二个视图,尽管第二个视图确实按预期初始化,第一个视图第二次推送到导航堆栈上.

这似乎是一个 iOS 14 错误,但也许我做错了什么,所以在向 Apple 提交错误报告之前先在这里询问.

这是我正在尝试做的简化版本,此代码适用于 iOS 13 但不适用于 iOS 14.

<预><代码>///定义使用 Coordinator & 动态呈现的 Screen 的界面屏幕提供者协议屏幕{var coordinator: Coordinator { get }var pushNext: Bool { get set }var sheetNext: Bool { get set }func screenIsComplete() 抛出}///根据请求创建屏幕struct ScreenProvider {///在接收到一个 `screenId` 时创建一个屏幕 &将其包装在 AnyView 中func screen(for screenId: String, coordinator: Coordinator) ->任意视图{打印(正在获取屏幕(对于 screenId:\(screenId))")切换屏幕 ID {案例视图1":返回 AnyView(View1(coordinator: coordinator))案例视图2":返回 AnyView(View2(coordinator: coordinator))案例View3":返回 AnyView(View3(coordinator: coordinator))案例View4":返回 AnyView(View4(coordinator: coordinator))默认:致命错误()}}}///当前屏幕完成后应该做什么枚举 ScreenPresentationAction {案例推送Next案例表下一页无所事事}///确定一个屏幕完成后应该采取什么动作并将下一个屏幕提供给当前屏幕最后的班级协调员{让 screenProvider: ScreenProvider私有 var screenIndex: Int = 1初始化(屏幕提供者:屏幕提供者){self.screenProvider = screenProvider}///当前屏幕完成后应该做什么func nextActionAfterScreenCompletion() 抛出 ->ScreenPresentationAction {如果屏幕索引<4 {屏幕索引 += 1}如果屏幕索引 == 4 {返回 .sheetNext}返回 .pushNext}///获取下一个要呈现的屏幕func nextScreen() ->一些视图{让 screenId = "View\(screenIndex)";打印(为索引\(screenIndex)获取nextScreen()")返回 screenProvider.screen(for: screenId, coordinator: self)}}

有四个几乎相同的视图:

<预><代码>struct View1:视图,屏幕{//------------------------------------//MARK: 屏幕//------------------------------------var coordinator: 协调器@State var pushNext: Bool = false@State var sheetNext: Bool = false//------------------------------------//标记:属性//------------------------------------//# 身体var主体:一些视图{虚拟堆栈{垫片()文本(视图 1")垫片()按钮(动作:{尝试!self.screenIsComplete()}) {文本(下一个")}垫片()NavigationLink(destination: self.coordinator.nextScreen(), isActive: self.$pushNext) {空视图()}}.sheet(isPresented: self.$sheetNext) {self.coordinator.nextScreen()}}}扩展视图 1 {func screenIsComplete() 抛出 {让动作 = 尝试 coordinator.nextActionAfterScreenCompletion()切换动作{案例.pushNext:self.pushNext = true案例.sheetNext:self.sheetNext = true案例.doNothing:休息}}}

解决方案

您需要推迟构建导航链接目标视图(否则它会在第一次刷新时立即创建,您会看到效果).

使用 Xcode 12/iOS 14 测试

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

DeferView 取自 其他我的解决方案

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
        }
    }
}

解决方案

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

这篇关于视图在 iOS 14 上的 SwiftUI 中呈现两次的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆