我在创建新的UIViewControllerRepresentable时遇到了问题,因为makeUIViewController方法在UIViewControllerRepresentable中只运行一次,从而阻止对新视图的更新.在保持ControllerViewMyView的隐私的同时,修改此代码的最佳方式是什么?

private struct ControllerView<Content: View>: View {
    struct MyView<ContentM: View>: UIViewControllerRepresentable {
        let rootView: ContentM
        
        init(rootView: ContentM) {
            self.rootView = rootView
            print("init MyView")
        }
        
        func makeUIViewController(context: Context) -> UIViewController {
            print("makeUI")
            /// create my custom VC
            return UIHostingController(rootView: rootView)
        }
        func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
            print("updateUI")
        }
    }
    
    
    let rootView: () -> Content
    @Binding var isGreen: Bool
    
    init(isGreen: Binding<Bool>,@ViewBuilder rootView: @escaping () -> Content) {
        self.rootView = rootView
        self._isGreen = isGreen
    }
    
    var body: some View {
        ZStack {
            MyView(rootView: rootView())
            
            Button {
                isGreen.toggle()
            } label: {
                Text("Change")
            }
        }
    }
}

private struct GreenView: View  {
    var body: some View {Color.green.ignoresSafeArea()}
}
private struct OrangeView: View  {
    var body: some View {Color.orange.ignoresSafeArea()}
}




struct SwiftUIView21: View {
    @State var isGreen: Bool = true
    
    var body: some View {
        ControllerView(isGreen: $isGreen) {
            if isGreen {
                GreenView()
            } else {
                OrangeView()
            }
        }
    }
}

#Preview {
    SwiftUIView21()
}

推荐答案

虽然不是makeUIViewController,但updateUIViewController是.您可以更改UIHostingController中显示的视图.

func makeUIViewController(context: Context) -> UIHostingController<ContentM> {
    print("makeUI")
    return UIHostingController(rootView: rootView)
}
func updateUIViewController(_ uiViewController: UIHostingController<ContentM>, context: Context) {
    uiViewController.rootView = rootView
    print("updateUI")
}

请注意,当您在两个视图之间切换时,这将中断您想要执行的任何动画.SwiftUI无法设置uiViewController.rootView = rootView的动画.


或者,您可以在每次发生切换时更改MyView‘S ID:

MyView(rootView: rootView()).id(isGreen)

这将在您每次更改视图时重新创建一个新的UIHostingController,并可以设置更改的动画.然而,这只是为两个MyView之间的变化设置动画,恰好看起来与在OrangeViewGreenView之间变化的动画相似(两者都是交叉淡入淡出).如果您有其他动画,如设置某物比例的动画:

Text("Foo").scaleEffect(isGreen ? 1 : 2)

则动画仍将是交叉淡入淡出,而不是zoom 动画.


我发现的第三种方法是用一个@Observable的包装纸把Bool包起来,然后把它放在Environment里.

@Observable
class BoolWrapper: ExpressibleByBooleanLiteral {
    var bool: Bool
    required init(booleanLiteral value: Bool) {
        bool = value
    }
}

private struct ControllerView<Content: View>: View {
    struct MyView<ContentM: View>: UIViewControllerRepresentable {
        let rootView: () -> ContentM
        
        init(@ViewBuilder rootView: @escaping () -> ContentM) {
            self.rootView = rootView
            print("init MyView")
        }
        
        func makeUIViewController(context: Context) -> UIHostingController<ContentM> {
            print("makeUI")
            return UIHostingController(rootView: rootView())
        }
        func updateUIViewController(_ uiViewController: UIHostingController<ContentM>, context: Context) {
            print("updateUI")
        }
    }
    
    
    let rootView: () -> Content
    let isGreen: BoolWrapper
    
    init(isGreen: BoolWrapper,@ViewBuilder rootView: @escaping () -> Content) {
        self.rootView = rootView
        self.isGreen = isGreen
    }
    
    var body: some View {
        ZStack {
            MyView(rootView: rootView)
            
            Button {
                withAnimation {
                    isGreen.bool.toggle()
                }
            } label: {
                Text("Change")
            }
        }
    }
}

struct ContentView: View {
    @State var isGreen: BoolWrapper = true
    
    var body: some View {
        ControllerView(isGreen: isGreen) {
            GreenOrangeWrapper().environment(isGreen)
        }
    }
}

struct GreenOrangeWrapper: View {
    @Environment(BoolWrapper.self) var boolWrapper
    
    var body: some View {
        if boolWrapper.bool {
            GreenView()
        } else {
            OrangeView()
        }
    }
}

现在动画都被保存下来了.

Swift相关问答推荐

操作员如何点逻辑非(.!)在Swift工作?

如何消除SwiftUI中SF符号的填充

SWIFT Vapor控制台应用程序-操作无法完成.权限被拒绝

当计数大于索引时,索引超出范围崩溃

使 tabview 垂直将视图移动到右侧 swift ui

如何在设备(或常量)地址空间中创建缓冲区?

为什么我的Swift app不能用xcodebuild构建,但是用XCode可以构建?

如何在 Swift 中做类型安全的索引?

Swiftui 无法从核心数据中获取数据

try 制作一个 Swift 扩展来替换字符串中的多次出现

类型 '()' 不能符合 View (除非它肯定是 View,这次没有恶作剧)

将内容与工作表顶部而不是中间对齐

如何在 ZStack 中单独下移卡片?

SwiftUI View Builder 参数无法更改状态

Vapor Swift 如何配置客户端连接超时

macOS 守护进程应该由Command Line ToolXcode 模板制作吗?

临时添加到视图层次 struct 后,Weak view引用不会被释放

更新我的 pod 导致 GoogleDataTransport Umbrella 标头出现错误

自定义 MKAnnotation 标注视图?

用 UIBezierPath 画一条线