What’s new in SFSafariViewController
New API to bring one of your app extensions to a customized button on
SFSafariViewController.Tapping that button will run your app extensions directly from
SFSafariViewController’s toolbar, including running JavaScript on the page
import UIKit
import SafariServices
class ViewController: UIViewController {
func showSafariViewController (pageURL: URL) {
let configuration = SFSafariViewController.Configuration()
configuration.activityButton = SFSafariViewController.ActivityButton(
templateImage: UlImage(named: "example_image")!,
extensionIdentifier: "com.example.extension"
)
let safariViewController = SFSafariViewController(url: pageURL, configuration: configuration)
present(safariViewController, animated: true)
}
}For anything more than this, you will need to use
WKWebView.
What’s new in WKWebView
new APIs to allow you to easily interact with the content in your web view without having to deal with injecting JavaScript:
access theme and related colors for a website
manage text interaction
control media playback
new browser-level APIs, previously only available to Safari.app:
disable HTTPS upgrade
control media capture (getUserMedia)
manage downloads
Access colors
Get themeColor:
class ViewController : UIViewController {
var headerView: UIView
var webView: WKWebView
var observations: [NSKevValueObservation] = []
override func viewDidLoad() {
let observation = webView.observe(\.themeColor) { // 👈🏻
[unowned self] webView, _ in
self.headerView.backgroundColor=webView.themeColor
}
observations.append (observation)
}
}If themeColor isn’t set, there’s an alternate calculated background color exposed as underPageBackgroundColor:
class ViewController : UIViewController {
var headerView: UIView
var webView: WKWebView
var observations: [NSKeyValueObservation] = []
override func viewDidLoad() {
let observation = webView.observe(\.underPageBackgroundColor) { // 👈🏻
[unowned self] webView,_ in
self.headerView.backgroundColor = webView.underPageBackgroundColor
}
observations.append (observation)
}
}Text interaction
Disable/enable text interaction:
override func loadView(){
let preferences = WKPreferences()
preferences.textInteractionEnabled = false
let webConfiguration = WKWebViewConfiguration()
configuration.preferences = preferences
webView = WKWebView(frame: .zero, configuration: preferences)
...
}Media playback
Pause all media:
await webView.pauseAllMediaPlayback()Close all media windows:
await webView.closeAllMediaPresentations()Request media state:
let playbackState = await webView.requestMediaPlaybackState()Set Media Playback Suspended State
await webView.setAllMediaPlaybackSuspended(true)Disable HTTPS upgrade
override func loadView(){
let webConfiguration = WKWebViewConfiguration()
webConfiguration.upgradeknownHostsToHTTPS = false
webView = WKWebView(frame: .zero, configuration: webConfiguration)
...
}Control media capture (getUserMedia)
WKWebViewsupportsgetUserMediafrom iOS 14.3; which allows WebRTC functions to work inside your appFrom iOS 15, you can load your web content from a custom scheme handler; the user request prompt will show your app as the origin of the request, rather than show a request from the website URL
New api to prompt user for camera and microphone permissions when working with web content
// Prompt for microphone access only
class ViewController: UIViewController, WKUIDelegate{
var webView: WKWebView
override func viewDidLoad() {
webView.uiDelegate = self
webView.load(URLRequest (url: URL(string: "https://recordMyMic.example.org")!))
}
func webView(
_ webView: WKWebView,
decideMediaCapturePermissionFor origin: WKSecurityOrigin,
initiatedByFrame frame: WKFrameInfo,
type: WKMediaCaptureType
) async -> WKPermissionDecision {
return type == .microphone? .prompt: .deny
}
}Manage downloads
Three ways to initiate a download from the web:
The Web Content can initiate a download via Javascript:
let a = document.createElement('a'):
let b = new Blob([1,2,3]);
a.href = URL.createObjectURL (b);
a.download = "filename.dat":
a.click();The server can initiate a download by responding like this, after calling a
loadRequeston your web view
HTTP/1.1 200 OK
Content-Disposition: attachment; filename="filename.dat"
Content-Length: 100App via new API
class ViewController : UIViewController, WKDownloadDelegate {
@IBOutlet var textField: UITextField!
@IBOutlet var webView: WKWebView!
@IBAction func downloadFile() {
guard let url = URL(string: textField.text ??"') else { return }
async {
let download = await webView.startDownload(using: URLRequest (url: url))
download.delegate = self
}
}Whatever method you use, when you get the
WKDownloadobject, you need to set thedelegateproperty on that object to be able to tell it where to write the bytes to disk. If you do not, the download will automatically be cancelledIf a download fails, the data to resume the download will be handed to you if you implement the
download(_:didFailWithError:resumeData:)method on the delegate
