Home > Software design >  UIViewRepresentable not updating Published / Binding properties in swiftui
UIViewRepresentable not updating Published / Binding properties in swiftui

Time:09-21

I am trying to wrap UIScrollView in SwiftUI for some additional functionality. I want to access contentOffset property of UIScrollView and assign it to some property (Published or Binding) in SwiftUI, but SwiftUI ( my ContentView) is not detecting any property changes. Can you help me get it right ?

This is my UIScrollView

import SwiftUI
import UIKit

struct MyScrollView<Content: View>: UIViewRepresentable {
    
    @ObservedObject var viewModel: ViewModel
    @ViewBuilder var content: () -> Content
   
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: NSObject, UIScrollViewDelegate {
        var parent: MyScrollView

        init(_ parent: MyScrollView) {
            self.parent = parent
        }
        
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
           parent.viewModel.scrollviewContentOffset = scrollView.contentOffset.y
        }
        
    }
    
    func makeUIView(context: Context) -> UIScrollView {
        let scrollView = UIScrollView()
        
        scrollView.delegate = context.coordinator
        scrollView.isScrollEnabled = true
        
        let child = UIHostingController(rootView: content())
        scrollView.addSubview(child.view)
        
        let newSize = child.view.sizeThatFits(CGSize(width: UIScreen.screenWidth, height: UIScreen.screenHeight))
        child.view.frame = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
        
        scrollView.contentSize = newSize
        
        return scrollView
    }
    
    func updateUIView(_ uiView: UIScrollView, context: Context) {
        //
    }
    
    typealias UIViewType = UIScrollView
}

and this is my ContentView

import SwiftUI

class ViewModel: ObservableObject {
    @Published var scrollviewContentOffset = CGFloat.zero
}

struct ContentView: View {
    
    @StateObject private var viewModel = ViewModel()
    
    var body: some View {
        MyScrollView(viewModel: viewModel) {
            ForEach(0..<100) { i in
                Text("\(i)")
                    .offset(y: viewModel.scrollviewContentOffset / CGFloat(i   1)
            }
        }
    }
}

CodePudding user response:

You're only capturing the initial content when creating your MyScrollView, and not updating it on view model change.

You need to set updated content inside updateUIView. For example you can store hostingController inside Coordinator:

struct MyScrollView<Content: View>: UIViewRepresentable {

    @StateObject var viewModel: ViewModel
    @ViewBuilder var content: () -> Content

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, UIScrollViewDelegate {
        let parent: MyScrollView
        var hostingController: UIHostingController<Content>!

        init(_ parent: MyScrollView) {
            self.parent = parent
        }

        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            parent.viewModel.scrollviewContentOffset = scrollView.contentOffset.y
        }

    }

    func makeUIView(context: Context) -> UIScrollView {
        let scrollView = UIScrollView()

        scrollView.delegate = context.coordinator
        scrollView.isScrollEnabled = true

        let child = UIHostingController(rootView: content())
        context.coordinator.hostingController = child
        scrollView.addSubview(child.view)

        let newSize = child.view.sizeThatFits(CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
        child.view.frame = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)

        scrollView.contentSize = newSize

        return scrollView
    }

    func updateUIView(_ uiView: UIScrollView, context: Context) {
        context.coordinator.hostingController.rootView = content()
    }
}
  • Related