UIViewRepresentable ignores constraints of wrapped UIView

1.5k Views Asked by At

I have subclass of UIButton, which defines it's height inside by using NSLayoutConstraints, which I need to reuse in SwiftUI view by wrapping it into UIViewRepresentable.

So here is the code:

struct TestView: View {
    var body: some View {
        TestButtonWrapper()
            .background(Color.red)
    }
}

final class TestButton: UIButton {
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }

    func setup() {
        translatesAutoresizingMaskIntoConstraints = false
        setTitle("Hello", for: .normal)
        // these are ignored:
        heightAnchor.constraint(equalToConstant: 200).isActive = true
        widthAnchor.constraint(equalToConstant: 300).isActive = true
    }
}

struct TestButtonWrapper: UIViewRepresentable {
    func makeUIView(context: Context) -> TestButton {
        let view = TestButton()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.setContentHuggingPriority(.defaultHigh, for: .horizontal)
        view.setContentHuggingPriority(.defaultHigh, for: .vertical)
        return view
    }

    func updateUIView(_ uiView: TestButton, context: Context) {
    }
}

Result is:

Screenshot

Important: I can't remove constraints from TestButton and set frame inside TestView. This UIKit button is being reused in regular UIKit screens

How it can be solved? Why UIViewRepresentable ignores constraints of it's children?

1

There are 1 best solutions below

4
DonMag On

SwiftUI and UIKit's layout constraints are not the same...

One approach is to override intrinsicContentSize in your TestButton instead of trying to set constraints.

Give this a try:

struct TestView: View {
    var body: some View {
        TestButtonWrapper()
            .background(Color.red)
    }
}

final class TestButton: UIButton {
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }
    
    func setup() {
        translatesAutoresizingMaskIntoConstraints = false
        setTitle("Hello", for: .normal)
        // these are ignored:
        //heightAnchor.constraint(equalToConstant: 200).isActive = true
        //widthAnchor.constraint(equalToConstant: 300).isActive = true
    }
    
    // add this override
    override var intrinsicContentSize: CGSize {
        return .init(width: 300.0, height: 200.0)
        
    }
}

struct TestButtonWrapper: UIViewRepresentable {
    func makeUIView(context: Context) -> TestButton {
        let view = TestButton()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.setContentHuggingPriority(.defaultHigh, for: .horizontal)
        view.setContentHuggingPriority(.defaultHigh, for: .vertical)
        return view
    }
    
    func updateUIView(_ uiView: TestButton, context: Context) {
    }
}

enter image description here


Edit

For clarification...

When using UIKit auto-layout:

  • the constraints define the size
  • intrinsicContentSize has no effect

When using SwiftUI:

  • intrinsicContentSize defines the size
  • the constraints have no effect

We can use the same TestButton class in both environments:

// if this class is used in a UIKit auto-layout environment
//  its width and height constraints will define the size of the button
//  intrinsicContentSize will have no effect

// if this class is used in a SwiftUI environment
//  its width and height constraints will have no effect
//  intrinsicContentSize will define the size of the button (absent any other sizing actions)

final class TestButton: UIButton {
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }
    
    func setup() {
        translatesAutoresizingMaskIntoConstraints = false
        setTitle("Hello", for: .normal)

        heightAnchor.constraint(equalToConstant: 200).isActive = true
        widthAnchor.constraint(equalToConstant: 300).isActive = true
    }
    
    override var intrinsicContentSize: CGSize {
        return .init(width: 300.0, height: 200.0)
        
    }
}

So, for a SwiftUI implementation:

struct TestView: View {
    var body: some View {
        VStack(alignment: .center, spacing: 20.0) {
            Text("SwiftUI")
            TestButtonWrapper()
                .background(Color.red)
        }
    }
}

struct TestButtonWrapper: UIViewRepresentable {
    func makeUIView(context: Context) -> TestButton {
        let view = TestButton()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.setContentHuggingPriority(.defaultHigh, for: .horizontal)
        view.setContentHuggingPriority(.defaultHigh, for: .vertical)
        return view
    }
    
    func updateUIView(_ uiView: TestButton, context: Context) {
    }
}

and here is a Storyboard / UIKit implementation:

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        
        let label = UILabel()
        label.text = "Storyboard / UIKit"
        
        let btn1 = TestButton()
        btn1.backgroundColor = .systemRed
        
        let stackView = UIStackView()
        stackView.axis = .vertical
        stackView.alignment = .center
        stackView.spacing = 20.0

        stackView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(stackView)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([

            stackView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            stackView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            
        ])

        stackView.addArrangedSubview(label)
        stackView.addArrangedSubview(btn1)
    }
    
}

Both produce the same output:

enter image description here enter image description here