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