Set custom UIView frame in UIViewRepresentable SwiftUI
Asked Answered
L

3

12

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.

Luminescence answered 10/6, 2020 at 10:35 Comment(0)
H
14

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.

Homoeo answered 10/6, 2020 at 10:41 Comment(4)
Thank you for pointing me in the right direction! I looked at my code and found that I added MyView subviews in viewDidLoad, not in layoutSubviews.Luminescence
Thank you @IreiDev, that did the trick for me too. Maybe you add this as an answer to your own question, that xould be helpfulProceeds
Thanks @IrelDev, I actually had a similar problem with the frame size of UIViewControllerRepresentable, and assigning frame in viewDidLayoutSubviews with updateUIViewController helped solve my issue!Apostate
Actually I posted an answer to cover more details on this.Apostate
A
1

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 frame and bounds properties on the UIView.
    • Auto-Resizing Masks. Under the hood, an autoResizingMask uses archaic springs and struts. See autoResizingMask, this answer, and this article.
    • AutoLayout Constraints.
    • AutoLayout StackView.
    • SwiftUI's 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.

Apostate answered 20/3, 2022 at 7:3 Comment(0)
D
1

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>) {}
Dynamo answered 3/1, 2024 at 17:1 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.