swiftswiftuiuiviewuiviewrepresentable

How to make a custom UIViewRepresentable fill its parent VStack and have custom subview centered in it?


So I have the following in my project.

  1. ContentView ~ contains 3 vstacks.
  2. CustomView ~ a custom view with a containerView with fixed size of 300x300 that should always be centered in ContentView
  3. CustomViewRepresentable ~ a UIViewRepresentable in order to insert CustomView into ContentView.

The code for each is as follows:

ContentView

import SwiftUI
import UIKit

struct ContentView: View {
    
    var body: some View {
        VStack(alignment: .center, spacing: 0) {
            VStack {
                Color.blue
                    .frame(height: 50)
            }
            VStack {
                HStack {
                    Color.green
                        .frame(height: 100)
                    Text("Text")
                    Color.green
                        .frame(height: 100)
                }
            }
            VStack { // content view
                GeometryReader { geometry in
                    VStack {
                        CustomViewRepresentable()
                            .frame(width: geometry.size.width, height: geometry.size.height)
                            .background(.gray)
                            .border(.red, width: 1)
                    }
                    .frame(width: geometry.size.width, height: geometry.size.height)
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

CustomView

import UIKit

class CustomView: UIView {
    private let containerView = UIView()
    override func layoutSubviews() {
        containerView.center = self.center
    }

    func addContainerView() {
        containerView.frame = CGRect(origin: .zero, size: CGSize(width: 300, height: 300))
        addSubview(containerView)
        containerView.center = self.center
    }
}

CustomViewRepresentable

import UIKit
import SwiftUI

struct CustomViewRepresentable: UIViewRepresentable {
    typealias UIViewType = CustomView
    
    func makeUIView(context: Context) -> CustomView {
        let customView = CustomView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
        customView.addContainerView()
        return customView
    }

    func updateUIView(_ uiView: CustomView, context: Context) {
        
    }
}

The issue that I'm facing is that the containerView inside CustomView does not display unless I initialise CustomView with a frame inside the representable.

func makeUIView(context: Context) -> CustomView {
        let customView = CustomView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))

I don't understand why this is needed because I'd like the CustomView to take the full width and height of the VStack that it is embedded into. Therefore I figured that just doing

CustomViewRepresentable()
   .frame(width: geometry.size.width, height: geometry.size.height)

inside the VStack should just work, but apparently it does not. So what is the best way for the CustomViewRepresentable to fill its parent while keeping the containerView always centered within it?


Solution

  • No need to provide the CustomView frame in func makeUIView(context: Context) -> CustomView function. After replacing let customView = CustomView(frame: CGRect(x: 0, y: 0, width: 300, height: 300)) code with let customView = CustomView() working at my end. Please check with the code below.

    ContenView

    import SwiftUI
    import UIKit
    
    struct ContentView: View {
        
        var body: some View {
            VStack(alignment: .center, spacing: 0) {
                VStack {
                    Color.blue
                        .frame(height: 50)
                }
                VStack {
                    HStack {
                        Color.green
                            .frame(height: 100)
                        Text("Text")
                        Color.green
                            .frame(height: 100)
                    }
                }
                VStack { // content view
                    GeometryReader { geometry in
                        VStack {
                            CustomViewRepresentable()
                                .background(.gray)
                                .border(.red, width: 1)
                        }
                        .frame(width: geometry.size.width, height: geometry.size.height)
                    }
                }
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

    CustomView

    import UIKit
    
    class CustomView: UIView {
        private let containerView = UIView()
        override func layoutSubviews() {
            containerView.center = self.center
        }
    
        func addContainerView() {
            containerView.frame = CGRect(origin: .zero, size: CGSize(width: 300, height: 300))
            addSubview(containerView)
            containerView.center = self.center
        }
    }
    

    CustomViewRepresentable

    import UIKit
    import SwiftUI
    
    struct CustomViewRepresentable: UIViewRepresentable {
        typealias UIViewType = CustomView
        
        func makeUIView(context: Context) -> CustomView {
            let customView = CustomView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
            customView.addContainerView()
            return customView
        }
    
        func updateUIView(_ uiView: CustomView, context: Context) {
            
        }
    }