Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
787 views
Welcome To Ask or Share your Answers For Others

1 Answer

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

demo

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...