Reputation: 357
I'm trying to use my custom UIView in SwiftUI using UIViewRepresentable and I want my UIView to have the same size as I set in .frame() so that I can use it like this:
MyViewRepresentable()
.frame(width: 400, height: 250, alignment: .center)
For example, I can set a frame as a property:
struct MyViewRepresentable: UIViewRepresentable {
var frame: CGRect
func makeUIView(context: Context) -> UIView {
let myView = MyView(frame: frame)
return view
}
func updateUIView(_ uiView: UIView, context: Context) {}
}
Usage:
struct ContentView: View {
var body: some View {
MyViewRepresentable(frame: CGRect(x: 0, y: 0, width: 400, height: 250))
.frame(width: 400, height: 250, alignment: .center)
}
}
It is not a solution and I wonder how to make it right.
Upvotes: 13
Views: 12328
Reputation: 8571
On iOS 16 support was added for sizeThatFits
, which is exactly how most layout views work. You can either perform whatever calculation is required inside of it, or just respond to the proposed view frame (the one passed from outside):
func sizeThatFits(_ proposal: ProposedViewSize, uiView: LPLinkView, context: Context) -> CGSize? {
return proposal.replacingUnspecifiedDimensions()
}
And as someone else mentioned, if the represented view is not responding to it (e.g clipping out of bounds) then it must be an issue with the wrapped UIView.
Upvotes: 0
Reputation: 915
One easy hack if you do not now what is wrong with MyView is to add it inside a UIView and then return it ie
public func makeUIView(context _: UIViewRepresentableContext< MyViewRepresentable>) -> UIView {
let view = UIView(frame: .zero)
let myView = MyView(frame: frame)
myView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myView)
NSLayoutConstraint.activate([
myView.heightAnchor.constraint(equalTo: view.heightAnchor),
myView.widthAnchor.constraint(equalTo: view.widthAnchor),
])
return view
}
public func updateUIView(_: UIView, context _: UIViewRepresentableContext<MyViewRepresentable>) {}
Upvotes: 1
Reputation: 258345
If MyView
has correct internal layout (which depends only on own bounds), then there is not needs in external additional limitation, ie
struct MyViewRepresentable: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
return MyView(frame: .zero)
}
func updateUIView(_ uiView: UIView, context: Context) {}
}
will be exactly sized below having 400x250 frame
struct ContentView: View {
var body: some View {
MyViewRepresentable()
.frame(width: 400, height: 250, alignment: .center)
}
}
if it is not then internal MyView layout has defects.
Upvotes: 15
Reputation: 3602
If Asperi's answer did not work out for you, then it's probably as they said: the internal MyView layout has defects.
To resolve this matter, you have a couple options:
viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
// 1. View Hierarchy
self.addChild(self.mySubview)
self.view.addSubview(self.mySubview.view)
self.mySubview.didMove(toParent: self)
// 2. View AutoLayout Constraints
self.mySubview.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: self.mySubview.view.leadingAnchor),
view.trailingAnchor.constraint(equalTo: self.mySubview.view.trailingAnchor),
view.topAnchor.constraint(equalTo: self.mySubview.view.topAnchor),
view.bottomAnchor.constraint(equalTo: self.mySubview.view.bottomAnchor)
])
}
viewDidLayoutSubviews
Simply within your UIViewController
, set subviews frames in viewDidLayoutSubviews
.
override func viewDidLoad() {
super.viewDidLoad()
// 1. Add your subviews once in `viewDidLoad`
self.addChild(self.mySubview)
self.view.addSubview(self.mySubview.view)
self.mySubview.didMove(toParent: self)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// 2. Layout your subviews `viewDidLayoutSubviews`
// Update subview frame size
// Must be done in `viewDidLayoutSubviews`
// Otherwise in `viewDidLoad` the bounds equal `UIScreen.main.bounds`, even if you used explicit frame within SwiftUI and used GeometryReader to pass the `CGSize` yourself to this UIViewController!
let mySubviewFrame = self.view.bounds
self.mySubview.view.frame = mySubviewFrame
}
frame
and bounds
properties on the UIView.autoResizingMask
uses archaic springs and struts. See autoResizingMask
, this answer, and this article.VStack
and HStack
! This is only for SwiftUI and all the above applies to UIKit only.Probably should have made a small dev article out of this.
Upvotes: 1