9

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.

IrelDev
  • 317
  • 5
  • 12

2 Answers2

12

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.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • 2
    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. – IrelDev Jun 10 '20 at 11:22
  • 1
    Thank you @IreiDev, that did the trick for me too. Maybe you add this as an answer to your own question, that xould be helpful – heiko Apr 08 '21 at 13:13
  • 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! – om-ha Mar 19 '22 at 10:30
  • 1
    Actually I posted an answer to cover more details on this. – om-ha Mar 20 '22 at 07:16
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.

om-ha
  • 3,102
  • 24
  • 37