A better way to present a SFSafariViewController or start a ASWebAuthenticationSession in SwiftUI

Overview

version Swift: 5.1+ iOS: 13.0+ macOS: 10.15+ watchOS: 6.2+
Build on Xcode SwiftPM: compatible license contact: @stleamist

BetterSafariView

A better way to present a SFSafariViewController or start a ASWebAuthenticationSession in SwiftUI.

Contents

Motivation

SwiftUI is a strong, intuitive way to build user interfaces, but was released with some part of existing elements missing. One example of those missing elements is the SFSafariViewController.

Fortunately, Apple provides a way to wrap UIKit elements into SwiftUI views. A common approach to place the SFSafariViewController inside SwiftUI is to create a simple view representing a SFSafariViewController, then present it with a sheet(isPresented:onDismiss:content:) modifier or a NavigationLink button (See RootView.swift in the demo project).

However, there’s a problem in this approach: it can’t present the SFSafariViewController with its default presentation style — a push transition covers full screen. A sheet modifier can present the view only in a modal sheet, and a navigation link shows the two navigation bars at the top so we have to deal with them. This comes down to the conclusion that there’s no option to present it the right way except for using present(_:animated:completion:) method of a UIViewController instance, but it is prohibited and not a good design to access the UIHostingController directly from the SwiftUI view.

BetterSafariView clearly achieves this goal by hosting a simple UIViewController to present a SFSafariViewController as a view’s background. In this way, a ASWebAuthenticationSession is also able to be started without any issue in SwiftUI.

Requirements

  • Xcode 11.0+
  • Swift 5.1+

SafariView

  • iOS 13.0+
  • Mac Catalyst 13.0+

WebAuthenticationSession

  • iOS 13.0+
  • Mac Catalyst 13.0+
  • macOS 10.15+
  • watchOS 6.2+

Usage

With the following modifiers, you can use it in a similar way to present a sheet.

SafariView

Example

import SwiftUI
import BetterSafariView

struct ContentView: View {
    
    @State private var presentingSafariView = false
    
    var body: some View {
        Button(action: {
            self.presentingSafariView = true
        }) {
            Text("Present SafariView")
        }
        .safariView(isPresented: $presentingSafariView) {
            SafariView(
                url: URL(string: "https://github.com/")!,
                configuration: SafariView.Configuration(
                    entersReaderIfAvailable: false,
                    barCollapsingEnabled: true
                )
            )
            .preferredBarAccentColor(.clear)
            .preferredControlAccentColor(.accentColor)
            .dismissButtonStyle(.done)
        }
    }
}

View Modifiers

safariView(isPresented:onDismiss:content:)
/// Presents a Safari view when a given condition is true.
func safariView(
    isPresented: Binding<Bool>,
    onDismiss: (() -> Void)? = nil,
    content: @escaping () -> SafariView
) -> some View
safariView(item:onDismiss:content:)
/// Presents a Safari view using the given item as a data source for the `SafariView` to present.
func safariView<Item: Identifiable>(
    item: Binding
    ?>,
    
    onDismiss: (() 
    -> 
    Void)
    ? 
    = 
    nil,
    
    content: 
    @escaping (Item) 
    -> SafariView
) 
    -> 
    some View
   

SafariView Initializers

init(url:)
/// Creates a Safari view that loads the specified URL.
init(url: URL)
init(url:configuration:)
/// Creates and configures a Safari view that loads the specified URL.
init(url: URL, configuration: SafariView.Configuration)

SafariView Modifiers

preferredBarAccentColor(_:)
/// Sets the accent color for the background of the navigation bar and the toolbar.
func preferredBarAccentColor(_ color: Color?) -> SafariView
preferredControlAccentColor(_:)
/// Sets the accent color for the control buttons on the navigation bar and the toolbar.
func preferredControlAccentColor(_ color: Color?) -> SafariView
dismissButtonStyle(_:)
/// Sets the style of dismiss button to use in the navigation bar to close `SafariView`.
func dismissButtonStyle(_ style: SafariView.DismissButtonStyle) -> SafariView

WebAuthenticationSession

Example

import SwiftUI
import BetterSafariView

struct ContentView: View {
    
    @State private var startingWebAuthenticationSession = false
    
    var body: some View {
        Button(action: {
            self.startingWebAuthenticationSession = true
        }) {
            Text("Start WebAuthenticationSession")
        }
        .webAuthenticationSession(isPresented: $startingWebAuthenticationSession) {
            WebAuthenticationSession(
                url: URL(string: "https://github.com/login/oauth/authorize")!,
                callbackURLScheme: "github"
            ) { callbackURL, error in
                print(callbackURL, error)
            }
            .prefersEphemeralWebBrowserSession(false)
        }
    }
}

