diff --git a/Sources/SafariView/DismissButtonStyle.swift b/Sources/SafariView/DismissButtonStyle.swift new file mode 100644 index 000000000..2b2fa1007 --- /dev/null +++ b/Sources/SafariView/DismissButtonStyle.swift @@ -0,0 +1,55 @@ +// SafariView +// DismissButtonStyle.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 SafariServices + +public extension SafariView { + + /// An enumeration describing the various dismiss buttons styles available in a ``SafariView`` + enum DismissButtonStyle: Equatable, Hashable, Sendable { + + /// The done dismiss button style. + case done + + /// The close dismiss button style. + case close + + /// The cancel dismiss button style. + case cancel + + /// The default dismiss button style. + public static var `default`: DismissButtonStyle = .close + + var uikit: SFSafariViewController.DismissButtonStyle { + switch self { + case .done: return .done + case .close: return .close + case .cancel: return .cancel + } + } + + } + +} diff --git a/Sources/SafariView/Environment.swift b/Sources/SafariView/Environment.swift index 65c83cbc3..d50c23d24 100644 --- a/Sources/SafariView/Environment.swift +++ b/Sources/SafariView/Environment.swift @@ -139,7 +139,7 @@ private struct SafariViewDismissButtonStyleEnvironmentKey: EnvironmentKey { typealias Value = SafariView.DismissButtonStyle - static var defaultValue: Value { .close } + static var defaultValue: Value { .default } } diff --git a/Sources/SafariView/Presentation.swift b/Sources/SafariView/Presentation.swift index 8d97d3913..52df52dae 100644 --- a/Sources/SafariView/Presentation.swift +++ b/Sources/SafariView/Presentation.swift @@ -264,7 +264,7 @@ public extension View { func safari( url: Binding, onDismiss: (() -> Void)? = nil, - @ViewBuilder safariView: @escaping (URL) -> SafariView + @ViewBuilder safariView: @escaping (URL) -> SafariView = { url in SafariView(url: url) } ) -> some View { safari( item: url, @@ -273,50 +273,4 @@ public extension View { safariView: safariView ) } - - /// Presents a ``SafariView`` using the given URL as a data source for the ``SafariView``'s content - /// - /// Use this method when you need to present a ``SafariView`` with content from a custom data source. The example below shows a custom data source `InventoryItem` that the closure uses to populate the ``SafariView`` before it is shown to the user: - /// - /// ```swift - /// import Foundation - /// import SafariView - /// import SwiftUI - /// - /// struct InventoryItem { - /// let title: String - /// let url: URL - /// } - /// - /// struct InventoryList: View { - /// - /// init(inventory: [InventoryItem]) { - /// self.inventory = inventory - /// } - /// - /// var inventory: [InventoryItem] - /// - /// @State private var selectedURL: URL? - /// - /// var body: some View { - /// List(inventory.indices, id: \.self) { index in - /// Button(action: { - /// self.selectedURL = inventory[index].url - /// }) { - /// Text(inventory[index].title) - /// } - /// } - /// .safari(item: $selectedURL) - /// } - /// - /// } - /// ``` - /// - /// - 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, onDismiss: (() -> Void)? = nil) -> some View { - safari(url: url, onDismiss: onDismiss) { url in SafariView(url: url) } - } } diff --git a/Sources/SafariView/PrewarmingToken.swift b/Sources/SafariView/PrewarmingToken.swift new file mode 100644 index 000000000..a53e1ecd3 --- /dev/null +++ b/Sources/SafariView/PrewarmingToken.swift @@ -0,0 +1,51 @@ +// SafariView +// PrewarmingToken.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 SafariServices + +@available(iOS 15.0, macCatalyst 15.0, *) +public extension SafariView { + + /// A type created when SafariServices begins prewarming a connection. + final class PrewarmingToken { + + /// The URLs who's connections are being prewarmed. + public let urls: [URL] + + /// Invalidate the prewarmed connections. + public func invalidate() { + token.invalidate() + } + + init(_ token: SFSafariViewController.PrewarmingToken, urls: [URL]) { + self.token = token + self.urls = urls + } + + private let token: SFSafariViewController.PrewarmingToken + + } + +} diff --git a/Sources/SafariView/SafariView.docc/Environment.md b/Sources/SafariView/SafariView.docc/Environment.md deleted file mode 100644 index b1e679902..000000000 --- a/Sources/SafariView/SafariView.docc/Environment.md +++ /dev/null @@ -1,10 +0,0 @@ -# ``SafariView/SwiftUI/EnvironmentValues`` - -Environment Values used by Safari View - -## Topics - -### Values - -- ``safariViewIncludedActivities`` -- ``safariViewExcludedActivityTypes`` diff --git a/Sources/SafariView/SafariView.docc/ExcludedActivityTypes.md b/Sources/SafariView/SafariView.docc/ExcludedActivityTypes.md index 030cc4fca..5da743a1b 100644 --- a/Sources/SafariView/SafariView.docc/ExcludedActivityTypes.md +++ b/Sources/SafariView/SafariView.docc/ExcludedActivityTypes.md @@ -4,6 +4,8 @@ @DocumentationExtension(mergeBehavior: append) } +## Overview + You can initialize instances of this type using an array literal of `UIActivity.ActivityType` values. For example: ```swift diff --git a/Sources/SafariView/SafariView.docc/IncludedActivities.md b/Sources/SafariView/SafariView.docc/IncludedActivities.md index cb187a841..c90ed05e7 100644 --- a/Sources/SafariView/SafariView.docc/IncludedActivities.md +++ b/Sources/SafariView/SafariView.docc/IncludedActivities.md @@ -4,6 +4,8 @@ @DocumentationExtension(mergeBehavior: append) } +## Overview + You can initialize instances of this type using an array literal of `UIActivity` values. For example: ```swift diff --git a/Sources/SafariView/SafariView.docc/Modifiers.md b/Sources/SafariView/SafariView.docc/Modifiers.md deleted file mode 100644 index cbabf91f4..000000000 --- a/Sources/SafariView/SafariView.docc/Modifiers.md +++ /dev/null @@ -1,30 +0,0 @@ -# ``SafariView/SwiftUI/View`` - -View modifiers used to configure and present Safari Views. - -## Topics - -### Configuration - -- ``safariEntersReaderIfAvailable(_:)`` -- ``safariBarCollapsingEnabled(_:)`` - -### Appearance - -- ``safariBarTintColor(_:)`` -- ``safariControlTintColor(_:)`` -- ``safariDismissButtonStyle(_:)`` - -### Presentation - -- ``safari(isPresented:onDismiss:safariView:)`` -- ``safari(url:onDismiss:safariView:)`` -- ``safari(item:onDismiss:safariView:)`` -- ``safari(item:id:onDismiss:safariView:)`` - -### Activities - -- ``includedSafariActivities(_:)-2u8l9`` -- ``includedSafariActivities(_:)-362lz`` -- ``excludedSafariActivityTypes(_:)-tvrg`` -- ``excludedSafariActivityTypes(_:)-1v8zq`` diff --git a/Sources/SafariView/SafariView.docc/SafariView.md b/Sources/SafariView/SafariView.docc/SafariView.md index 7be90bb80..e4162a68c 100644 --- a/Sources/SafariView/SafariView.docc/SafariView.md +++ b/Sources/SafariView/SafariView.docc/SafariView.md @@ -4,29 +4,10 @@ SafariServices in SwiftUI ## Overview -`SafariView` is a wrapper around [`SFSafariViewController`](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) for use within SwiftUI applications. The view includes Safari features such as Reader, AutoFill, Fraudulent Website Detection, and content blocking. The user's activity and interaction with `SafariView` are not visible to your app, which cannot access AutoFill data, browsing history, or website data. You do not need to secure data between your app and Safari. If you would like to share data between your app and Safari, so it is easier for a user to log in only one time, use [`ASWebAuthenticationSession`](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession) instead. - -- Important: In accordance with [App Store Review Guidelines](https://developer.apple.com/app-store/review/guidelines/), this view must be used to visibly present information to users; the view may not be hidden or obscured by other views or layers. Additionally, an app may not use `SafariView` to track users without their knowledge and consent. - -UI features include the following: -- A read-only address field with a security indicator and a Reader button -- An Action button that invokes an activity view controller offering custom services from your app, and activities, such as messaging, from the system and other extensions -- A Done button, back and forward navigation buttons, and a button to open the page directly in Safari - -The library contains a single `View`-conforming struct, also named `SafariView`, as well as several view modifiers used to control the appearance, behavior and presentation of a ``SafariView/SafariView`` - -- Note: In Mac apps built with Mac Catalyst, SafariView launches the default web browser instead of displaying a modal window. SafariView is not compatible with macOS, tvOS, or watchOS. +`SafariView` is a package that wraps [`SafariServices`](https://developer.apple.com/documentation/safariservices/) for use with SwiftUI applications. Currently, it contains a single SwiftUI view, also called `SafariView`, which wraps [`SFSafariViewController`](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller). ## Topics ### Views -- ``SafariView`` - -### Modifiers - -- ``SwiftUI/View`` - -### Environment - -- ``SwiftUI/EnvironmentValues`` +- ``SafariView/SafariView`` diff --git a/Sources/SafariView/SafariView.docc/View.md b/Sources/SafariView/SafariView.docc/View.md index cee02147a..74b68a248 100644 --- a/Sources/SafariView/SafariView.docc/View.md +++ b/Sources/SafariView/SafariView.docc/View.md @@ -4,7 +4,29 @@ @DocumentationExtension(mergeBehavior: append) } -A SafariView is best presented by using one the `safari` view modifiers, or via sheet presentation, though it does work in other scenarios as well. For configuration, appearance, and presentation options, see the documentation for the included view modifiers here: ``SwiftUI/View``. +## Overview + +The view includes Safari features such as Reader, AutoFill, Fraudulent Website Detection, and content blocking. The user's activity and interaction with `SafariView` are not visible to your app, which cannot access AutoFill data, browsing history, or website data. You do not need to secure data between your app and Safari. If you would like to share data between your app and Safari, so it is easier for a user to log in only one time, use [`ASWebAuthenticationSession`](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession) instead. + +- Important: In accordance with [App Store Review Guidelines](https://developer.apple.com/app-store/review/guidelines/), this view must be used to visibly present information to users; the view may not be hidden or obscured by other views or layers. Additionally, an app may not use `SafariView` to track users without their knowledge and consent. + +UI features include the following: +- A read-only address field with a security indicator and a Reader button +- An Action button that invokes an activity view controller offering custom services from your app, and activities, such as messaging, from the system and other extensions +- A Done button, back and forward navigation buttons, and a button to open the page directly in Safari + +The library contains a single `View`-conforming struct, also named `SafariView`, as well as several view modifiers used to control the appearance, behavior and presentation of a ``SafariView/SafariView`` + +- Note: In Mac apps built with Mac Catalyst, SafariView launches the default web browser instead of displaying a modal window. SafariView is not compatible with macOS, tvOS, or watchOS. + +You can present a `SafariView` using the built-in presentation view modifiers: + +- ``SwiftUI/View/safari(isPresented:onDismiss:safariView:)`` +- ``SwiftUI/View/safari(url:onDismiss:safariView:)`` +- ``SwiftUI/View/safari(item:onDismiss:safariView:)`` +- ``SwiftUI/View/safari(item:id:onDismiss:safariView:)`` + +You can also use sheet presentation, or any other presentation mechanism of your choice. ## Topics @@ -14,10 +36,24 @@ A SafariView is best presented by using one the `safari` view modifiers, or via - ``init(url:activityButton:onInitialLoad:onInitialRedirect:onOpenInBrowser:)`` - ``init(url:activityButton:eventAttribution:onInitialLoad:onInitialRedirect:onOpenInBrowser:)`` -### Custom Activities +### Configuration -- ``IncludedActivities`` -- ``ExcludedActivityTypes`` +- ``SwiftUI/View/safariEntersReaderIfAvailable(_:)`` +- ``SwiftUI/View/safariBarCollapsingEnabled(_:)`` + +### Appearance + +- ``SwiftUI/View/safariBarTintColor(_:)`` +- ``SwiftUI/View/safariControlTintColor(_:)`` +- ``SwiftUI/View/safariDismissButtonStyle(_:)`` +- ``SafariView/SafariView/DismissButtonStyle`` + +### Presentation + +- ``SwiftUI/View/safari(isPresented:onDismiss:safariView:)`` +- ``SwiftUI/View/safari(url:onDismiss:safariView:)`` +- ``SwiftUI/View/safari(item:onDismiss:safariView:)`` +- ``SwiftUI/View/safari(item:id:onDismiss:safariView:)`` ### Connection Prewarming @@ -28,3 +64,17 @@ A SafariView is best presented by using one the `safari` view modifiers, or via - ``clearWebsiteData()`` - ``clearWebsiteData(completionHandler:)`` + +### Custom Activities + +- ``SwiftUI/View/includedSafariActivities(_:)-2u8l9`` +- ``SwiftUI/View/includedSafariActivities(_:)-362lz`` +- ``SafariView/SafariView/IncludedActivities`` +- ``SwiftUI/View/excludedSafariActivityTypes(_:)-tvrg`` +- ``SwiftUI/View/excludedSafariActivityTypes(_:)-1v8zq`` +- ``SafariView/SafariView/ExcludedActivityTypes`` + +### Environment Values + +- ``SwiftUI/EnvironmentValues/safariViewIncludedActivities`` +- ``SwiftUI/EnvironmentValues/safariViewExcludedActivityTypes`` diff --git a/Sources/SafariView/SafariView.swift b/Sources/SafariView/SafariView.swift index f5a73d9ca..acd93aa8c 100644 --- a/Sources/SafariView/SafariView.swift +++ b/Sources/SafariView/SafariView.swift @@ -105,19 +105,10 @@ public struct SafariView: View { // MARK: - API - /// A convenience typealias for [`SFSafariViewController.DismissButtonStyle`](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller/dismissbuttonstyle) - public typealias DismissButtonStyle = SFSafariViewController.DismissButtonStyle - /// A convenience typealias for [`SFSafariViewController.ActivityButton`](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller/activitybutton) @available(iOS 15.0, macCatalyst 15.0, *) public typealias ActivityButton = SFSafariViewController.ActivityButton - /// A convenience typealias for [`SFSafariViewController.PrewarmingToken`](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller/prewarmingtoken) - /// - /// You can generate prewarming tokens for invalidation using the ``prewarmConnections(to:)`` static method. - @available(iOS 15.0, macCatalyst 15.0, *) - public typealias PrewarmingToken = SFSafariViewController.PrewarmingToken - /// Prewarm the connection to a list of provided URLs /// /// You can use this returned value of this method to invalidate the prewarmed cache by invoking the `invaldate()` method on the token. @@ -133,7 +124,8 @@ public struct SafariView: View { @available(iOS 15.0, macCatalyst 15.0, *) @discardableResult public static func prewarmConnections(to URLs: [URL]) -> PrewarmingToken { - SFSafariViewController.prewarmConnections(to: URLs) + let token = SFSafariViewController.prewarmConnections(to: URLs) + return .init(token, urls: URLs) } /// Clears the safari view's cache using [Swift Concurrency](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/). @@ -151,6 +143,7 @@ public struct SafariView: View { // MARK: - View + @_documentation(visibility: internal) public var body: some View { Safari(parent: self) .ignoresSafeArea(.container, edges: .all) @@ -189,7 +182,7 @@ public struct SafariView: View { private func apply(to controller: SFSafariViewController) { controller.preferredBarTintColor = barTintColor.map(UIColor.init) controller.preferredControlTintColor = UIColor(controlTintColor) - controller.dismissButtonStyle = dismissButtonStyle + controller.dismissButtonStyle = dismissButtonStyle.uikit } private func buildConfiguration() -> SFSafariViewController.Configuration {