0

I built the Custom modal window for SwiftUI using UIKit reference below code. This modal almost works well but variable isPresented does not changed when the modal closed by tap outside of it or pull down it.

Therefore, When the modal closed in these ways, in updateUIViewController try to present modal because isPresented is always still true then app crash. In my research UIAdaptivePresentationControllerDelegate might help to solve this issue but I don't know how to use it.

struct Modal<Content: View>: UIViewControllerRepresentable {
    typealias Context = UIViewControllerRepresentableContext<Modal>

    @Binding var isPresented: Bool
    
    let modal: UIHostingController<Content>
    let viewer: ViewController = ViewController()
    
    init(isPresented: Binding<Bool>,
         transitionStyle: UIModalTransitionStyle = .coverVertical,
         presentationStyle: UIModalPresentationStyle = .pageSheet,
         isModalInPresentation: Bool = false,
         content: () -> Content
    ) {
        self._isPresented = isPresented
        self.modal = UIHostingController(rootView: content())
        self.modal.modalTransitionStyle = transitionStyle
        self.modal.modalPresentationStyle presentationStyle
        self.modal.isModalInPresentation = isModalInPresentation
        self.modal.preferredContentSize = CGSize(width: 900, height: 520)
        self.modal.presentationController?.delegate = viewer
    }

    func makeUIViewController(context: Context) -> UIViewController {
        return viewer
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        switch isPresented {
        case true:
            uiViewController.present(modal, animated: true, completion: nil)
        case false:
            // Never called when modal closed with pulldown/touch outside
            uiViewController.dismiss(animated: true, completion: nil)
        }
    }
}


class ViewController: UIViewController {
}

extension ViewController: UIAdaptivePresentationControllerDelegate {
    public func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
        // Called when the modal dismiss by pulldown/tap outside
    }
}

How to change isPresented in presentationControllerDidDismiss()?

Sincerely.

Source Code(100% Working)

import UIKit
import SwiftUI

struct Modal<Content: View>: UIViewControllerRepresentable {

    let content: () -> Content
    @Binding var isPresented: Bool
    let transitionStyle: ModalTransitionStyle
    let presentationStyle: ModalPresentationStyle
    let isModalInPresentation: Bool
    let contentSize: CGSize?

    init(isPresented: Binding<Bool>,
         transitionStyle: ModalTransitionStyle = .flipHorizontal,
         presentationStyle: ModalPresentationStyle = .pageSheet,
         isModalInPresentation: Bool = false,
         contentSize: CGSize?,
         content: @escaping () -> Content
    ) {
        self.content = content
        self.transitionStyle = transitionStyle
        self.presentationStyle = presentationStyle
        self.isModalInPresentation = isModalInPresentation
        self.contentSize = contentSize
        self._isPresented = isPresented
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIViewController(context: Context) -> ViewController<Content> {
        return ViewController(coordinator: context.coordinator,
                              transitionStyle: transitionStyle,
                              presentationStyle: presentationStyle,
                              isModalInPresentation: isModalInPresentation,
                              contentSize: contentSize,
                              content: content)
    }

    func updateUIViewController(_ uiViewController: ViewController<Content>, context: Context) {
        switch isPresented {
        case true:
            uiViewController.present()
        case false:
            uiViewController.dismiss()
        }
    }
    
    class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate {
        var parent: Modal
        init(_ parent: Modal) {
            self.parent = parent
        }
        
        func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
            if parent.isPresented {
                parent.isPresented = false
            }
        }
    }
}

// This custom view controller
class ViewController<Content: View>: UIViewController {
    let content: Content
    let coordinator: Modal<Content>.Coordinator
    let transitionStyle: ModalTransitionStyle
    let presentationStyle: ModalPresentationStyle
    let contentSize: CGSize?
    
    init(coordinator: Modal<Content>.Coordinator,
         transitionStyle: ModalTransitionStyle,
         presentationStyle: ModalPresentationStyle,
         isModalInPresentation: Bool,
         contentSize: CGSize?,
         content: @escaping () -> Content
         ) {
        self.content = content()
        self.coordinator = coordinator
        self.transitionStyle = transitionStyle
        self.presentationStyle = presentationStyle
        self.contentSize = contentSize
        super.init(nibName: nil, bundle: .main)
        self.isModalInPresentation = isModalInPresentation
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func present() {
        // Build UIHostingController
        let hosting = UIHostingController(rootView: content)
        hosting.modalTransitionStyle = UIModalTransitionStyle(rawValue: transitionStyle.rawValue)!
        hosting.modalPresentationStyle = UIModalPresentationStyle(rawValue: presentationStyle.rawValue)!
        hosting.presentationController?.delegate = coordinator as UIAdaptivePresentationControllerDelegate
        hosting.isModalInPresentation = isModalInPresentation

        if presentedViewController == nil {
            present(hosting, animated: true, completion: nil)
        }
    }
    
    func dismiss() {
        dismiss(animated: true, completion: nil)
    }
}

0 Answers0