问题
Is it possible to make a UIViewRepresentable
view which takes a ViewBuilder
argument work with dynamic content such as ForEach
loops?
I have the following UIViewRepresentable
view which I’m using to drop down to UIKit
and get some custom UIScrollView
behaviour:
struct CustomScrollView<Content:View>: UIViewRepresentable {
private let content: UIView
private let scrollView = CustomUIScrollView()
init(@ViewBuilder content: () -> Content) {
self.content = UIHostingController(rootView: content()).view
self.content.backgroundColor = .clear
}
func makeUIView(context: Context) -> UIView {
scrollView.addSubview(content)
// ...
return scrollView
}
func updateUIView(_ uiView: UIView, context: Context) {}
}
This works fine with static content as follows:
var body: some View {
CustomScrollView {
VStack {
ForEach(1..<50) { number in
Text(String(number))
}
}
}
}
But it fails with dynamic content, showing a blank view:
var body: some View {
CustomScrollView {
VStack {
ForEach(self.numbers) { number in
Text(String(number))
}
}
}
}
I understand that this is because when makeUIView()
is called my dynamic data is empty, and it is later filled or updated. I evaluate my UIViewRepresentable
’s content
at init, and don’t update it in updateUIView()
.
How do you go about updating dynamic child content in updateUIView()
? I tried capturing the @ViewBuilder
parameter as an @escaping
closure and evaluating it every time updateUIView()
is called, which seems like the right solution (albeit inefficient?), but no luck so far.
回答1:
The following might be helpful. It is not clear how absent CustomUIScrollView
behaves (probably the issue is there), but using standard UIScrollView
works with dynamic ForEach. Tested with Xcode 11.4 / iOS 13.4
struct CustomScrollView<Content:View>: UIViewRepresentable {
private let content: UIView
private let scrollView = UIScrollView()
init(@ViewBuilder content: () -> Content) {
self.content = UIHostingController(rootView: content()).view
self.content.backgroundColor = .clear
}
func makeUIView(context: Context) -> UIView {
content.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(content)
let constraints = [
content.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
content.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
content.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
content.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
content.widthAnchor.constraint(equalTo: scrollView.widthAnchor)
]
scrollView.addConstraints(constraints)
return scrollView
}
func updateUIView(_ uiView: UIView, context: Context) {}
}
struct TestCustomScrollView: View {
private var items = Array(repeating: "Test", count: 50)
var body: some View {
CustomScrollView {
VStack {
ForEach(Array(items.enumerated()), id: \.0) { i, item in
Text("\(item) - \(i)")
}
}
}
}
}
来源:https://stackoverflow.com/questions/62386390/how-to-make-a-uiviewrepresentable-viewbuilder-work-with-dynamic-content