2 // Created by Jesse Squires
3 // http://www.jessesquires.com
7 // http://jessesquires.github.io/JSQWebViewController
11 // https://github.com/jessesquires/JSQWebViewController
15 // Copyright (c) 2015 Jesse Squires
16 // Released under an MIT license: http://opensource.org/licenses/MIT
23 private let titleKeyPath = "title"
24 private let estimatedProgressKeyPath = "estimatedProgress"
27 /// An instance of `WebViewController` displays interactive web content.
28 open class WebViewController: UIViewController {
32 /// Returns the web view for the controller.
33 public final var webView: WKWebView {
39 /// Returns the progress view for the controller.
40 public final var progressBar: UIProgressView {
46 /// The URL request for the web view. Upon setting this property, the web view immediately begins loading the request.
47 public final var urlRequest: URLRequest {
49 webView.load(urlRequest)
54 Specifies whether or not to display the web view title as the navigation bar title.
55 The default is `false`, which sets the navigation bar title to the URL host name of the URL request.
57 public final var displaysWebViewTitle: Bool = false
59 // MARK: Private properties
61 private final let configuration: WKWebViewConfiguration
62 private final let activities: [UIActivity]?
64 private lazy final var _webView: WKWebView = {
65 let webView = WKWebView(frame: CGRect.zero, configuration: configuration)
66 webView.addObserver(self, forKeyPath: titleKeyPath, options: .new, context: nil)
67 webView.addObserver(self, forKeyPath: estimatedProgressKeyPath, options: .new, context: nil)
68 webView.allowsBackForwardNavigationGestures = true
69 if #available(iOS 9.0, *) {
70 webView.allowsLinkPreview = true
75 private lazy final var _progressBar: UIProgressView = {
76 let progressBar = UIProgressView(progressViewStyle: .bar)
77 progressBar.backgroundColor = .clear
78 progressBar.trackTintColor = .clear
82 // MARK: Initialization
85 Constructs a new `WebViewController`.
87 - parameter urlRequest: The URL request for the web view to load.
88 - parameter configuration: The configuration for the web view.
89 - parameter activities: The custom activities to display in the `UIActivityViewController` that is presented when the action button is tapped.
91 - returns: A new `WebViewController` instance.
93 public init(urlRequest: URLRequest, configuration: WKWebViewConfiguration = WKWebViewConfiguration(), activities: [UIActivity]? = nil) {
94 self.configuration = configuration
95 self.urlRequest = urlRequest
96 self.activities = activities
97 super.init(nibName: nil, bundle: nil)
101 Constructs a new `WebViewController`.
103 - parameter url: The URL to display in the web view.
105 - returns: A new `WebViewController` instance.
107 public convenience init(url: URL) {
108 self.init(urlRequest: URLRequest(url: url))
112 public required init?(coder aDecoder: NSCoder) {
113 self.configuration = WKWebViewConfiguration()
114 self.urlRequest = URLRequest(url: URL(string: "http://")!)
115 self.activities = nil
116 super.init(coder: aDecoder)
120 webView.removeObserver(self, forKeyPath: titleKeyPath, context: nil)
121 webView.removeObserver(self, forKeyPath: estimatedProgressKeyPath, context: nil)
125 // MARK: View lifecycle
128 open override func viewDidLoad() {
130 title = urlRequest.url?.host
131 view.addSubview(_webView)
132 view.addSubview(_progressBar)
134 if presentingViewController?.presentedViewController != nil {
135 navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done,
137 action: #selector(didTapDoneButton(_:)))
140 navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action,
142 action: #selector(didTapActionButton(_:)))
144 webView.load(urlRequest)
148 open override func viewWillAppear(_ animated: Bool) {
149 assert(navigationController != nil, "\(WebViewController.self) must be presented in a \(UINavigationController.self)")
150 super.viewWillAppear(animated)
154 open override func viewDidDisappear(_ animated: Bool) {
155 super.viewDidDisappear(animated)
156 webView.stopLoading()
160 open override func viewDidLayoutSubviews() {
161 super.viewDidLayoutSubviews()
162 webView.frame = view.bounds
164 let isIOS11 = ProcessInfo.processInfo.isOperatingSystemAtLeast(
165 OperatingSystemVersion(majorVersion: 11, minorVersion: 0, patchVersion: 0))
166 let top = isIOS11 ? CGFloat(0.0) : topLayoutGuide.length
167 let insets = UIEdgeInsets(top: top, left: 0, bottom: 0, right: 0)
168 webView.scrollView.contentInset = insets
169 webView.scrollView.scrollIndicatorInsets = insets
171 view.bringSubview(toFront: progressBar)
172 progressBar.frame = CGRect(x: view.frame.minX,
173 y: topLayoutGuide.length,
174 width: view.frame.size.width,
181 @objc private func didTapDoneButton(_ sender: UIBarButtonItem) {
182 dismiss(animated: true, completion: nil)
185 @objc private func didTapActionButton(_ sender: UIBarButtonItem) {
186 if let url = urlRequest.url {
187 let activityVC = UIActivityViewController(activityItems: [url], applicationActivities: activities)
188 activityVC.popoverPresentationController?.barButtonItem = sender
189 present(activityVC, animated: true, completion: nil)
197 open override func observeValue(forKeyPath keyPath: String?,
199 change: [NSKeyValueChangeKey : Any]?,
200 context: UnsafeMutableRawPointer?) {
201 guard let theKeyPath = keyPath , object as? WKWebView == webView else {
202 super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
206 if displaysWebViewTitle && theKeyPath == titleKeyPath {
207 title = webView.title
210 if theKeyPath == estimatedProgressKeyPath {
217 private final func updateProgress() {
218 let completed = webView.estimatedProgress == 1.0
219 progressBar.setProgress(completed ? 0.0 : Float(webView.estimatedProgress), animated: !completed)
220 UIApplication.shared.isNetworkActivityIndicatorVisible = !completed