Skip to content

Commit

Permalink
Bump to v2.4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
stleamist committed Sep 25, 2021
2 parents 7046fd1 + 93dc9e7 commit be72487
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 86 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,46 +40,33 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.Carousel"
RemotePath = "/BetterSafari">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C7BA0FEF2525D4A7002BC9F7"
BuildableName = "BetterSafariViewDemo.app"
BlueprintName = "BetterSafariViewDemo (watchOS)"
ReferencedContainer = "container:BetterSafariViewDemo.xcodeproj">
</BuildableReference>
</RemoteRunnable>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.Carousel"
RemotePath = "/BetterSafari">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C7BA0FEF2525D4A7002BC9F7"
BuildableName = "BetterSafariViewDemo.app"
BlueprintName = "BetterSafariViewDemo (watchOS)"
ReferencedContainer = "container:BetterSafariViewDemo.xcodeproj">
</BuildableReference>
</RemoteRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C7BA0FEF2525D4A7002BC9F7"
BuildableName = "BetterSafariViewDemo.app"
BlueprintName = "BetterSafariViewDemo (watchOS)"
ReferencedContainer = "container:BetterSafariViewDemo.xcodeproj">
</BuildableReference>
</MacroExpansion>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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"])
Expand Down
62 changes: 31 additions & 31 deletions Sources/BetterSafariView/SafariView/SafariViewPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,25 @@
import SwiftUI
import SafariServices

struct SafariViewPresenter<Item: Identifiable>: UIViewControllerRepresentable {
struct SafariViewPresenter<Item: Identifiable>: UIViewRepresentable {

// MARK: Representation

@Binding var item: Item?
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
Expand All @@ -43,7 +42,8 @@ extension SafariViewPresenter {

// MARK: View Controller Holding

let uiViewController = UIViewController()
let uiView = UIView()
private weak var safariViewController: SFSafariViewController?

// MARK: Item Handling

Expand Down Expand Up @@ -75,54 +75,54 @@ 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 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 `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)
guard let presentingViewController = uiView.viewController else {
self.resetItemBinding()
return
}

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)
representation.applyModification(to: safariViewController)
}

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.handleDismissal()
completion?()
}
safariViewController.dismiss(animated: true, completion: dismissCompletion)
}

// 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?()
}

Expand Down
File renamed without changes.
21 changes: 21 additions & 0 deletions Sources/BetterSafariView/Shared/UIView+viewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#if os(iOS)

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
} else if let nextResponder = self.next as? UIView {
return nextResponder.viewController
} else {
return nil
}
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -7,46 +7,43 @@ 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<Item: Identifiable>: ViewControllerRepresentable {
struct WebAuthenticationPresenter<Item: Identifiable>: ViewRepresentableType {

// MARK: Representation

@Binding var item: Item?
var representationBuilder: (Item) -> WebAuthenticationSession

// MARK: ViewControllerRepresentable
// MARK: ViewRepresentable

func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}

#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()
Expand All @@ -56,31 +53,31 @@ struct WebAuthenticationPresenter<Item: Identifiable>: 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
Expand All @@ -101,8 +98,8 @@ extension WebAuthenticationPresenter {

// MARK: View Controller Holding

let viewController = ConcreteViewController()
private var session: ASWebAuthenticationSession?
let view = ViewType()
private weak var session: ASWebAuthenticationSession?

// MARK: Item Handling

Expand Down Expand Up @@ -146,13 +143,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
Expand All @@ -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
Expand All @@ -182,7 +179,7 @@ extension WebAuthenticationPresenter {
// MARK: ASWebAuthenticationPresentationContextProviding

func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
return coordinator!.viewController.view.window!
return coordinator.view.window!
}
}

Expand All @@ -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
Expand Down

0 comments on commit be72487

Please sign in to comment.