0

I need to embed an UIScrollView in a SwiftUI view hierarchy. I wrapped one in UIViewRepresentable, but for some reason my scroll view has zero width.

How do I make the connection between the outer SwiftUI views and the autolayout constraints that define the size of the UIScrollView, so that it expands to fill the space given to it by SwiftUI?

In the sample below, I create a simple "content view" to be inside the UIScrollView. It is is too wide for my screen (500), so will use horizontal scrolling. It simply draws two colored boxes side by side.

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Wrapped UIScrollView Test").padding()
            Divider()
            WrappedScrollView()
            Spacer()
        }
    }
}

struct WrappedScrollView: UIViewRepresentable {
    typealias UIViewType = MyScrollView
    func makeUIView(context: Context) -> MyScrollView {
        let v = MyScrollView()
        return v
    }
    func updateUIView(_ uiView: MyScrollView, context: Context) {}
}

class MyScrollView: UIScrollView {
    init() {
        super.init(frame: .zero)
        translatesAutoresizingMaskIntoConstraints = false
        let content = MyContentView()
        self.addSubview(content)

        // This is how you define the scrollable area with UIScrollView
        content.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        content.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        content.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
        content.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
        
        // I only want to scroll horizontally
        self.heightAnchor.constraint(equalTo: content.heightAnchor).isActive = true

        // But I have to use this hack to get it to show up. (320 is width of my screen)
        // self.widthAnchor.constraint(equalToConstant: 320).isActive = true        
    }
    required init?(coder: NSCoder) { fatalError() }
}

// Just two blue and green squares, so you can see scrolling.
class MyContentView: UIView {
    
    init() {
        super.init(frame:.zero)
        translatesAutoresizingMaskIntoConstraints = false
    }
    
    required init?(coder: NSCoder) { fatalError() }
    
    override var intrinsicContentSize: CGSize {
        return .init(width: 500, height: 200)
    }
    
    override func draw(_ rect: CGRect) {
        print("draw \(bounds)")
        guard let c = UIGraphicsGetCurrentContext() else { return }
        var r1 = bounds
        r1.size.width = 250
        var r2 = r1
        r2.origin.x = 250
        c.setFillColor(UIColor.blue.cgColor)
        c.fill(r1)
        c.setFillColor(UIColor.green.cgColor)
        c.fill(r2)
    }
}
Rob N
  • 15,024
  • 17
  • 92
  • 165

1 Answers1

0

Add this to your MyScrollView class:

override func didMoveToSuperview() {
    // unwrap optional superview
    guard let sv = superview else {
        print("this should never fail, because we are INSIDE didMoveToSuperview()")
        return
    }
    // constrain self leading/trailing to superview
    self.leadingAnchor.constraint(equalTo: sv.leadingAnchor).isActive = true
    self.trailingAnchor.constraint(equalTo: sv.trailingAnchor).isActive = true
}

That should get your scroll view's width to size to its container's width.

DonMag
  • 69,424
  • 5
  • 50
  • 86