如何强制创建新的 UIViewControllerRepresentable |更新 makeUIViewController 上的视图

How to force to create a new UIViewControllerRepresentable | updating views on makeUIViewController

提问人:Amirca 提问时间:11/10/2023 最后编辑:Amirca 更新时间:11/11/2023 访问量:43

问:

我在创建新视图时遇到了问题,因为该方法仅在 中运行一次,从而阻止了对新视图的更新。在保持内部隐私的同时修改此代码的最佳方法是什么?UIViewControllerRepresentablemakeUIViewControllerUIViewControllerRepresentableMyViewControllerView

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

Swift SwiftUI 视图 iOS17 ViewBuilder

评论

0赞 Sweeper 11/10/2023
您还应该更改问题描述。代码中不再存在编译器错误。
0赞 Amirca 11/11/2023
描述已更新。

答:

1赞 Sweeper 11/10/2023 #1

虽然不叫,但就是。您可以更改其中显示的视图。makeUIViewControllerupdateUIViewControllerUIHostingController

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


或者,您可以在每次切换发生时更改 的 id:MyView

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

这将在每次更改视图时重新创建一个新视图,并可以对更改进行动画处理。然而,这只对两个 s 之间的变化进行了动画处理,这恰好看起来类似于在 和 之间切换的动画(都是交叉淡入淡出)。如果您有其他动画,例如对某物的比例进行动画处理:UIHostingControllerMyViewOrangeViewGreenView

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

然后动画仍将是交叉淡入淡出,而不是缩放动画。


我发现的第三种方法是使用包装器包装 ,然后将其放入 .@ObservableBoolEnvironment

@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()
        }
    }
}

现在动画都保留了。

评论

0赞 Amirca 11/10/2023
感谢您注意到这个习俗。但是,以这种方式使代码泛型会导致在按下更改按钮时视图不会更新,因为调用仅发生一次。因此,我能想到的唯一方法就是传递给 .你有什么方法吗?ViewBuildermakeUIViewControllerMyViewControllerView
0赞 Amirca 11/10/2023
你是对的。该代码适用于此泛型。让我解释一下。我试图在我的包裹中保留内部。所以只是可以访问它并把而不是.在本例中,视图未更新(因为调用了一次)。那么有没有办法保持 ControllerView 的私密性呢?MyViewControllerViewConterollerViewMyView(rootView: rootView())rootView()makeUIViewController
0赞 Amirca 11/10/2023
很抱歉造成混乱。我更新了代码。
0赞 Sweeper 11/10/2023
@Amirca 请参阅编辑。我想到了两种方法。两者都不是很好。要让 SwiftUI 认识到 UIKit 代码的“间隙”发生了变化,这真的很困难。如果你允许知道,那就容易多了。MyViewisGreen