Skip to content

Commit be72487

Browse files
committed
Bump to v2.4.0
2 parents 7046fd1 + 93dc9e7 commit be72487

File tree

7 files changed

+98
-86
lines changed

7 files changed

+98
-86
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## [v2.4.0](https://github.com/stleamist/BetterSafariView/releases/tag/v2.4.0) (2020-09-25)
4+
### Changed
5+
- `SafariViewPresenter` and `WebAuthenticationPresenter` now conforms to `UIViewRepresentable`, instead of `UIViewControllerRepresentable`.
6+
7+
### Fixed
8+
- Fixed an issue where the `SafariView` is not presented on the multi-layered modal sheets (#20). Thanks @twodayslate!
9+
310
## [v2.3.1](https://github.com/stleamist/BetterSafariView/releases/tag/v2.3.1) (2020-01-20)
411
### Fixed
512
- Fixed an issue where the `SafariView` is not presented on the modal sheets (#9). Thanks @boherna!

Demo/BetterSafariViewDemo.xcodeproj/xcshareddata/xcschemes/BetterSafariViewDemo (watchOS).xcscheme

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,46 +40,33 @@
4040
debugDocumentVersioning = "YES"
4141
debugServiceExtension = "internal"
4242
allowLocationSimulation = "YES">
43-
<RemoteRunnable
44-
runnableDebuggingMode = "2"
45-
BundleIdentifier = "com.apple.Carousel"
46-
RemotePath = "/BetterSafari">
43+
<BuildableProductRunnable
44+
runnableDebuggingMode = "0">
4745
<BuildableReference
4846
BuildableIdentifier = "primary"
4947
BlueprintIdentifier = "C7BA0FEF2525D4A7002BC9F7"
5048
BuildableName = "BetterSafariViewDemo.app"
5149
BlueprintName = "BetterSafariViewDemo (watchOS)"
5250
ReferencedContainer = "container:BetterSafariViewDemo.xcodeproj">
5351
</BuildableReference>
54-
</RemoteRunnable>
52+
</BuildableProductRunnable>
5553
</LaunchAction>
5654
<ProfileAction
5755
buildConfiguration = "Release"
5856
shouldUseLaunchSchemeArgsEnv = "YES"
5957
savedToolIdentifier = ""
6058
useCustomWorkingDirectory = "NO"
6159
debugDocumentVersioning = "YES">
62-
<RemoteRunnable
63-
runnableDebuggingMode = "2"
64-
BundleIdentifier = "com.apple.Carousel"
65-
RemotePath = "/BetterSafari">
60+
<BuildableProductRunnable
61+
runnableDebuggingMode = "0">
6662
<BuildableReference
6763
BuildableIdentifier = "primary"
6864
BlueprintIdentifier = "C7BA0FEF2525D4A7002BC9F7"
6965
BuildableName = "BetterSafariViewDemo.app"
7066
BlueprintName = "BetterSafariViewDemo (watchOS)"
7167
ReferencedContainer = "container:BetterSafariViewDemo.xcodeproj">
7268
</BuildableReference>
73-
</RemoteRunnable>
74-
<MacroExpansion>
75-
<BuildableReference
76-
BuildableIdentifier = "primary"
77-
BlueprintIdentifier = "C7BA0FEF2525D4A7002BC9F7"
78-
BuildableName = "BetterSafariViewDemo.app"
79-
BlueprintName = "BetterSafariViewDemo (watchOS)"
80-
ReferencedContainer = "container:BetterSafariViewDemo.xcodeproj">
81-
</BuildableReference>
82-
</MacroExpansion>
69+
</BuildableProductRunnable>
8370
</ProfileAction>
8471
<AnalyzeAction
8572
buildConfiguration = "Debug">

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ func prefersEphemeralWebBrowserSession(_ prefersEphemeralWebBrowserSession: Bool
285285
Add the following line to the `dependencies` in your [`Package.swift`](https://developer.apple.com/documentation/swift_packages/package) file:
286286

287287
```swift
288-
.package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.3.1"))
288+
.package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.4.0"))
289289
```
290290

291291
Next, add `BetterSafariView` as a dependency for your targets:
@@ -304,7 +304,7 @@ import PackageDescription
304304
let package = Package(
305305
name: "MyPackage",
306306
dependencies: [
307-
.package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.3.1"))
307+
.package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.4.0"))
308308
],
309309
targets: [
310310
.target(name: "MyTarget", dependencies: ["BetterSafariView"])

Sources/BetterSafariView/SafariView/SafariViewPresenter.swift

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,25 @@
33
import SwiftUI
44
import SafariServices
55

6-
struct SafariViewPresenter<Item: Identifiable>: UIViewControllerRepresentable {
6+
struct SafariViewPresenter<Item: Identifiable>: UIViewRepresentable {
77

88
// MARK: Representation
99

1010
@Binding var item: Item?
1111
var onDismiss: (() -> Void)? = nil
1212
var representationBuilder: (Item) -> SafariView
1313

14-
// MARK: UIViewControllerRepresentable
14+
// MARK: UIViewRepresentable
1515

1616
func makeCoordinator() -> Coordinator {
1717
return Coordinator(parent: self)
1818
}
1919

20-
func makeUIViewController(context: Context) -> UIViewController {
21-
return context.coordinator.uiViewController
20+
func makeUIView(context: Context) -> UIView {
21+
return context.coordinator.uiView
2222
}
2323

24-
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
25-
24+
func updateUIView(_ uiView: UIView, context: Context) {
2625
// Keep the coordinator updated with a new presenter struct.
2726
context.coordinator.parent = self
2827
context.coordinator.item = item
@@ -43,7 +42,8 @@ extension SafariViewPresenter {
4342

4443
// MARK: View Controller Holding
4544

46-
let uiViewController = UIViewController()
45+
let uiView = UIView()
46+
private weak var safariViewController: SFSafariViewController?
4747

4848
// MARK: Item Handling
4949

@@ -75,54 +75,54 @@ extension SafariViewPresenter {
7575
// MARK: Presentation Handlers
7676

7777
private func presentSafariViewController(with item: Item) {
78-
guard uiViewController.presentedViewController == nil else {
79-
return
80-
}
81-
8278
let representation = parent.representationBuilder(item)
8379
let safariViewController = SFSafariViewController(url: representation.url, configuration: representation.configuration)
8480
safariViewController.delegate = self
8581
representation.applyModification(to: safariViewController)
8682

87-
// 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`.
88-
// As a workaround, use a `rootViewController` of the `window` for presenting.
89-
// (Unlike the other view controllers, a view controller hosted by a cell doesn't have a parent, but has the same window.)
90-
var presentingViewController = uiViewController.view.window?.rootViewController
91-
presentingViewController = presentingViewController?.presentedViewController ?? presentingViewController ?? uiViewController
92-
presentingViewController?.present(safariViewController, animated: true)
83+
// Present a Safari view controller from the `viewController` of `UIViewRepresentable`, instead of `UIViewControllerRepresentable`.
84+
// This fixes an issue where the Safari view controller is not presented properly
85+
// when the `UIViewControllerRepresentable` is detached from the root view controller (e.g. `UIViewController` contained in `UITableViewCell`)
86+
// while allowing it to be presented even on the modal sheets.
87+
// Thanks to: Bohdan Hernandez Navia (@boherna)
88+
guard let presentingViewController = uiView.viewController else {
89+
self.resetItemBinding()
90+
return
91+
}
92+
93+
presentingViewController.present(safariViewController, animated: true)
94+
95+
self.safariViewController = safariViewController
9396
}
9497

9598
private func updateSafariViewController(with item: Item) {
96-
guard let safariViewController = uiViewController.presentedViewController as? SFSafariViewController else {
99+
guard let safariViewController = safariViewController else {
97100
return
98101
}
99102
let representation = parent.representationBuilder(item)
100103
representation.applyModification(to: safariViewController)
101104
}
102105

103106
private func dismissSafariViewController(completion: (() -> Void)? = nil) {
104-
let dismissCompletion: () -> Void = {
105-
self.handleDismissalWithoutResettingItemBinding()
106-
completion?()
107-
}
108-
109-
guard uiViewController.presentedViewController != nil else {
110-
dismissCompletion()
107+
guard let safariViewController = safariViewController else {
111108
return
112109
}
113110

114-
// Check if the `uiViewController` is a instance of the `SFSafariViewController`
115-
// to prevent other controllers presented by the container view from being dismissed unintentionally.
116-
guard let safariViewController = uiViewController.presentedViewController as? SFSafariViewController else {
117-
return
111+
safariViewController.dismiss(animated: true) {
112+
self.handleDismissal()
113+
completion?()
118114
}
119-
safariViewController.dismiss(animated: true, completion: dismissCompletion)
120115
}
121116

122117
// MARK: Dismissal Handlers
123118

119+
// Used when the `viewController` of `uiView` does not exist during the preparation of presentation.
120+
private func resetItemBinding() {
121+
parent.item = nil
122+
}
123+
124124
// Used when the Safari view controller is finished by an item change during view update.
125-
private func handleDismissalWithoutResettingItemBinding() {
125+
private func handleDismissal() {
126126
parent.onDismiss?()
127127
}
128128

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#if os(iOS)
2+
3+
import UIKit
4+
5+
extension UIView {
6+
7+
/// The receiver’s view controller, or `nil` if it has none.
8+
///
9+
/// This property is `nil` if the view has not yet been added to a view controller.
10+
var viewController: UIViewController? {
11+
if let nextResponder = self.next as? UIViewController {
12+
return nextResponder
13+
} else if let nextResponder = self.next as? UIView {
14+
return nextResponder.viewController
15+
} else {
16+
return nil
17+
}
18+
}
19+
}
20+
21+
#endif

Sources/BetterSafariView/WebAuthenticationSession/WebAuthenticationPresenter.swift

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,46 +7,43 @@ import SafariServices
77
#endif
88

99
#if os(iOS)
10-
typealias ConcreteViewController = UIViewController
11-
typealias ViewController = UIViewController
12-
typealias ViewControllerRepresentable = UIViewControllerRepresentable
10+
typealias ViewType = UIView
11+
typealias ViewRepresentableType = UIViewRepresentable
1312
#elseif os(macOS)
14-
typealias ConcreteViewController = NSTabViewController
15-
typealias ViewController = NSViewController
16-
typealias ViewControllerRepresentable = NSViewControllerRepresentable
13+
typealias ViewType = NSView
14+
typealias ViewRepresentableType = NSViewRepresentable
1715
#elseif os(watchOS)
1816
// Use `WKInterfaceInlineMovie` as a concrete interface objct type,
1917
// since there is no public initializer for `WKInterfaceObject`.
20-
typealias ConcreteViewController = WKInterfaceInlineMovie
21-
typealias ViewController = WKInterfaceObject
22-
typealias ViewControllerRepresentable = WKInterfaceObjectRepresentable
18+
typealias ViewType = WKInterfaceInlineMovie
19+
typealias ViewRepresentableType = WKInterfaceObjectRepresentable
2320
#endif
2421

25-
struct WebAuthenticationPresenter<Item: Identifiable>: ViewControllerRepresentable {
22+
struct WebAuthenticationPresenter<Item: Identifiable>: ViewRepresentableType {
2623

2724
// MARK: Representation
2825

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

32-
// MARK: ViewControllerRepresentable
29+
// MARK: ViewRepresentable
3330

3431
func makeCoordinator() -> Coordinator {
3532
return Coordinator(parent: self)
3633
}
3734

3835
#if os(iOS)
3936

40-
func makeUIViewController(context: Context) -> ViewController {
41-
return makeViewController(context: context)
37+
func makeUIView(context: Context) -> ViewType {
38+
return makeView(context: context)
4239
}
4340

44-
func updateUIViewController(_ uiViewController: ViewController, context: Context) {
41+
func updateUIView(_ uiView: ViewType, context: Context) {
4542

46-
updateViewController(uiViewController, context: context)
43+
updateView(uiView, context: context)
4744

4845
// To set a delegate for the presentation controller of an `SFAuthenticationViewController` as soon as possible,
49-
// check the view controller presented by `uiViewController` then set it as a delegate on every view updates.
46+
// check the view controller presented by `view.viewController` then set it as a delegate on every view updates.
5047
// INFO: `SFAuthenticationViewController` is a private subclass of `SFSafariViewController`.
5148
guard #available(iOS 14.0, *) else {
5249
context.coordinator.setInteractiveDismissalDelegateIfPossible()
@@ -56,31 +53,31 @@ struct WebAuthenticationPresenter<Item: Identifiable>: ViewControllerRepresentab
5653

5754
#elseif os(macOS)
5855

59-
func makeNSViewController(context: Context) -> ViewController {
60-
return makeViewController(context: context)
56+
func makeNSView(context: Context) -> ViewType {
57+
return makeView(context: context)
6158
}
6259

63-
func updateNSViewController(_ nsViewController: ViewController, context: Context) {
64-
updateViewController(nsViewController, context: context)
60+
func updateNSView(_ nsView: ViewType, context: Context) {
61+
updateView(nsView, context: context)
6562
}
6663

6764
#elseif os(watchOS)
6865

69-
func makeWKInterfaceObject(context: Context) -> ViewController {
70-
return makeViewController(context: context)
66+
func makeWKInterfaceObject(context: Context) -> ViewType {
67+
return makeView(context: context)
7168
}
7269

73-
func updateWKInterfaceObject(_ wkInterfaceObject: ViewController, context: Context) {
74-
updateViewController(wkInterfaceObject, context: context)
70+
func updateWKInterfaceObject(_ wkInterfaceObject: ViewType, context: Context) {
71+
updateView(wkInterfaceObject, context: context)
7572
}
7673

7774
#endif
7875

79-
private func makeViewController(context: Context) -> ViewController {
80-
return context.coordinator.viewController
76+
private func makeView(context: Context) -> ViewType {
77+
return context.coordinator.view
8178
}
8279

83-
private func updateViewController(_ viewController: ViewController, context: Context) {
80+
private func updateView(_ view: ViewType, context: Context) {
8481
// Keep the coordinator updated with a new presenter struct.
8582
context.coordinator.parent = self
8683
context.coordinator.item = item
@@ -101,8 +98,8 @@ extension WebAuthenticationPresenter {
10198

10299
// MARK: View Controller Holding
103100

104-
let viewController = ConcreteViewController()
105-
private var session: ASWebAuthenticationSession?
101+
let view = ViewType()
102+
private weak var session: ASWebAuthenticationSession?
106103

107104
// MARK: Item Handling
108105

@@ -146,13 +143,13 @@ extension WebAuthenticationPresenter {
146143

147144
representation.applyModification(to: session)
148145

149-
self.session = session
150146
session.start()
147+
148+
self.session = session
151149
}
152150

153151
private func cancelWebAuthenticationSession() {
154152
session?.cancel()
155-
session = nil
156153
}
157154

158155
// MARK: Dismissal Handlers
@@ -173,7 +170,7 @@ extension WebAuthenticationPresenter {
173170

174171
class PresentationContextProvider: NSObject, ASWebAuthenticationPresentationContextProviding {
175172

176-
weak var coordinator: WebAuthenticationPresenter.Coordinator?
173+
unowned var coordinator: WebAuthenticationPresenter.Coordinator
177174

178175
init(coordinator: WebAuthenticationPresenter.Coordinator) {
179176
self.coordinator = coordinator
@@ -182,7 +179,7 @@ extension WebAuthenticationPresenter {
182179
// MARK: ASWebAuthenticationPresentationContextProviding
183180

184181
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
185-
return coordinator!.viewController.view.window!
182+
return coordinator.view.window!
186183
}
187184
}
188185

@@ -202,7 +199,7 @@ extension WebAuthenticationPresenter {
202199

203200
@available(iOS, introduced: 13.0, deprecated: 14.0)
204201
func setInteractiveDismissalDelegateIfPossible() {
205-
guard let safariViewController = viewController.presentedViewController as? SFSafariViewController else {
202+
guard let safariViewController = view.viewController?.presentedViewController as? SFSafariViewController else {
206203
return
207204
}
208205
safariViewController.presentationController?.delegate = interactiveDismissalDelegate

0 commit comments

Comments
 (0)