How do I make my SwiftUI UIViewRepresentable respect intrinsicContentSize in previews?
Asked Answered
P

3

26

When I create a view in SwiftUI and render it in an Xcode preview, using PreviewLayout.sizeThatFits, the preview adjusts its size according to its content. When I import a UIKIt view using UIViewRepresentable, it appears with a full device-size frame.

Is there a way to make SwiftUI respect the intrinsicContentSize of UIView subclasses?

struct Label: UIViewRepresentable {

    func makeUIView(context: UIViewRepresentableContext<Label>) -> UILabel {
        return UILabel()
    }

    func updateUIView(_ uiView: UILabel, context: UIViewRepresentableContext<Label>) {
        uiView.text = "Some text"
    }
}

#if DEBUG
struct Label_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            Label().previewLayout(.sizeThatFits)
        }
    }
}
#endif
Pineapple answered 19/6, 2019 at 10:2 Comment(0)
M
35

Add the following to your updateUIView function:

uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
uiView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
Mutualize answered 24/6, 2019 at 23:51 Comment(2)
Thanks this is super helpful. Turns out that it also works when setting the contentHuggingPriorities in makeUIView after creating the view. It feels a bit cleaner to me because this needs to be configured only once, not every time the view is updated...Witwatersrand
I added this settings in func makeUIView(context: Context) -> UIViewType, it works well. Thanks for the suggestion!Bick
L
2

You can also limit the UIViewRepresentable size from the SwiftUI side.

For this you can use fixedSize:

struct Label_Previews: PreviewProvider {
    static var previews: some View {
        Label()
            .fixedSize()
            .previewLayout(.sizeThatFits)
    }
}

You can also fix the view size in one dimension only:

.fixedSize(horizontal: false, vertical: true)
Lifeline answered 18/1, 2021 at 16:7 Comment(0)
S
0

Using Swift 5, SwiftUI 15, the following is a solution that works for a view hierarchy using layout constraints.

The key is to pass the representable a binding that the SwiftUI view uses to set the height of the view. The representable sets the value of the height by asking the system for the minimum size that fits.

This works for both UIViewRepresentable and UIViewControllerRepresentable.

struct WrappingView: UIViewControllerRepresentable {
    @Binding var height: CGFloat
    
    func makeUIViewController(context: Context) -> WrappedViewController {   
        return WrappedViewController()
    }
    
    func updateUIViewController(_ uiViewController: WrappedViewController, context: Context) {

        DispatchQueue.main.async {
            height = uiViewController.view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
        }
    }
    
    typealias UIViewControllerType = WrappedViewController
}

Then in the SwiftUI view, set the frame height to match the value from the representable.

struct MixedView: View {
    @State private var wrappedViewHeight: CGFloat = 0
   
    var body: some View {
        VStack {
            WrappingView(height: $wrappedViewHeight)
            .frame(height: wrappedViewHeight)
        }
    }
}

Sample wrapped view controller for trivial case

class WrappedViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let label = UILabel()
        label.text = "Testing"
        label.backgroundColor = .lightGray
        label.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(label)
        NSLayoutConstraint.activate([
            label.topAnchor.constraint(equalTo: view.topAnchor),
            label.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
}

And in Xcode 15, a preview is as simple as

#Preview {
    MixedView()
}
Swarts answered 26/10, 2023 at 6:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.