diff --git a/Sources/SafariView/Configuration.swift b/Sources/SafariView/Configuration.swift deleted file mode 100644 index 5bce45253..000000000 --- a/Sources/SafariView/Configuration.swift +++ /dev/null @@ -1,99 +0,0 @@ -// SafariView -// Configuration.swift -// -// MIT License -// -// Copyright (c) 2021 Varun Santhanam -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the Software), to deal -// -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import UIKit - -@available(iOS 15.0, macCatalyst 15.0, *) -public extension SafariView { - - /// The configuration for a ``SafariView/SafariView`` - struct Configuration { - - /// Create a new safari view configuration - /// - Parameters: - /// - entersReaderIfAvailable: Whether or not the view should enter reader mode automatically, if available. - /// - barCollapsingEnabled: Whether or not the view should support bar collapsing. - /// - activityButton: A custom activity button. - /// - eventAttribution: Event attribution data for Private Click Measurement. - @available(iOS 15.2, *) - public init( - entersReaderIfAvailable: Bool = false, - barCollapsingEnabled: Bool = false, - activityButton: ActivityButton? = nil, - eventAttribution: UIEventAttribution? = nil - ) { - self.entersReaderIfAvailable = entersReaderIfAvailable - self.barCollapsingEnabled = barCollapsingEnabled - self.activityButton = activityButton - _eventAttribution = eventAttribution - } - - /// Create a new safari view configuration - /// - Parameters: - /// - entersReaderIfAvailable: Whether or not the view should enter reader mode automatically, if available. - /// - barCollapsingEnabled: Whether or not the view should support bar collapsing. - /// - activityButton: A custom activity button. - @available(iOS, deprecated: 15.2) - public init( - entersReaderIfAvailable: Bool = false, - barCollapsingEnabled: Bool = false, - activityButton: ActivityButton? = nil - ) { - self.entersReaderIfAvailable = entersReaderIfAvailable - self.barCollapsingEnabled = barCollapsingEnabled - self.activityButton = activityButton - _eventAttribution = nil - } - - /// A value that specifies whether Safari should enter Reader mode, if it is available. - /// - /// Set the value to `true` if Reader mode should be entered automatically when it is available for the webpage; otherwise, false. The default value is `false`. - public var entersReaderIfAvailable: Bool - - /// A value that specifies whether Safari should allow bar collapsing - /// - /// Set the value `true` if bar collapsing should be enabled when the user scrolls; otherwise, false. The default value is `false`. - public var barCollapsingEnabled: Bool - - /// The activity button to use in the Safari View. - public var activityButton: ActivityButton? - - /// An object you use to send tap event attribution data to the browser for Private Click Measurement. - /// - /// For more information about preparing event attribution data, see [`UIEventAttribution`](https://developer.apple.com/documentation/uikit/uieventattribution). - @available(iOS 15.2, macCatalyst 15.2, *) - public var eventAttribution: UIEventAttribution? { - get { - _eventAttribution as? UIEventAttribution? ?? nil - } - set { - _eventAttribution = newValue - } - } - - private var _eventAttribution: Any? - } - -} diff --git a/Sources/SafariView/Environment.swift b/Sources/SafariView/Environment.swift index 27a1c12d8..4b13da90d 100644 --- a/Sources/SafariView/Environment.swift +++ b/Sources/SafariView/Environment.swift @@ -28,23 +28,6 @@ import SwiftUI @available(iOS 15.0, macCatalyst 15.0, *) public extension EnvironmentValues { - /// The configuration value used by a ``SafariView`` - /// - /// You can retrieve this value for the currnet scope using the `@Environment` property wrapper - /// - /// ```swift - /// struct MyView: View { - /// - /// @Environment(\.safariViewConfiguration) - /// var safariViewConfiguration - /// - /// } - /// ``` - var safariViewConfiguration: SafariView.Configuration { - get { self[SafariViewConfigurationEnvironmentKey.self] } - set { self[SafariViewConfigurationEnvironmentKey.self] = newValue } - } - /// The additional activies to include the share sheet displayed inside a ``SafariView`` /// /// You can retrieve this value for the currnet scope using the `@Environment` property wrapper @@ -83,6 +66,16 @@ public extension EnvironmentValues { extension EnvironmentValues { + var safariViewEntersReaderIfAvailable: Bool { + get { self[SafariViewEntersReaderIfAvailableEnvironmentKey.self] } + set { self[SafariViewEntersReaderIfAvailableEnvironmentKey.self] = newValue } + } + + var safariViewBarCollapsingEnabled: Bool { + get { self[SafariViewBarCollapsingEnabledEnvironmentKey.self] } + set { self[SafariViewBarCollapsingEnabledEnvironmentKey.self] = newValue } + } + var safariViewControlTintColor: Color { get { self[SafariViewControlTintColorEnvironmentKey.self] } set { self[SafariViewControlTintColorEnvironmentKey.self] = newValue } @@ -100,13 +93,23 @@ extension EnvironmentValues { } -private struct SafariViewConfigurationEnvironmentKey: EnvironmentKey { +private struct SafariViewEntersReaderIfAvailableEnvironmentKey: EnvironmentKey { + + // MARK: - EnvironmentKey + + typealias Value = Bool + + static let defaultValue: Value = false + +} + +private struct SafariViewBarCollapsingEnabledEnvironmentKey: EnvironmentKey { // MARK: - EnvironmentKey - typealias Value = SafariView.Configuration + typealias Value = Bool - static var defaultValue: SafariView.Configuration { .init() } + static let defaultValue: Value = false } diff --git a/Sources/SafariView/Modifiers.swift b/Sources/SafariView/Modifiers.swift index 550b10399..09c10ebff 100644 --- a/Sources/SafariView/Modifiers.swift +++ b/Sources/SafariView/Modifiers.swift @@ -28,12 +28,25 @@ import SwiftUI @available(iOS 15.0, macCatalyst 15.0, *) public extension View { - /// Set the configuration of safari views within this view + /// Set the automatic reader behavior of safari views within this view /// - /// - Parameter configuration: The configuration to use + /// This modifier is the equivelent of the of the [`entersReaderIfAvailable`](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller/configuration/1648471-entersreaderifavailable) property of a [`SFSafariViewController.Configuration`](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller/configuration) + /// + /// - Parameter entersReaderIfAvailable: Whether or not the safari view should automatically enter reader mode if available. + /// - Returns: The modified view + func safariEntersReaderIfAvailable(_ entersReaderIfAvailable: Bool = true) -> some View { + let modifier = SafariViewEntersReaderIfAvailableModifier(entersReaderIfAvailable: entersReaderIfAvailable) + return ModifiedContent(content: self, modifier: modifier) + } + + /// Set the bar collapsing behavior of safari views within this view + /// + /// This modifier is the equivelent of the of the [`barCollapsingEnabled`](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller/configuration/2887469-barcollapsingenabled) property of a [`SFSafariViewController.Configuration`](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller/configuration) + /// + /// - Parameter barCollapsingEnabled: Whether or not bar collpasing should be enabled. /// - Returns: The modified view - func safariConfiguration(_ configuration: SafariView.Configuration) -> some View { - let modifier = SafariViewConfigurationModifier(configuration: configuration) + func safariBarCollapsingEnabled(_ barCollapsingEnabled: Bool = true) -> some View { + let modifier = SafariViewBarCollapsingEnabledModifier(barCollapsingEnabled: barCollapsingEnabled) return ModifiedContent(content: self, modifier: modifier) } @@ -191,24 +204,47 @@ public extension View { } -private struct SafariViewConfigurationModifier: ViewModifier { +private struct SafariViewEntersReaderIfAvailableModifier: ViewModifier { // MARK: - Initializers - init(configuration: SafariView.Configuration) { - self.configuration = configuration + init(entersReaderIfAvailable: Bool) { + self.entersReaderIfAvailable = entersReaderIfAvailable } + // MARK: - ViewModifier + @ViewBuilder func body(content: Content) -> some View { content - .environment(\.safariViewConfiguration, configuration) + .environment(\.safariViewEntersReaderIfAvailable, entersReaderIfAvailable) } // MARK: - Private - private let configuration: SafariView.Configuration + private let entersReaderIfAvailable: Bool + +} + +private struct SafariViewBarCollapsingEnabledModifier: ViewModifier { + + // MARK: - Initializers + + init(barCollapsingEnabled: Bool) { + self.barCollapsingEnabled = barCollapsingEnabled + } + + // MARK: - ViewModifier + + @ViewBuilder + func body(content: Content) -> some View { + content + .environment(\.safariViewBarCollapsingEnabled, barCollapsingEnabled) + } + + // MARK: - Private + private let barCollapsingEnabled: Bool } private struct SafariViewControlTintColorModifier: ViewModifier { diff --git a/Sources/SafariView/Presentation.swift b/Sources/SafariView/Presentation.swift index 11ffaf3e8..763715e38 100644 --- a/Sources/SafariView/Presentation.swift +++ b/Sources/SafariView/Presentation.swift @@ -312,11 +312,11 @@ public extension View { /// } /// ``` /// - /// - Parameter url: A binding to an optional source of truth for the ``SafariView``. When the URL is non-nil, the system passes the URL to the modifier’s closure. You display this content in a ``SafariView`` that you create that the system displays to the user. If the URL changes, the system dismisses the ``SafariView`` and replaces it with a new one using the same process. + /// - Parameters: + /// - url: A binding to an optional source of truth for the ``SafariView``. When the URL is non-nil, the system passes the URL to the modifier’s closure. You display this content in a ``SafariView`` that you create that the system displays to the user. If the URL changes, the system dismisses the ``SafariView`` and replaces it with a new one using the same process. + /// - onDismiss: The closure to execute when dismissing the ``SafariView`` /// - Returns: The modified view - func safari(url: Binding) -> some View { - safari(item: url, id: \.hashValue) { url in - SafariView(url: url) - } + func safari(url: Binding, onDismiss: (() -> Void)? = nil) -> some View { + safari(url: url, onDismiss: onDismiss) { url in SafariView(url: url) } } } diff --git a/Sources/SafariView/SafariView.docc/Environment.md b/Sources/SafariView/SafariView.docc/Environment.md index 488945159..b1e679902 100644 --- a/Sources/SafariView/SafariView.docc/Environment.md +++ b/Sources/SafariView/SafariView.docc/Environment.md @@ -6,6 +6,5 @@ Environment Values used by Safari View ### Values -- ``safariViewConfiguration`` - ``safariViewIncludedActivities`` - ``safariViewExcludedActivityTypes`` diff --git a/Sources/SafariView/SafariView.docc/Modifiers.md b/Sources/SafariView/SafariView.docc/Modifiers.md index f0e28418f..cbabf91f4 100644 --- a/Sources/SafariView/SafariView.docc/Modifiers.md +++ b/Sources/SafariView/SafariView.docc/Modifiers.md @@ -6,7 +6,8 @@ View modifiers used to configure and present Safari Views. ### Configuration -- ``safariConfiguration(_:)`` +- ``safariEntersReaderIfAvailable(_:)`` +- ``safariBarCollapsingEnabled(_:)`` ### Appearance @@ -20,7 +21,6 @@ View modifiers used to configure and present Safari Views. - ``safari(url:onDismiss:safariView:)`` - ``safari(item:onDismiss:safariView:)`` - ``safari(item:id:onDismiss:safariView:)`` -- ``safari(url:)`` ### Activities diff --git a/Sources/SafariView/SafariView.docc/View.md b/Sources/SafariView/SafariView.docc/View.md index 3be7d10ff..83c631f6b 100644 --- a/Sources/SafariView/SafariView.docc/View.md +++ b/Sources/SafariView/SafariView.docc/View.md @@ -10,11 +10,11 @@ A SafariView is best presented by using one the `safari` view modifiers, or via ### Creating a Safari View -- ``init(url:onInitialLoad:onInitialRedirect:onOpenInBrowser:)`` +- ``init(url:activityButton:onInitialLoad:onInitialRedirect:onOpenInBrowser:)`` +- ``init(url:activityButton:eventAttribution:onInitialLoad:onInitialRedirect:onOpenInBrowser:)`` ### Configuring the View -- ``Configuration`` - ``ActivityButton`` - ``DismissButtonStyle`` diff --git a/Sources/SafariView/SafariView.swift b/Sources/SafariView/SafariView.swift index 5da32cfd4..51f35d0f9 100644 --- a/Sources/SafariView/SafariView.swift +++ b/Sources/SafariView/SafariView.swift @@ -36,16 +36,47 @@ public struct SafariView: View { /// Create a SafariView /// - Parameters: /// - url: URL to load + /// - activityButton: Custom activity button to include in the safari view /// - onInitialLoad: Closure to execute on initial load /// - onInitialRedirect: Closure to execute on intial redirect - /// - onOpenInBrowser: Closure to execute if a user moves from a SafariView to Safari.app + /// - onOpenInBrowser: Closure to execute if a user moves from a `SafariView` to `Safari.app` public init( url: URL, + activityButton: ActivityButton? = nil, onInitialLoad: ((_ didLoadSuccessfully: Bool) -> Void)? = nil, onInitialRedirect: ((_ url: URL) -> Void)? = nil, onOpenInBrowser: (() -> Void)? = nil ) { self.url = url + self.activityButton = activityButton + eventAttribution = nil + self.onInitialLoad = onInitialLoad + self.onInitialRedirect = onInitialRedirect + self.onOpenInBrowser = onOpenInBrowser + } + + /// Create a SafariView with tap attribution for Private Click Measurement + /// + /// For more information about preparing event attribution data, see [`UIEventAttribution`](https://developer.apple.com/documentation/uikit/uieventattribution) + /// - Parameters: + /// - url: URL to load + /// - activityButton: Custom activity button to include in the safari view + /// - eventAttribution: An object you use to send tap event attribution data to the browser for Private Click Measurement. + /// - onInitialLoad: Closure to execute on initial load + /// - onInitialRedirect: Closure to execute on intial redirect + /// - onOpenInBrowser: Closure to execute if a user moves from a `SafariView` to `Safari.app` + @available(iOS 15.2, macCatalyst 15.2, *) + public init( + url: URL, + activityButton: ActivityButton? = nil, + eventAttribution: UIEventAttribution?, + onInitialLoad: ((_ didLoadSuccessfully: Bool) -> Void)? = nil, + onInitialRedirect: ((_ url: URL) -> Void)? = nil, + onOpenInBrowser: (() -> Void)? = nil + ) { + self.url = url + self.activityButton = activityButton + self.eventAttribution = eventAttribution self.onInitialLoad = onInitialLoad self.onInitialRedirect = onInitialRedirect self.onOpenInBrowser = onOpenInBrowser @@ -103,8 +134,11 @@ public struct SafariView: View { // MARK: - Private - @Environment(\.safariViewConfiguration) - private var configuration: Configuration + @Environment(\.safariViewEntersReaderIfAvailable) + private var entersReaderIfAvailable: Bool + + @Environment(\.safariViewBarCollapsingEnabled) + private var barCollapsingEnabled: Bool @Environment(\.safariViewBarTintColor) private var barTintColor: Color? @@ -121,6 +155,8 @@ public struct SafariView: View { @Environment(\.safariViewExcludedActivityTypes) private var excludedActivityTypes: ExcludedActivityTypes + private let activityButton: ActivityButton? + private let eventAttribution: AnyObject? private let url: URL private let onInitialLoad: ((Bool) -> Void)? private let onInitialRedirect: ((URL) -> Void)? @@ -132,6 +168,18 @@ public struct SafariView: View { controller.dismissButtonStyle = dismissButtonStyle } + private func buildConfiguration() -> SFSafariViewController.Configuration { + let configuration = SFSafariViewController.Configuration() + configuration.entersReaderIfAvailable = entersReaderIfAvailable + configuration.barCollapsingEnabled = barCollapsingEnabled + configuration.activityButton = activityButton + if #available(iOS 15.2, *), + let eventAttribution { + configuration.eventAttribution = unsafeDowncast(eventAttribution, to: UIEventAttribution.self) + } + return configuration + } + private struct Safari: UIViewControllerRepresentable { // MARK: - Initializers @@ -151,7 +199,7 @@ public struct SafariView: View { func makeUIViewController(context: Context) -> SFSafariViewController { let safari = SFSafariViewController(url: parent.url, - configuration: parent.configuration.buildConfiguration()) + configuration: parent.buildConfiguration()) safari.modalPresentationStyle = .none safari.delegate = delegate parent.apply(to: safari) @@ -342,7 +390,7 @@ public struct SafariView: View { onOpenInBrowser = rep.onOpenInBrowser includedActivities = rep.includedActivities excludedActivityTypes = rep.excludedActivityTypes - let vc = SFSafariViewController(url: rep.url, configuration: rep.configuration.buildConfiguration()) + let vc = SFSafariViewController(url: rep.url, configuration: rep.buildConfiguration()) vc.delegate = self rep.apply(to: vc) @@ -514,7 +562,7 @@ public struct SafariView: View { onOpenInBrowser = rep.onOpenInBrowser includedActivities = rep.includedActivities excludedActivityTypes = rep.excludedActivityTypes - let vc = SFSafariViewController(url: rep.url, configuration: rep.configuration.buildConfiguration()) + let vc = SFSafariViewController(url: rep.url, configuration: rep.buildConfiguration()) vc.delegate = self rep.apply(to: vc) guard let presenting = view.controller else { @@ -644,18 +692,3 @@ private extension UIView { } } - -private extension SafariView.Configuration { - - func buildConfiguration() -> SFSafariViewController.Configuration { - let configuration = SFSafariViewController.Configuration() - configuration.entersReaderIfAvailable = entersReaderIfAvailable - configuration.barCollapsingEnabled = barCollapsingEnabled - if #available(iOS 15.2, macCatalyst 15.2, *) { - configuration.eventAttribution = eventAttribution - } - configuration.activityButton = activityButton - return configuration - } - -}