UIViewControllerRepresentable height is too big

1k views Asked by At

I am trying to add UIKit context menu to a SwiftUI view because SwiftUI context menu is pretty limited. I managed to do it, but UIViewControllerRepresentable takes more space than it needs. How can I resize it to fit the content inside it? Here is my code.

Screenhot

SwiftUI code

struct TrackerCard: View {
    var tracker: Tracker
    var body: some View {
        HStack {
            HStack {
               ...
            }
            .padding()
            .background(Color("Wrapper"))
            .cornerRadius(15)
        }
        .padding(.horizontal)
    }
}

struct TrackerCardView: View {
    var tracker: Tracker
    var body: some View {
        ContextMenuView(card: TrackerCard(tracker: tracker))
    }
}

UIKit code

class ContextMenuController : UIViewController, UIContextMenuInteractionDelegate {
    
    var card: TrackerCard?
    var hostingViewController: UIHostingController<TrackerCard>?
    
    func setViewController(_ controller : UIHostingController<TrackerCard>) {
        hostingViewController = controller
        self.addChild(controller)
        controller.view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .init(white: 1, alpha: 1)
        view.addSubview(controller.view)
        controller.didMove(toParent: self)
        controller.view.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
    }
    
    func setInteraction() {
        let interaction = UIContextMenuInteraction(delegate: self)
        hostingViewController?.view.addInteraction(interaction)
    }
    
    func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
        return ...
}

struct ContextMenuView: UIViewControllerRepresentable {
    typealias UIViewControllerType = ContextMenuController
    var card: TrackerCard
    var controller : UIViewControllerType? = nil
    
    func makeUIViewController(context: Context) -> ContextMenuController {
        let contextMenuController = controller ?? ContextMenuController()
        contextMenuController.card = card
        return contextMenuController
    }

    func updateUIViewController(_ contextMenuController: ContextMenuController, context: Context) {
        contextMenuController.setViewController(UIHostingController(rootView: card))
        contextMenuController.setInteraction()
    }
}
1

There are 1 answers

0
robinst On

Try this. First, add .fixedSize to your SwiftUI view:

ContextMenuView(card: TrackerCard(tracker: tracker))
    .fixedSize(horizontal: false, vertical: true)

With that, the view should be unconstrained horizontally but only take up as much space as it needs vertically, based on its preferred size.

But you might notice that with the current layout, the height gets squished (all the way to 0).

To fix that, we need to set the preferredContentSize like this in the UIViewController:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    let targetSize = CGSize(width: view.bounds.width, height: UIView.layoutFittingCompressedSize.height)
    self.preferredContentSize = view.systemLayoutSizeFitting(targetSize)
}

See the blog post Self-sizing Child Views for more details about that.

But the height might still get set to 0. In my case, I had to add a constraint for the height for the preferred height to be calculated as desired (not 0).

controller.view.heightAnchor.constraint(lessThanOrEqualTo: view.heightAnchor).isActive = true

Hope that helps. If not, it would be good if you edited your answer to include a minimal working example with a preview that shows the problem.