From ce420bda57509ecefbb5a8726b77f82fcc26a2da Mon Sep 17 00:00:00 2001 From: Dongkyu Kim Date: Tue, 19 Jan 2021 22:31:47 +0900 Subject: [PATCH 01/10] Make SFSafariViewController presented from the super view controller --- .../SafariView/SafariViewPresenter.swift | 17 +++++++---------- .../SafariView/UIView+viewController.swift | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 Sources/BetterSafariView/SafariView/UIView+viewController.swift diff --git a/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift b/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift index 8de0043..c226210 100644 --- a/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift +++ b/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift @@ -75,21 +75,18 @@ extension SafariViewPresenter { // MARK: Presentation Handlers private func presentSafariViewController(with item: Item) { - guard uiViewController.presentedViewController == nil else { - return - } - let representation = parent.representationBuilder(item) let safariViewController = SFSafariViewController(url: representation.url, configuration: representation.configuration) safariViewController.delegate = self representation.applyModification(to: safariViewController) - // There is a problem that page loading and parallel push animation are not working when a modifier is attached to the view in a `List`. - // As a workaround, use a `rootViewController` of the `window` for presenting. - // (Unlike the other view controllers, a view controller hosted by a cell doesn't have a parent, but has the same window.) - var presentingViewController = uiViewController.view.window?.rootViewController - presentingViewController = presentingViewController?.presentedViewController ?? presentingViewController ?? uiViewController - presentingViewController?.present(safariViewController, animated: true) + // Present `SFSafariViewController` from the super view controller of `uiViewController`, instead of `uiViewController`. + // This fixes an issue where the Safari view controller is not presented properly + // when the `uiViewController` is detached from the root view controller (e.g. `uiViewController` contained in `UITableViewCell`) + // while allowing it to be presented even on the modal sheets. + // Thanks to: Bohdan Hernandez Navia (@boherna) + let presentingViewController = uiViewController.view.superview?.viewController ?? uiViewController + presentingViewController.present(safariViewController, animated: true) } private func updateSafariViewController(with item: Item) { diff --git a/Sources/BetterSafariView/SafariView/UIView+viewController.swift b/Sources/BetterSafariView/SafariView/UIView+viewController.swift new file mode 100644 index 0000000..e517933 --- /dev/null +++ b/Sources/BetterSafariView/SafariView/UIView+viewController.swift @@ -0,0 +1,17 @@ +#if os(iOS) + +import UIKit + +extension UIView { + var viewController: UIViewController? { + if let nextResponder = self.next as? UIViewController { + return nextResponder + } else if let nextResponder = self.next as? UIView { + return nextResponder.viewController + } else { + return nil + } + } +} + +#endif From edb7e6fdea0e3f65490837bdcc236ac84c25f4db Mon Sep 17 00:00:00 2001 From: Dongkyu Kim Date: Tue, 19 Jan 2021 22:58:08 +0900 Subject: [PATCH 02/10] Declare a weak reference to safariViewController --- .../SafariView/SafariViewPresenter.swift | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift b/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift index c226210..e69f888 100644 --- a/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift +++ b/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift @@ -44,6 +44,7 @@ extension SafariViewPresenter { // MARK: View Controller Holding let uiViewController = UIViewController() + private weak var safariViewController: SFSafariViewController? // MARK: Item Handling @@ -87,10 +88,12 @@ extension SafariViewPresenter { // Thanks to: Bohdan Hernandez Navia (@boherna) let presentingViewController = uiViewController.view.superview?.viewController ?? uiViewController presentingViewController.present(safariViewController, animated: true) + + self.safariViewController = safariViewController } private func updateSafariViewController(with item: Item) { - guard let safariViewController = uiViewController.presentedViewController as? SFSafariViewController else { + guard let safariViewController = safariViewController else { return } let representation = parent.representationBuilder(item) @@ -98,22 +101,14 @@ extension SafariViewPresenter { } private func dismissSafariViewController(completion: (() -> Void)? = nil) { - let dismissCompletion: () -> Void = { - self.handleDismissalWithoutResettingItemBinding() - completion?() - } - - guard uiViewController.presentedViewController != nil else { - dismissCompletion() + guard let safariViewController = safariViewController else { return } - // Check if the `uiViewController` is a instance of the `SFSafariViewController` - // to prevent other controllers presented by the container view from being dismissed unintentionally. - guard let safariViewController = uiViewController.presentedViewController as? SFSafariViewController else { - return + safariViewController.dismiss(animated: true) { + self.handleDismissalWithoutResettingItemBinding() + completion?() } - safariViewController.dismiss(animated: true, completion: dismissCompletion) } // MARK: Dismissal Handlers From 44d473856964ca7c739f301b30f3686cc6b82201 Mon Sep 17 00:00:00 2001 From: Dongkyu Kim Date: Tue, 19 Jan 2021 23:02:35 +0900 Subject: [PATCH 03/10] Make session property have a weak reference --- .../WebAuthenticationPresenter.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/BetterSafariView/WebAuthenticationSession/WebAuthenticationPresenter.swift b/Sources/BetterSafariView/WebAuthenticationSession/WebAuthenticationPresenter.swift index 1975e33..3c40b6f 100644 --- a/Sources/BetterSafariView/WebAuthenticationSession/WebAuthenticationPresenter.swift +++ b/Sources/BetterSafariView/WebAuthenticationSession/WebAuthenticationPresenter.swift @@ -102,7 +102,7 @@ extension WebAuthenticationPresenter { // MARK: View Controller Holding let viewController = ConcreteViewController() - private var session: ASWebAuthenticationSession? + private weak var session: ASWebAuthenticationSession? // MARK: Item Handling @@ -146,13 +146,13 @@ extension WebAuthenticationPresenter { representation.applyModification(to: session) - self.session = session session.start() + + self.session = session } private func cancelWebAuthenticationSession() { session?.cancel() - session = nil } // MARK: Dismissal Handlers From 6e10ed37f83d8ebe62dd952a7bce81942c077038 Mon Sep 17 00:00:00 2001 From: Dongkyu Kim Date: Tue, 19 Jan 2021 23:46:58 +0900 Subject: [PATCH 04/10] Change SafariViewPresenter to UIViewRepresentable --- .../SafariView/SafariViewPresenter.swift | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift b/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift index e69f888..ec98958 100644 --- a/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift +++ b/Sources/BetterSafariView/SafariView/SafariViewPresenter.swift @@ -3,7 +3,7 @@ import SwiftUI import SafariServices -struct SafariViewPresenter: UIViewControllerRepresentable { +struct SafariViewPresenter: UIViewRepresentable { // MARK: Representation @@ -11,18 +11,17 @@ struct SafariViewPresenter: UIViewControllerRepresentable { var onDismiss: (() -> Void)? = nil var representationBuilder: (Item) -> SafariView - // MARK: UIViewControllerRepresentable + // MARK: UIViewRepresentable func makeCoordinator() -> Coordinator { return Coordinator(parent: self) } - func makeUIViewController(context: Context) -> UIViewController { - return context.coordinator.uiViewController + func makeUIView(context: Context) -> UIView { + return context.coordinator.uiView } - func updateUIViewController(_ uiViewController: UIViewController, context: Context) { - + func updateUIView(_ uiView: UIView, context: Context) { // Keep the coordinator updated with a new presenter struct. context.coordinator.parent = self context.coordinator.item = item @@ -43,7 +42,7 @@ extension SafariViewPresenter { // MARK: View Controller Holding - let uiViewController = UIViewController() + let uiView = UIView() private weak var safariViewController: SFSafariViewController? // MARK: Item Handling @@ -81,12 +80,16 @@ extension SafariViewPresenter { safariViewController.delegate = self representation.applyModification(to: safariViewController) - // Present `SFSafariViewController` from the super view controller of `uiViewController`, instead of `uiViewController`. + // Present a Safari view controller from the `viewController` of `UIViewRepresentable`, instead of `UIViewControllerRepresentable`. // This fixes an issue where the Safari view controller is not presented properly - // when the `uiViewController` is detached from the root view controller (e.g. `uiViewController` contained in `UITableViewCell`) + // when the `UIViewControllerRepresentable` is detached from the root view controller (e.g. `UIViewController` contained in `UITableViewCell`) // while allowing it to be presented even on the modal sheets. // Thanks to: Bohdan Hernandez Navia (@boherna) - let presentingViewController = uiViewController.view.superview?.viewController ?? uiViewController + guard let presentingViewController = uiView.viewController else { + self.resetItemBinding() + return + } + presentingViewController.present(safariViewController, animated: true) self.safariViewController = safariViewController @@ -106,15 +109,20 @@ extension SafariViewPresenter { } safariViewController.dismiss(animated: true) { - self.handleDismissalWithoutResettingItemBinding() + self.handleDismissal() completion?() } } // MARK: Dismissal Handlers + // Used when the `viewController` of `uiView` does not exist during the preparation of presentation. + private func resetItemBinding() { + parent.item = nil + } + // Used when the Safari view controller is finished by an item change during view update. - private func handleDismissalWithoutResettingItemBinding() { + private func handleDismissal() { parent.onDismiss?() } From 202dff9e206c3a5572c66e04e9240eacccc29f9f Mon Sep 17 00:00:00 2001 From: Dongkyu Kim Date: Wed, 20 Jan 2021 00:10:52 +0900 Subject: [PATCH 05/10] Change WebAuthenticationPresenter to UIViewRepresentable --- .../UIView+viewController.swift | 0 .../WebAuthenticationPresenter.swift | 59 +++++++++---------- 2 files changed, 28 insertions(+), 31 deletions(-) rename Sources/BetterSafariView/{SafariView => }/UIView+viewController.swift (100%) diff --git a/Sources/BetterSafariView/SafariView/UIView+viewController.swift b/Sources/BetterSafariView/UIView+viewController.swift similarity index 100% rename from Sources/BetterSafariView/SafariView/UIView+viewController.swift rename to Sources/BetterSafariView/UIView+viewController.swift diff --git a/Sources/BetterSafariView/WebAuthenticationSession/WebAuthenticationPresenter.swift b/Sources/BetterSafariView/WebAuthenticationSession/WebAuthenticationPresenter.swift index 3c40b6f..ec664c6 100644 --- a/Sources/BetterSafariView/WebAuthenticationSession/WebAuthenticationPresenter.swift +++ b/Sources/BetterSafariView/WebAuthenticationSession/WebAuthenticationPresenter.swift @@ -7,29 +7,26 @@ import SafariServices #endif #if os(iOS) -typealias ConcreteViewController = UIViewController -typealias ViewController = UIViewController -typealias ViewControllerRepresentable = UIViewControllerRepresentable +typealias ViewType = UIView +typealias ViewRepresentableType = UIViewRepresentable #elseif os(macOS) -typealias ConcreteViewController = NSTabViewController -typealias ViewController = NSViewController -typealias ViewControllerRepresentable = NSViewControllerRepresentable +typealias ViewType = NSView +typealias ViewRepresentableType = NSViewRepresentable #elseif os(watchOS) // Use `WKInterfaceInlineMovie` as a concrete interface objct type, // since there is no public initializer for `WKInterfaceObject`. -typealias ConcreteViewController = WKInterfaceInlineMovie -typealias ViewController = WKInterfaceObject -typealias ViewControllerRepresentable = WKInterfaceObjectRepresentable +typealias ViewType = WKInterfaceInlineMovie +typealias ViewRepresentableType = WKInterfaceObjectRepresentable #endif -struct WebAuthenticationPresenter: ViewControllerRepresentable { +struct WebAuthenticationPresenter: ViewRepresentableType { // MARK: Representation @Binding var item: Item? var representationBuilder: (Item) -> WebAuthenticationSession - // MARK: ViewControllerRepresentable + // MARK: ViewRepresentable func makeCoordinator() -> Coordinator { return Coordinator(parent: self) @@ -37,16 +34,16 @@ struct WebAuthenticationPresenter: ViewControllerRepresentab #if os(iOS) - func makeUIViewController(context: Context) -> ViewController { - return makeViewController(context: context) + func makeUIView(context: Context) -> ViewType { + return makeView(context: context) } - func updateUIViewController(_ uiViewController: ViewController, context: Context) { + func updateUIView(_ uiView: ViewType, context: Context) { - updateViewController(uiViewController, context: context) + updateView(uiView, context: context) // To set a delegate for the presentation controller of an `SFAuthenticationViewController` as soon as possible, - // check the view controller presented by `uiViewController` then set it as a delegate on every view updates. + // check the view controller presented by `view.viewController` then set it as a delegate on every view updates. // INFO: `SFAuthenticationViewController` is a private subclass of `SFSafariViewController`. guard #available(iOS 14.0, *) else { context.coordinator.setInteractiveDismissalDelegateIfPossible() @@ -56,31 +53,31 @@ struct WebAuthenticationPresenter: ViewControllerRepresentab #elseif os(macOS) - func makeNSViewController(context: Context) -> ViewController { - return makeViewController(context: context) + func makeNSView(context: Context) -> ViewType { + return makeView(context: context) } - func updateNSViewController(_ nsViewController: ViewController, context: Context) { - updateViewController(nsViewController, context: context) + func updateNSView(_ nsView: ViewType, context: Context) { + updateView(nsView, context: context) } #elseif os(watchOS) - func makeWKInterfaceObject(context: Context) -> ViewController { - return makeViewController(context: context) + func makeWKInterfaceObject(context: Context) -> ViewType { + return makeView(context: context) } - func updateWKInterfaceObject(_ wkInterfaceObject: ViewController, context: Context) { - updateViewController(wkInterfaceObject, context: context) + func updateWKInterfaceObject(_ wkInterfaceObject: ViewType, context: Context) { + updateView(wkInterfaceObject, context: context) } #endif - private func makeViewController(context: Context) -> ViewController { - return context.coordinator.viewController + private func makeView(context: Context) -> ViewType { + return context.coordinator.view } - private func updateViewController(_ viewController: ViewController, context: Context) { + private func updateView(_ view: ViewType, context: Context) { // Keep the coordinator updated with a new presenter struct. context.coordinator.parent = self context.coordinator.item = item @@ -101,7 +98,7 @@ extension WebAuthenticationPresenter { // MARK: View Controller Holding - let viewController = ConcreteViewController() + let view = ViewType() private weak var session: ASWebAuthenticationSession? // MARK: Item Handling @@ -173,7 +170,7 @@ extension WebAuthenticationPresenter { class PresentationContextProvider: NSObject, ASWebAuthenticationPresentationContextProviding { - weak var coordinator: WebAuthenticationPresenter.Coordinator? + unowned var coordinator: WebAuthenticationPresenter.Coordinator init(coordinator: WebAuthenticationPresenter.Coordinator) { self.coordinator = coordinator @@ -182,7 +179,7 @@ extension WebAuthenticationPresenter { // MARK: ASWebAuthenticationPresentationContextProviding func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { - return coordinator!.viewController.view.window! + return coordinator.view.window! } } @@ -202,7 +199,7 @@ extension WebAuthenticationPresenter { @available(iOS, introduced: 13.0, deprecated: 14.0) func setInteractiveDismissalDelegateIfPossible() { - guard let safariViewController = viewController.presentedViewController as? SFSafariViewController else { + guard let safariViewController = view.viewController?.presentedViewController as? SFSafariViewController else { return } safariViewController.presentationController?.delegate = interactiveDismissalDelegate From 462e76624c4e4f0f8cfb8b85cc71fd9bf22da652 Mon Sep 17 00:00:00 2001 From: Dongkyu Kim Date: Wed, 20 Jan 2021 00:18:54 +0900 Subject: [PATCH 06/10] Add a summary comment to UIView+viewController extension --- Sources/BetterSafariView/UIView+viewController.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/BetterSafariView/UIView+viewController.swift b/Sources/BetterSafariView/UIView+viewController.swift index e517933..80aa67c 100644 --- a/Sources/BetterSafariView/UIView+viewController.swift +++ b/Sources/BetterSafariView/UIView+viewController.swift @@ -3,6 +3,10 @@ import UIKit extension UIView { + + /// The receiver’s view controller, or `nil` if it has none. + /// + /// This property is `nil` if the view has not yet been added to a view controller. var viewController: UIViewController? { if let nextResponder = self.next as? UIViewController { return nextResponder From fc50ead4519779a52a1985371d8197a4d944aee4 Mon Sep 17 00:00:00 2001 From: Dongkyu Kim Date: Wed, 20 Jan 2021 01:20:34 +0900 Subject: [PATCH 07/10] Create Shared directory --- .../BetterSafariView/{Shared.swift => Shared/Identifiables.swift} | 0 Sources/BetterSafariView/{ => Shared}/UIView+viewController.swift | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename Sources/BetterSafariView/{Shared.swift => Shared/Identifiables.swift} (100%) rename Sources/BetterSafariView/{ => Shared}/UIView+viewController.swift (100%) diff --git a/Sources/BetterSafariView/Shared.swift b/Sources/BetterSafariView/Shared/Identifiables.swift similarity index 100% rename from Sources/BetterSafariView/Shared.swift rename to Sources/BetterSafariView/Shared/Identifiables.swift diff --git a/Sources/BetterSafariView/UIView+viewController.swift b/Sources/BetterSafariView/Shared/UIView+viewController.swift similarity index 100% rename from Sources/BetterSafariView/UIView+viewController.swift rename to Sources/BetterSafariView/Shared/UIView+viewController.swift From d04f01553045e01d455b56dc33775d2e56b7f264 Mon Sep 17 00:00:00 2001 From: Dongkyu Kim Date: Sat, 25 Sep 2021 20:41:30 +0900 Subject: [PATCH 08/10] Update BetterSafariViewDemo (watchOS).xcscheme for Xcode 13.0 --- .../BetterSafariViewDemo (watchOS).xcscheme | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/Demo/BetterSafariViewDemo.xcodeproj/xcshareddata/xcschemes/BetterSafariViewDemo (watchOS).xcscheme b/Demo/BetterSafariViewDemo.xcodeproj/xcshareddata/xcschemes/BetterSafariViewDemo (watchOS).xcscheme index 3023fee..475c138 100644 --- a/Demo/BetterSafariViewDemo.xcodeproj/xcshareddata/xcschemes/BetterSafariViewDemo (watchOS).xcscheme +++ b/Demo/BetterSafariViewDemo.xcodeproj/xcshareddata/xcschemes/BetterSafariViewDemo (watchOS).xcscheme @@ -40,10 +40,8 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - + - + - + - - - - - + From 735bbd632031dbd74b74c0ced5587e9a1860b9d4 Mon Sep 17 00:00:00 2001 From: Dongkyu Kim Date: Sat, 25 Sep 2021 21:17:27 +0900 Subject: [PATCH 09/10] Update CHANGELOG.md --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09908f3..5f3cd81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [v2.4.0](https://github.com/stleamist/BetterSafariView/releases/tag/v2.4.0) (2020-09-25) +### Changed +- `SafariViewPresenter` and `WebAuthenticationPresenter` now conforms to `UIViewRepresentable`, instead of `UIViewControllerRepresentable`. + +### Fixed +- Fixed an issue where the `SafariView` is not presented on the multi-layered modal sheets (#20). Thanks @twodayslate! + ## [v2.3.1](https://github.com/stleamist/BetterSafariView/releases/tag/v2.3.1) (2020-01-20) ### Fixed - Fixed an issue where the `SafariView` is not presented on the modal sheets (#9). Thanks @boherna! From 93dc9e79b611ac0f6a3e070e63efe19a13ef7f1f Mon Sep 17 00:00:00 2001 From: Dongkyu Kim Date: Sat, 25 Sep 2021 21:18:15 +0900 Subject: [PATCH 10/10] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8bd990f..ab22bb1 100644 --- a/README.md +++ b/README.md @@ -285,7 +285,7 @@ func prefersEphemeralWebBrowserSession(_ prefersEphemeralWebBrowserSession: Bool Add the following line to the `dependencies` in your [`Package.swift`](https://developer.apple.com/documentation/swift_packages/package) file: ```swift -.package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.3.1")) +.package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.4.0")) ``` Next, add `BetterSafariView` as a dependency for your targets: @@ -304,7 +304,7 @@ import PackageDescription let package = Package( name: "MyPackage", dependencies: [ - .package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.3.1")) + .package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.4.0")) ], targets: [ .target(name: "MyTarget", dependencies: ["BetterSafariView"])