View Modifiers

webAuthenticationSession(isPresented:content:)
/// Starts a web authentication session when a given condition is true.
func webAuthenticationSession(
    isPresented: Binding<Bool>,
    content: @escaping () -> WebAuthenticationSession
) -> some View
webAuthenticationSession(item:content:)
/// Starts a web authentication session using the given item as a data source for the `WebAuthenticationSession` to start.
func webAuthenticationSession<Item: Identifiable>(
    item: Binding
    ?>,
    
    content: 
    @escaping (Item) 
    -> WebAuthenticationSession
) 
    -> 
    some View
   

WebAuthenticationSession Initializers

init(url:callbackURLScheme:completionHandler:)
/// Creates a web authentication session instance.
init(
    url: URL,
    callbackURLScheme: String?,
    completionHandler: @escaping (URL?, Error?) -> Void
)
init(url:callbackURLScheme:onCompletion:)
/// Creates a web authentication session instance.
init(
    url: URL,
    callbackURLScheme: String?,
    onCompletion: @escaping (Result
    Error>) 
    -> 
    Void
)
   

WebAuthenticationSession Modifier

prefersEphemeralWebBrowserSession(_:)
/// Configures whether the session should ask the browser for a private authentication session.
func prefersEphemeralWebBrowserSession(_ prefersEphemeralWebBrowserSession: Bool) -> WebAuthenticationSession

Known Issues

  • In .webAuthenticationSession(item:content:) modifier, the functionality that replaces a session on the item's identity change is not implemented, as there is no non-hacky way to be notified when the session's dismissal animation is completed.

Installation

Swift Package Manager

Add the following line to the dependencies in your Package.swift file:

.package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.4.0"))

Next, add BetterSafariView as a dependency for your targets:

.target(name: "MyTarget", dependencies: ["BetterSafariView"])

Your completed description may look like this:

// swift-tools-version:5.1

import PackageDescription

let package = Package(
    name: "MyPackage",
    dependencies: [
        .package(url: "https://github.com/stleamist/BetterSafariView.git", .upToNextMajor(from: "2.4.0"))
    ],
    targets: [
        .target(name: "MyTarget", dependencies: ["BetterSafariView"])
    ]
)

Xcode

Select File > Swift Packages > Add Package Dependency, then enter the following URL:

https://github.com/stleamist/BetterSafariView.git

For more details, see Adding Package Dependencies to Your App.

Demo

You can see how it works on each platform and compare it with the other naive implementations in the demo project. Check out the demo app by opening BetterSafariView.xcworkspace.

NOTE: This demo project is available for iOS 14.0+, macOS 11.0+, and watchOS 7.0+, while the package is compatible with iOS 13.0+, macOS 10.15+, and watchOS 6.2+.

License

BetterSafariView is released under the MIT license. See LICENSE for details.

You might also like...
A better way to operate QR Code in Swift, support iOS, macOS, watchOS and tvOS.
A better way to operate QR Code in Swift, support iOS, macOS, watchOS and tvOS.

EFQRCode is a lightweight, pure-Swift library for generating stylized QRCode images with watermark or icon, and for recognizing QRCode from images, in

A better way to handle gestures on iOS
A better way to handle gestures on iOS

Tactile is a safer and more idiomatic way to respond to gestures and control events. It lets you catch bugs at compile time and write more expressive

A better way to operate QR Code in Swift, support iOS, macOS, watchOS and tvOS.
A better way to operate QR Code in Swift, support iOS, macOS, watchOS and tvOS.

EFQRCode is a lightweight, pure-Swift library for generating stylized QRCode images with watermark or icon, and for recognizing QRCode from images, in

The better way to deal with JSON in Objective-C (inspired by SwiftyJSON)

NSTEasyJSON Inpired by SwiftyJSON. NSTEasyJSON makes it easy to deal with JSON data in Objective-C. Why is the typical JSON handling in Objective-C NO

🚀 Create XCFrameworks with ease! A Command Line Tool to create XCFramework for multiple platforms at one shot! The better way to deal with XCFrameworks for iOS, Mac Catalyst, tvOS, macOS, and watchOS.
🚀 Create XCFrameworks with ease! A Command Line Tool to create XCFramework for multiple platforms at one shot! The better way to deal with XCFrameworks for iOS, Mac Catalyst, tvOS, macOS, and watchOS.

