1 changed files with 412 additions and 0 deletions
@ -0,0 +1,412 @@ |
|||||
|
// |
||||
|
// WebBrowserViewController.swift |
||||
|
// WebBrowser |
||||
|
// |
||||
|
// Created by Xin Hong on 16/4/26. |
||||
|
// Copyright © 2016年 Teambition. All rights reserved. |
||||
|
// |
||||
|
|
||||
|
import UIKit |
||||
|
import WebKit |
||||
|
|
||||
|
open class WebBrowserViewController: UIViewController { |
||||
|
open weak var delegate: WebBrowserDelegate? |
||||
|
open var language: WebBrowserLanguage = .english { |
||||
|
didSet { |
||||
|
InternationalControl.sharedControl.language = language |
||||
|
} |
||||
|
} |
||||
|
open var tintColor = UIColor.blue { |
||||
|
didSet { |
||||
|
updateTintColor() |
||||
|
} |
||||
|
} |
||||
|
open var barTintColor: UIColor? { |
||||
|
didSet { |
||||
|
updateBarTintColor() |
||||
|
} |
||||
|
} |
||||
|
open var isToolbarHidden = false { |
||||
|
didSet { |
||||
|
navigationController?.setToolbarHidden(isToolbarHidden, animated: true) |
||||
|
} |
||||
|
} |
||||
|
open var toolbarItemSpace = WebBrowser.defaultToolbarItemSpace { |
||||
|
didSet { |
||||
|
itemFixedSeparator.width = toolbarItemSpace |
||||
|
} |
||||
|
} |
||||
|
open var isShowActionBarButton = true { |
||||
|
didSet { |
||||
|
updateToolBarState() |
||||
|
} |
||||
|
} |
||||
|
open var customApplicationActivities = [UIActivity]() |
||||
|
open var isShowURLInNavigationBarWhenLoading = true |
||||
|
open var isShowPageTitleInNavigationBar = true |
||||
|
|
||||
|
fileprivate var webView = WKWebView(frame: CGRect.zero) |
||||
|
|
||||
|
public func getWKWebView() -> WKWebView { |
||||
|
return webView |
||||
|
} |
||||
|
|
||||
|
fileprivate lazy var progressView: UIProgressView = { |
||||
|
let progressView = UIProgressView(progressViewStyle: .default) |
||||
|
progressView.trackTintColor = .clear |
||||
|
progressView.tintColor = self.tintColor |
||||
|
return progressView |
||||
|
}() |
||||
|
fileprivate var previousNavigationControllerNavigationBarAppearance = NavigationBarAppearance() |
||||
|
fileprivate var previousNavigationControllerToolbarAppearance = ToolbarAppearance() |
||||
|
|
||||
|
fileprivate lazy var refreshButton: UIBarButtonItem = { |
||||
|
let refreshButton = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(WebBrowserViewController.refreshButtonTapped(_:))) |
||||
|
return refreshButton |
||||
|
}() |
||||
|
fileprivate lazy var stopButton: UIBarButtonItem = { |
||||
|
let stopButton = UIBarButtonItem(barButtonSystemItem: .stop, target: self, action: #selector(WebBrowserViewController.stopButtonTapped(_:))) |
||||
|
return stopButton |
||||
|
}() |
||||
|
fileprivate lazy var backButton: UIBarButtonItem = { |
||||
|
let backIcon = WebBrowser.image(named: "backIcon") |
||||
|
let backButton = UIBarButtonItem(image: backIcon, style: .plain, target: self, action: #selector(WebBrowserViewController.backButtonTapped(_:))) |
||||
|
return backButton |
||||
|
}() |
||||
|
fileprivate lazy var forwardButton: UIBarButtonItem = { |
||||
|
let forwardIcon = WebBrowser.image(named: "forwardIcon") |
||||
|
let forwardButton = UIBarButtonItem(image: forwardIcon, style: .plain, target: self, action: #selector(WebBrowserViewController.forwardButtonTapped(_:))) |
||||
|
return forwardButton |
||||
|
}() |
||||
|
fileprivate lazy var actionButton: UIBarButtonItem = { |
||||
|
let actionButton = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(WebBrowserViewController.actionButtonTapped(_:))) |
||||
|
return actionButton |
||||
|
}() |
||||
|
fileprivate lazy var itemFixedSeparator: UIBarButtonItem = { |
||||
|
let itemFixedSeparator = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil) |
||||
|
itemFixedSeparator.width = self.toolbarItemSpace |
||||
|
return itemFixedSeparator |
||||
|
}() |
||||
|
fileprivate lazy var itemFlexibleSeparator: UIBarButtonItem = { |
||||
|
let itemFlexibleSeparator = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) |
||||
|
return itemFlexibleSeparator |
||||
|
}() |
||||
|
|
||||
|
public var onOpenExternalAppHandler: ((_ isOpen: Bool) -> Void)? |
||||
|
|
||||
|
// MARK: - Life cycle |
||||
|
open override func viewDidLoad() { |
||||
|
super.viewDidLoad() |
||||
|
|
||||
|
savePreviousNavigationControllerState() |
||||
|
configureWebView() |
||||
|
configureProgressView() |
||||
|
} |
||||
|
|
||||
|
open override func viewWillAppear(_ animated: Bool) { |
||||
|
super.viewWillAppear(animated) |
||||
|
navigationController?.setNavigationBarHidden(false, animated: true) |
||||
|
navigationController?.navigationBar.setBackgroundImage(nil, for: .default) |
||||
|
navigationController?.navigationBar.shadowImage = nil |
||||
|
navigationController?.navigationBar.isTranslucent = true |
||||
|
navigationController?.navigationBar.addSubview(progressView) |
||||
|
navigationController?.setToolbarHidden(isToolbarHidden, animated: true) |
||||
|
|
||||
|
progressView.alpha = 0 |
||||
|
updateTintColor() |
||||
|
updateBarTintColor() |
||||
|
updateToolBarState() |
||||
|
} |
||||
|
|
||||
|
open override func viewWillDisappear(_ animated: Bool) { |
||||
|
super.viewWillDisappear(animated) |
||||
|
restorePreviousNavigationControllerState(animated: animated) |
||||
|
progressView.removeFromSuperview() |
||||
|
} |
||||
|
|
||||
|
public convenience init(configuration: WKWebViewConfiguration) { |
||||
|
self.init() |
||||
|
webView = WKWebView(frame: CGRect.zero, configuration: configuration) |
||||
|
} |
||||
|
|
||||
|
open class func rootNavigationWebBrowser(webBrowser: WebBrowserViewController) -> UINavigationController { |
||||
|
webBrowser.navigationItem.rightBarButtonItem = UIBarButtonItem(title: LocalizedString(key: "Done"), style: .done, target: webBrowser, action: #selector(WebBrowserViewController.doneButtonTapped(_:))) |
||||
|
let navigationController = UINavigationController(rootViewController: webBrowser) |
||||
|
return navigationController |
||||
|
} |
||||
|
|
||||
|
deinit { |
||||
|
webView.uiDelegate = nil |
||||
|
webView.navigationDelegate = nil |
||||
|
if isViewLoaded { |
||||
|
webView.removeObserver(self, forKeyPath: WebBrowser.estimatedProgressKeyPath) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// MARK: - Public |
||||
|
open func loadRequest(_ request: URLRequest) { |
||||
|
webView.load(request) |
||||
|
} |
||||
|
|
||||
|
open func loadURL(_ url: URL) { |
||||
|
webView.load(URLRequest(url: url)) |
||||
|
} |
||||
|
|
||||
|
open func loadURLString(_ urlString: String) { |
||||
|
guard let url = URL(string: urlString) else { |
||||
|
return |
||||
|
} |
||||
|
webView.load(URLRequest(url: url)) |
||||
|
} |
||||
|
|
||||
|
open func loadHTMLString(_ htmlString: String, baseURL: URL?) { |
||||
|
webView.loadHTMLString(htmlString, baseURL: baseURL) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
extension WebBrowserViewController { |
||||
|
// MARK: - Helper |
||||
|
fileprivate func configureWebView() { |
||||
|
webView.frame = view.bounds |
||||
|
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight] |
||||
|
webView.autoresizesSubviews = true |
||||
|
webView.navigationDelegate = self |
||||
|
webView.uiDelegate = self |
||||
|
webView.isMultipleTouchEnabled = true |
||||
|
webView.scrollView.alwaysBounceVertical = true |
||||
|
view.addSubview(webView) |
||||
|
|
||||
|
webView.addObserver(self, forKeyPath: WebBrowser.estimatedProgressKeyPath, options: .new, context: &WebBrowser.estimatedProgressContext) |
||||
|
} |
||||
|
|
||||
|
fileprivate func configureProgressView() { |
||||
|
let yPosition: CGFloat = { |
||||
|
guard let navigationBar = self.navigationController?.navigationBar else { |
||||
|
return 0 |
||||
|
} |
||||
|
return navigationBar.frame.height - self.progressView.frame.height |
||||
|
}() |
||||
|
progressView.frame = CGRect(x: 0, y: yPosition, width: view.frame.width, height: progressView.frame.width) |
||||
|
progressView.autoresizingMask = [.flexibleWidth, .flexibleTopMargin] |
||||
|
} |
||||
|
|
||||
|
fileprivate func savePreviousNavigationControllerState() { |
||||
|
guard let navigationController = navigationController else { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
var navigationBarAppearance = NavigationBarAppearance(navigationBar: navigationController.navigationBar) |
||||
|
navigationBarAppearance.isHidden = navigationController.isNavigationBarHidden |
||||
|
previousNavigationControllerNavigationBarAppearance = navigationBarAppearance |
||||
|
|
||||
|
var toolbarAppearance = ToolbarAppearance(toolbar: navigationController.toolbar) |
||||
|
toolbarAppearance.isHidden = navigationController.isToolbarHidden |
||||
|
previousNavigationControllerToolbarAppearance = toolbarAppearance |
||||
|
} |
||||
|
|
||||
|
fileprivate func restorePreviousNavigationControllerState(animated: Bool) { |
||||
|
guard let navigationController = navigationController else { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
navigationController.setNavigationBarHidden(previousNavigationControllerNavigationBarAppearance.isHidden, animated: animated) |
||||
|
navigationController.setToolbarHidden(previousNavigationControllerToolbarAppearance.isHidden, animated: animated) |
||||
|
|
||||
|
previousNavigationControllerNavigationBarAppearance.apply(to: navigationController.navigationBar) |
||||
|
previousNavigationControllerToolbarAppearance.apply(to: navigationController.toolbar) |
||||
|
} |
||||
|
|
||||
|
fileprivate func updateTintColor() { |
||||
|
progressView.tintColor = tintColor |
||||
|
navigationController?.navigationBar.tintColor = tintColor |
||||
|
navigationController?.toolbar.tintColor = tintColor |
||||
|
} |
||||
|
|
||||
|
fileprivate func updateBarTintColor() { |
||||
|
navigationController?.navigationBar.barTintColor = barTintColor |
||||
|
navigationController?.toolbar.barTintColor = barTintColor |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
extension WebBrowserViewController { |
||||
|
// MARK: - UIBarButtonItem actions |
||||
|
@objc func refreshButtonTapped(_ sender: UIBarButtonItem) { |
||||
|
webView.stopLoading() |
||||
|
webView.reload() |
||||
|
} |
||||
|
|
||||
|
@objc func stopButtonTapped(_ sender: UIBarButtonItem) { |
||||
|
webView.stopLoading() |
||||
|
} |
||||
|
|
||||
|
@objc func backButtonTapped(_ sender: UIBarButtonItem) { |
||||
|
webView.goBack() |
||||
|
updateToolBarState() |
||||
|
} |
||||
|
|
||||
|
@objc func forwardButtonTapped(_ sender: UIBarButtonItem) { |
||||
|
webView.goForward() |
||||
|
updateToolBarState() |
||||
|
} |
||||
|
|
||||
|
@objc func actionButtonTapped(_ sender: UIBarButtonItem) { |
||||
|
DispatchQueue.main.async { |
||||
|
var activityItems = [Any]() |
||||
|
if let url = self.webView.url { |
||||
|
activityItems.append(url) |
||||
|
} |
||||
|
var applicationActivities = [UIActivity]() |
||||
|
applicationActivities.append(SafariActivity()) |
||||
|
applicationActivities.append(contentsOf: self.customApplicationActivities) |
||||
|
|
||||
|
let activityViewController = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities) |
||||
|
activityViewController.view.tintColor = self.tintColor |
||||
|
|
||||
|
if UIDevice.current.userInterfaceIdiom == .pad { |
||||
|
activityViewController.popoverPresentationController?.barButtonItem = sender |
||||
|
activityViewController.popoverPresentationController?.permittedArrowDirections = .any |
||||
|
self.present(activityViewController, animated: true, completion: nil) |
||||
|
} else { |
||||
|
self.present(activityViewController, animated: true, completion: nil) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@objc func doneButtonTapped(_ sender: UIBarButtonItem) { |
||||
|
delegate?.webBrowserWillDismiss(self) |
||||
|
dismiss(animated: true) { |
||||
|
self.delegate?.webBrowserDidDismiss(self) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
extension WebBrowserViewController { |
||||
|
// MARK: - Tool bar |
||||
|
fileprivate func updateToolBarState() { |
||||
|
backButton.isEnabled = webView.canGoBack |
||||
|
forwardButton.isEnabled = webView.canGoForward |
||||
|
|
||||
|
var barButtonItems = [UIBarButtonItem]() |
||||
|
if webView.isLoading { |
||||
|
barButtonItems = [backButton, itemFixedSeparator, forwardButton, itemFixedSeparator, stopButton, itemFlexibleSeparator] |
||||
|
if let urlString = webView.url?.absoluteString, isShowURLInNavigationBarWhenLoading { |
||||
|
var titleString = urlString.replacingOccurrences(of: "http://", with: "", options: .literal, range: nil) |
||||
|
titleString = titleString.replacingOccurrences(of: "https://", with: "", options: .literal, range: nil) |
||||
|
navigationItem.title = titleString |
||||
|
} |
||||
|
} else { |
||||
|
barButtonItems = [backButton, itemFixedSeparator, forwardButton, itemFixedSeparator, refreshButton, itemFlexibleSeparator] |
||||
|
if isShowPageTitleInNavigationBar { |
||||
|
navigationItem.title = webView.title |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if isShowActionBarButton { |
||||
|
barButtonItems.append(actionButton) |
||||
|
} |
||||
|
|
||||
|
setToolbarItems(barButtonItems, animated: true) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
extension WebBrowserViewController { |
||||
|
// MARK: - External app support |
||||
|
fileprivate func externalAppRequiredToOpen(_ url: URL) -> Bool { |
||||
|
let validSchemes: Set<String> = ["http", "https"] |
||||
|
if let urlScheme = url.scheme { |
||||
|
return !validSchemes.contains(urlScheme) |
||||
|
} else { |
||||
|
return false |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fileprivate func openExternalApp(with url: URL) { |
||||
|
let externalAppPermissionAlert = UIAlertController(title: LocalizedString(key: "OpenExternalAppAlert.title"), message: LocalizedString(key: "OpenExternalAppAlert.message"), preferredStyle: .alert) |
||||
|
let cancelAction = UIAlertAction(title: LocalizedString(key: "Cancel"), style: .cancel, handler: { [weak self] (action) in |
||||
|
self?.onOpenExternalAppHandler?(false) |
||||
|
}) |
||||
|
let openAction = UIAlertAction(title: LocalizedString(key: "Open"), style: .default) { [weak self] (action) in |
||||
|
UIApplication.shared.openURL(url) |
||||
|
self?.onOpenExternalAppHandler?(true) |
||||
|
} |
||||
|
externalAppPermissionAlert.addAction(cancelAction) |
||||
|
externalAppPermissionAlert.addAction(openAction) |
||||
|
present(externalAppPermissionAlert, animated: true, completion: nil) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
extension WebBrowserViewController { |
||||
|
// MARK: - Observer |
||||
|
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { |
||||
|
if let keyPath = keyPath, (keyPath == WebBrowser.estimatedProgressKeyPath && context == &WebBrowser.estimatedProgressContext) { |
||||
|
progressView.alpha = 1 |
||||
|
let animated = webView.estimatedProgress > Double(progressView.progress) |
||||
|
progressView.setProgress(Float(webView.estimatedProgress), animated: animated) |
||||
|
|
||||
|
if webView.estimatedProgress >= 1 { |
||||
|
UIView.animate(withDuration: 0.3, delay: 0.3, options: .curveEaseOut, animations: { |
||||
|
self.progressView.alpha = 0 |
||||
|
}, completion: { (finished) in |
||||
|
self.progressView.progress = 0 |
||||
|
}) |
||||
|
} |
||||
|
} else { |
||||
|
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
extension WebBrowserViewController: WKNavigationDelegate { |
||||
|
// MARK: - WKNavigationDelegate |
||||
|
public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { |
||||
|
updateToolBarState() |
||||
|
delegate?.webBrowser(self, didStartLoad: webView.url) |
||||
|
} |
||||
|
|
||||
|
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { |
||||
|
updateToolBarState() |
||||
|
delegate?.webBrowser(self, didFinishLoad: webView.url) |
||||
|
} |
||||
|
|
||||
|
public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { |
||||
|
updateToolBarState() |
||||
|
delegate?.webBrowser(self, didFailLoad: webView.url, withError: error) |
||||
|
} |
||||
|
|
||||
|
public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { |
||||
|
updateToolBarState() |
||||
|
delegate?.webBrowser(self, didFailLoad: webView.url, withError: error) |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { |
||||
|
if let oriDelegate = delegate, oriDelegate.webBrowser(self, decidePolicyFor: navigationAction, decisionHandler: decisionHandler) { |
||||
|
return |
||||
|
} |
||||
|
if let url = navigationAction.request.url { |
||||
|
if !externalAppRequiredToOpen(url) { |
||||
|
if navigationAction.targetFrame == nil { |
||||
|
loadURL(url) |
||||
|
decisionHandler(.cancel) |
||||
|
return |
||||
|
} |
||||
|
} else if UIApplication.shared.canOpenURL(url) { |
||||
|
openExternalApp(with: url) |
||||
|
decisionHandler(.cancel) |
||||
|
return |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
decisionHandler(.allow) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
extension WebBrowserViewController: WKUIDelegate { |
||||
|
// MARK: - WKUIDelegate |
||||
|
public func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { |
||||
|
if let mainFrame = navigationAction.targetFrame?.isMainFrame, mainFrame == false { |
||||
|
webView.load(navigationAction.request) |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue