'Set custom UIView frame in UIViewRepresentable SwiftUI
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.
Solution 1:[1]
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.
Solution 2:[2]
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:
Option A. Use AutoLayout Constraints within 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)
])
}
Option B. Set frame manually within 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
}
Supplementary Resources
- Basically you have multiple layout methods in iOS. Here they are ordered from oldest/worst to newest/best. This ordering is opinionated of course:
- Frame-Based Layout. Manually manipulate
frameandboundsproperties on the UIView. - Auto-Resizing Masks. Under the hood, an
autoResizingMaskuses archaic springs and struts. SeeautoResizingMask, this answer, and this article. - AutoLayout Constraints.
- AutoLayout StackView.
- SwiftUI's
VStackandHStack! This is only for SwiftUI and all the above applies to UIKit only.
- Frame-Based Layout. Manually manipulate
Probably should have made a small dev article out of this.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Asperi |
| Solution 2 |