Surmagic 🚀 Create XCFramework with ease! A Command Line Tool to create XCFramework for multiple platforms at one shot! The better way to deal with XC

A better way to operate QR Code in Swift, support iOS, macOS, watchOS and tvOS.
A better way to operate QR Code in Swift, support iOS, macOS, watchOS and tvOS.

EFQRCode is a lightweight, pure-Swift library for generating stylized QRCode images with watermark or icon, and for recognizing QRCode from images, in

Start your next Open-Source Swift Framework 📦
Start your next Open-Source Swift Framework 📦

SwiftKit enables you to easily generate a cross platform Swift Framework from your command line. It is the best way to start your next Open-Source Swi

FDSoundActivatedRecorder - Start recording when the user speaks

FDSoundActivatedRecorder Start recording when the user speaks. All you have to do is tell us when to start listening. Then we wait for an audible nois

Custom Time Picker ViewController with Selection of start and end times in Swift 🔶
Custom Time Picker ViewController with Selection of start and end times in Swift 🔶

LFTimePicker Custom Time Picker ViewController with Selection of start and end times in Swift 🔶 . Based on Adey Salyard's design @ Dribbble One to tw

This is a Swift package with support for macOS that allows to start Java Jar's with the default or a custom JVM.

Jar.swift jar runner for macos Jar.swift is created and maintaned with ❥ by Sascha Muellner. What? This is a Swift package with support for macOS that

An App that gives a nice interface where the user can type in their start location and destination

SixtCarSummoner What it does We developed an App that gives a nice interface where the user can type in their start location and destination. The user

You can easily start up a little breaking game by one line.
You can easily start up a little breaking game by one line.

JDBreaksLoading Introduction JDBreaksLoading Based on simple UIView and SpriteKit. You can easily start up a little breaking game by one line. By the

Swift extension which adds start, animating and completion closures for CAAnimation objects. Aka, CAAnimation + Closure / Block
Swift extension which adds start, animating and completion closures for CAAnimation objects. Aka, CAAnimation + Closure / Block

Swift-CAAnimation-Closure Swift extension which adds start, animating and completion closures for CAAnimation objects. Aka, CAAnimation + Closure or C

Allows a swipe on any part of the screen to start an interruptible pop animation to the previous view
Allows a swipe on any part of the screen to start an interruptible pop animation to the previous view

Lazy Pop SwiftUI Swiping on any part of the screen starts an interruptible pop animation to the previous view. Forked from https://github.com/rishi420

Green-grass-ios - GitHub Gardener? Start with Green Grass
Green-grass-ios - GitHub Gardener? Start with Green Grass

Green Grass 🌿 Introduction GitHub Gardener? Start with Green Grass! This app ma

A Metal application that mimics SAO
A Metal application that mimics SAO "Link Start" scene.

SAO Link Start Effect This is a Metal application that mimics SAO "Link Start" scene. Building The project requires Xcode 13.3 or later version. The a

A SwiftUI view that enables you to properly present a UIActivityViewController. (iOS)

ActivityView Also available as a part of my SwiftUI+ Collection – just add it to Xcode 13+ A SwiftUI view that enables you to properly present a UIAct

SwiftUI package to present a Bottom Sheet interactable view with the desired Detents. Also known as Half sheet.
SwiftUI package to present a Bottom Sheet interactable view with the desired Detents. Also known as Half sheet.

BottomSheetSUI BottomSheetSUI is a package that gives you the ability to show a Bottom sheet intractable, where you can add your own SwiftUI view. You

A collection of bugs present in the SwiftUI beta.

Gosh Darn Bugs! GoshDarnBugs is a collection of... you guessed it, bugs. Usage Clone the repository. Open GoshDarnBugs.xcodeproj Click Run. Why? Swift

