Search code examples
iosswiftuiuikitpresentmodalviewcontrolleruihostingcontroller

Presenting SwiftUIView sheet with height fitting content size from UIViewcontroller


I need to present a sheet containing SwiftUI view from UIViewController, but the height of the sheet has to fit the height of the content of the SwiftUI view.

I know that there is a possibility to specify custom detent for the height of the sheet, but I wonder whether it is possible to get the height of the content size of SwiftUI view when presenting it from the UIViewController?

So far I have tried getting the content size with the help of the GeometryReader in SwiftUI view, but did not get any result.

Here is my code where the SwiftUI view is presented for reference:

func presentSwiftUIPageSheet() {
    let swiftUIView = SwiftUIView()
    let hostingController = UIHostingController(rootView: swiftUIView)

    hostingController.modalPresentationStyle = .pageSheet      
    hostingController.isModalInPresentation = false
    hostingController.sheetPresentationController?.detents = [.medium(), .large()]

    present(hostingController, animated: true)
}

Any advice would be much appreciated!


Solution

  • For SwiftUI only code there is a question & answer here: Make sheet the exact size of the content inside Based on that, the iOS 16.0+ solution for presenting from an UIViewcontroller would be:

    func presentSwiftUIPageSheet() {
        var sheetHeight = CGFloat.zero
        let swiftUIView = SwiftUIView()
            .readSize { sheetHeight = $0.height }
    
        let hostingController = UIHostingController(rootView: swiftUIView)
        hostingController.modalPresentationStyle = .pageSheet
        hostingController.isModalInPresentation = false
        hostingController.sheetPresentationController?.detents = [.custom { _ in 0 },
                                                                  .large()]
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            hostingController.sheetPresentationController?.animateChanges {
                hostingController.sheetPresentationController?.detents = [.custom { _ in sheetHeight },
                                                                          .large()]
            }
        }
        present(hostingController, animated: true)
    }
    

    with SwiftUI extension:

    private struct SizePreferenceKey: PreferenceKey {
      static var defaultValue: CGSize = .zero
      static func reduce(value: inout CGSize, nextValue: () -> CGSize) { value = nextValue() }
    }
    
    extension View {
    
        func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
            background(
                GeometryReader { proxy in
                    Color.clear
                        .preference(key: SizePreferenceKey.self, value: proxy.size)
                }
            ).onPreferenceChange(SizePreferenceKey.self, perform: onChange)
        }
    }