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)
}
}