Comments
  • How to use this library in targets that don't support URL Schemes (App Clip)

    How to use this library in targets that don't support URL Schemes (App Clip)

    Apple has stated in several videos for App Clips to use ASWebAuthenticationSession to help sign in. I can't find any documentation on how this is achieved via an App Clip target, which URL Schemes are expressly prohibited.

    This seems like something that should be supported out of the box. Any experience or documentation to refer developers to? I suspect others that want to use ASWebAuthenticationSession in their SwiftUI App Clips might find this library too.

    opened by edorphy 0
  • [ImgBot] Optimize images

    [ImgBot] Optimize images

    Beep boop. Your images are optimized!

    Your image file size has been reduced by 49% 🎉

    Details

    | File | Before | After | Percent reduction | |:--|:--|:--|:--| | /Demo/Shared/Assets.xcassets/AppIcon-watchOS.appiconset/AppIcon-watchOS-1024px.png | 202.49kb | 60.39kb | 70.17% | | /Demo/Shared/Assets.xcassets/AppIcon-iOS.appiconset/AppIcon-iOS-1024px.png | 181.05kb | 56.09kb | 69.02% | | /Demo/Shared/Assets.xcassets/AppIcon-macOS.appiconset/[email protected] | 160.57kb | 72.06kb | 55.12% | | /Docs/Images/BetterSafariView-Cover.png | 340.96kb | 161.69kb | 52.58% | | /Demo/Shared/Assets.xcassets/AppIcon-watchOS.appiconset/AppIcon-watchOS-216px.png | 23.76kb | 12.46kb | 47.55% | | /Docs/Images/BetterSafariViewDemo-iOS.png | 352.08kb | 187.21kb | 46.83% | | /Demo/Shared/Assets.xcassets/AppIcon-macOS.appiconset/[email protected] | 59.88kb | 32.52kb | 45.69% | | /Demo/Shared/Assets.xcassets/AppIcon-macOS.appiconset/AppIcon-macOS-512px.png | 59.88kb | 32.52kb | 45.69% | | /Demo/Shared/Assets.xcassets/AppIcon-watchOS.appiconset/AppIcon-watchOS-196px.png | 20.54kb | 11.45kb | 44.26% | | /Demo/Shared/Assets.xcassets/AppIcon-iOS.appiconset/AppIcon-iOS-180px.png | 15.47kb | 9.05kb | 41.47% | | /Demo/Shared/Assets.xcassets/AppIcon-watchOS.appiconset/AppIcon-watchOS-172px.png | 16.82kb | 9.94kb | 40.88% | | /Demo/Shared/Assets.xcassets/AppIcon-iOS.appiconset/AppIcon-iOS-167px.png | 14.03kb | 8.67kb | 38.18% | | /Demo/Shared/Assets.xcassets/AppIcon-iOS.appiconset/AppIcon-iOS-152px.png | 12.26kb | 7.66kb | 37.54% | | /Demo/Shared/Assets.xcassets/AppIcon-macOS.appiconset/AppIcon-macOS-256px.png | 22.92kb | 15.12kb | 34.06% | | /Demo/Shared/Assets.xcassets/AppIcon-macOS.appiconset/[email protected] | 23.51kb | 15.51kb | 34.04% | | /Demo/Shared/Assets.xcassets/AppIcon-iOS.appiconset/AppIcon-iOS-120px.png | 8.85kb | 6.00kb | 32.19% | | /Demo/Shared/Assets.xcassets/AppIcon-watchOS.appiconset/AppIcon-watchOS-100px.png | 8.05kb | 5.64kb | 29.94% | | /Demo/Shared/Assets.xcassets/AppIcon-watchOS.appiconset/AppIcon-watchOS-88px.png | 6.69kb | 4.83kb | 27.80% | | /Demo/Shared/Assets.xcassets/AppIcon-watchOS.appiconset/AppIcon-watchOS-87px.png | 6.59kb | 4.80kb | 27.16% | | /Demo/Shared/Assets.xcassets/AppIcon-watchOS.appiconset/AppIcon-watchOS-80px.png | 5.86kb | 4.39kb | 25.08% | | /Demo/Shared/Assets.xcassets/AppIcon-iOS.appiconset/AppIcon-iOS-87px.png | 5.63kb | 4.28kb | 23.92% | | /Docs/Images/BetterSafariViewDemo-watchOS.png | 21.98kb | 16.75kb | 23.79% | | /Docs/Images/BetterSafariViewDemo-macOS.png | 58.20kb | 45.14kb | 22.45% | | /Demo/Shared/Assets.xcassets/AppIcon-macOS.appiconset/AppIcon-macOS-128px.png | 9.02kb | 7.02kb | 22.23% | | /Demo/Shared/Assets.xcassets/AppIcon-iOS.appiconset/AppIcon-iOS-80px.png | 5.00kb | 3.91kb | 21.76% | | /Demo/Shared/Assets.xcassets/AppIcon-iOS.appiconset/AppIcon-iOS-76px.png | 4.67kb | 3.70kb | 20.72% | | /Demo/Shared/Assets.xcassets/AppIcon-watchOS.appiconset/AppIcon-watchOS-58px.png | 3.88kb | 3.18kb | 18.03% | | /Demo/Shared/Assets.xcassets/AppIcon-iOS.appiconset/AppIcon-iOS-60px.png | 3.29kb | 2.81kb | 14.52% | | /Demo/Shared/Assets.xcassets/AppIcon-iOS.appiconset/AppIcon-iOS-58px.png | 3.22kb | 2.77kb | 14.05% | | /Demo/Shared/Assets.xcassets/AppIcon-watchOS.appiconset/AppIcon-watchOS-55px.png | 3.52kb | 3.04kb | 13.70% | | /Demo/Shared/Assets.xcassets/AppIcon-watchOS.appiconset/AppIcon-watchOS-48px.png | 2.88kb | 2.56kb | 11.21% | | /Demo/Shared/Assets.xcassets/AppIcon-macOS.appiconset/[email protected] | 3.20kb | 2.90kb | 9.43% | | /Docs/Images/BetterSafariView-Icon.svg | 3.34kb | 3.19kb | 4.62% | | /Demo/Shared/Assets.xcassets/AppIcon-iOS.appiconset/AppIcon-iOS-40px.png | 1.90kb | 1.85kb | 2.52% | | /Docs/Images/BetterSafariView-Comparison.svg | 61.99kb | 60.52kb | 2.37% | | /Docs/Images/BetterSafariView-Logotype.svg | 10.05kb | 9.98kb | 0.72% | | | | | | | Total : | 1,744.02kb | 891.60kb | 48.88% |


    📝 docs | :octocat: repo | 🙋🏾 issues | 🏪 marketplace

    ~Imgbot - Part of Optimole family

    opened by imgbot[bot] 0
Owner
Billy
Young and Wild and free
Billy
Useful SwiftUI container view's. FittingGeometry, ScrollableView, LayoutReader, PageView, UIKitView. (iOS)

Containers Also available as a part of my SwiftUI+ Collection – just add it to Xcode 13+ Useful SwiftUI container view's for additional convenience. I

SwiftUI+ 32 Dec 27, 2022
A better way to present a SFSafariViewController or start a ASWebAuthenticationSession in SwiftUI

A better way to present a SFSafariViewController or start a ASWebAuthenticationSession in SwiftUI.

Billy 4 Sep 26, 2022
Don't start from scratch, start from Here! This is a starter project for iOS projects. It contains all the basic configurations and common libraries for your project.

Starter-iOS Don't start from scratch, start from Here! This is a starter project for iOS projects. It contains all the basic configurations and common

Shaban Kamel 6 May 24, 2022
A simple to use, standard interface for authenticating to oauth 2.0 protected endpoints via SFSafariViewController.

AuthenticationViewController A simple to use, standard interface for authenticating to OAuth 2.0 protected endpoints via SFSafariViewController. Instr

Raul Riera 256 Dec 14, 2022
OIDCLite implements the basics of getting a token using Apple's ASWebAuthenticationSession

OIDCLite While there are a few good Swift packages for Open ID Connect out there, most are /very/ heavyweight and can get quite complex. For projects

Joel Rennich 7 Apr 20, 2022
DrawerKit lets an UIViewController modally present another UIViewController in a manner similar to the way Apple's Maps app works.

DrawerKit What is DrawerKit? DrawerKit is a custom view controller presentation mimicking the kind of behaviour in the Apple Maps app. It lets any vie

Babylon Health 773 Dec 27, 2022
ReleaseNotesKit - a brand new, elegant, and extremely simple way to present the recent version’s release notes to your users

ReleaseNotesKit This is ReleaseNotesKit, a brand new, elegant, and extremely simple way to present the recent version’s release notes to your users. R

Swapnanil Dhol 22 Jun 30, 2022
Simple way to present custom views as a popup in iOS and tvOS.

PopupKit PopupKit is a simple and flexible iOS framework for presenting any custom view as a popup. It includes a variety of options for controlling h

Pointwelve 59 Mar 1, 2022
The fastest and easiest way to present a UITableView - in 3 lines of code.

Swift Table The fastest and easiest way to present a UITableView - in 3 lines of code. Powered by Generics to present any data type. No need to use th

Benyam Alemu Sood 16 Nov 1, 2022
A better way to operate QR Code in Swift, support iOS, macOS, watchOS and tvOS.

EFQRCode is a lightweight, pure-Swift library for generating stylized QRCode images with watermark or icon, and for recognizing QRCode from images, in

EFPrefix 4.3k Dec 29, 2022