iosWebViewFix/ios/Classes/InAppBrowserWebViewControll...

484 lines
22 KiB
Swift
Executable File

//
// InAppBrowserWebViewController.swift
// flutter_inappwebview
//
// Created by Lorenzo on 17/09/18.
//
import Flutter
import UIKit
import WebKit
import Foundation
import AVFoundation
typealias OlderClosureType = @convention(c) (Any, Selector, UnsafeRawPointer, Bool, Bool, Any?) -> Void
typealias NewerClosureType = @convention(c) (Any, Selector, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Void
//extension WKWebView{
//
// var keyboardDisplayRequiresUserAction: Bool? {
// get {
// return self.keyboardDisplayRequiresUserAction
// }
// set {
// self.setKeyboardRequiresUserInteraction(newValue ?? true)
// }
// }
//
// func setKeyboardRequiresUserInteraction( _ value: Bool) {
//
// guard
// let WKContentViewClass: AnyClass = NSClassFromString("WKContentView") else {
// print("Cannot find the WKContentView class")
// return
// }
//
// let olderSelector: Selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:")
// let newerSelector: Selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:")
//
// if let method = class_getInstanceMethod(WKContentViewClass, olderSelector) {
//
// let originalImp: IMP = method_getImplementation(method)
// let original: OlderClosureType = unsafeBitCast(originalImp, to: OlderClosureType.self)
// let block : @convention(block) (Any, UnsafeRawPointer, Bool, Bool, Any?) -> Void = { (me, arg0, arg1, arg2, arg3) in
// original(me, olderSelector, arg0, !value, arg2, arg3)
// }
// let imp: IMP = imp_implementationWithBlock(block)
// method_setImplementation(method, imp)
// }
//
// if let method = class_getInstanceMethod(WKContentViewClass, newerSelector) {
//
// let originalImp: IMP = method_getImplementation(method)
// let original: NewerClosureType = unsafeBitCast(originalImp, to: NewerClosureType.self)
// let block : @convention(block) (Any, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Void = { (me, arg0, arg1, arg2, arg3, arg4) in
// original(me, newerSelector, arg0, !value, arg2, arg3, arg4)
// }
// let imp: IMP = imp_implementationWithBlock(block)
// method_setImplementation(method, imp)
// }
//
// }
//
//}
class InAppWebView_IBWrapper: InAppWebView {
required init(coder: NSCoder) {
let config = WKWebViewConfiguration()
super.init(frame: .zero, configuration: config, IABController: nil, channel: nil)
self.translatesAutoresizingMaskIntoConstraints = false
}
}
class InAppBrowserWebViewController: UIViewController, UIScrollViewDelegate, WKUIDelegate, UITextFieldDelegate {
@IBOutlet var containerWebView: UIView!
@IBOutlet var closeButton: UIButton!
@IBOutlet var reloadButton: UIBarButtonItem!
@IBOutlet var backButton: UIBarButtonItem!
@IBOutlet var forwardButton: UIBarButtonItem!
@IBOutlet var shareButton: UIBarButtonItem!
@IBOutlet var spinner: UIActivityIndicatorView!
@IBOutlet var toolbarTop: UIView!
@IBOutlet var toolbarBottom: UIToolbar!
@IBOutlet var urlField: UITextField!
@IBOutlet var toolbarTop_BottomToWebViewTopConstraint: NSLayoutConstraint!
@IBOutlet var toolbarBottom_TopToWebViewBottomConstraint: NSLayoutConstraint!
@IBOutlet var containerWebView_BottomFullScreenConstraint: NSLayoutConstraint!
@IBOutlet var containerWebView_TopFullScreenConstraint: NSLayoutConstraint!
@IBOutlet var webView_BottomFullScreenConstraint: NSLayoutConstraint!
@IBOutlet var webView_TopFullScreenConstraint: NSLayoutConstraint!
var webView: InAppWebView!
weak var navigationDelegate: SwiftFlutterPlugin?
var initURL: URL?
var tmpWindow: UIWindow?
var browserOptions: InAppBrowserOptions?
var webViewOptions: InAppWebViewOptions?
var initHeaders: [String: String]?
var initData: String?
var initMimeType: String?
var initEncoding: String?
var initBaseUrl: String?
var isHidden = false
var uuid: String = ""
var WKNavigationMap: [String: [String: Any]] = [:]
var startPageTime: Int64 = 0
var viewPrepared = false
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override func viewWillAppear(_ animated: Bool) {
if !viewPrepared {
let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(options: webViewOptions)
self.webView = InAppWebView(frame: .zero, configuration: preWebviewConfiguration, IABController: self, channel: nil)
self.containerWebView.addSubview(self.webView)
prepareConstraints()
prepareWebView()
if #available(iOS 11.0, *) {
if let contentBlockers = webView!.options?.contentBlockers, contentBlockers.count > 0 {
do {
let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: [])
let blockRules = String(data: jsonData, encoding: String.Encoding.utf8)
WKContentRuleListStore.default().compileContentRuleList(
forIdentifier: "ContentBlockingRules",
encodedContentRuleList: blockRules) { (contentRuleList, error) in
if let error = error {
print(error.localizedDescription)
return
}
let configuration = self.webView!.configuration
configuration.userContentController.add(contentRuleList!)
self.initLoad(initURL: self.initURL, initData: self.initData, initMimeType: self.initMimeType, initEncoding: self.initEncoding, initBaseUrl: self.initBaseUrl, initHeaders: self.initHeaders)
self.navigationDelegate?.onBrowserCreated(uuid: self.uuid, webView: self.webView)
}
return
} catch {
print(error.localizedDescription)
}
}
}
initLoad(initURL: initURL, initData: initData, initMimeType: initMimeType, initEncoding: initEncoding, initBaseUrl: initBaseUrl, initHeaders: initHeaders)
navigationDelegate?.onBrowserCreated(uuid: uuid, webView: webView)
}
viewPrepared = true
super.viewWillAppear(animated)
}
func initLoad(initURL: URL?, initData: String?, initMimeType: String?, initEncoding: String?, initBaseUrl: String?, initHeaders: [String: String]?) {
if self.initData == nil {
loadUrl(url: self.initURL!, headers: self.initHeaders)
}
else {
webView.loadData(data: initData!, mimeType: initMimeType!, encoding: initEncoding!, baseUrl: initBaseUrl!)
}
}
override func viewDidLoad() {
super.viewDidLoad()
urlField.delegate = self
urlField.text = self.initURL?.absoluteString
closeButton.addTarget(self, action: #selector(self.close), for: .touchUpInside)
forwardButton.target = self
forwardButton.action = #selector(self.goForward)
forwardButton.target = self
forwardButton.action = #selector(self.goForward)
backButton.target = self
backButton.action = #selector(self.goBack)
reloadButton.target = self
reloadButton.action = #selector(self.reload)
shareButton.target = self
shareButton.action = #selector(self.share)
spinner.hidesWhenStopped = true
spinner.isHidden = false
spinner.stopAnimating()
}
// Prevent crashes on closing windows
deinit {
print("InAppBrowserWebViewController - dealloc")
}
override func viewWillDisappear (_ animated: Bool) {
super.viewWillDisappear(animated)
webView.dispose()
navigationDelegate = nil
transitioningDelegate = nil
urlField.delegate = nil
closeButton.removeTarget(self, action: #selector(self.close), for: .touchUpInside)
forwardButton.target = nil
forwardButton.target = nil
backButton.target = nil
reloadButton.target = nil
shareButton.target = nil
}
func prepareConstraints () {
containerWebView_BottomFullScreenConstraint = NSLayoutConstraint(item: self.containerWebView, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1, constant: 0)
containerWebView_TopFullScreenConstraint = NSLayoutConstraint(item: self.containerWebView, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.view, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 0)
webView.translatesAutoresizingMaskIntoConstraints = false
let height = NSLayoutConstraint(item: webView, attribute: .height, relatedBy: .equal, toItem: containerWebView, attribute: .height, multiplier: 1, constant: 0)
let width = NSLayoutConstraint(item: webView, attribute: .width, relatedBy: .equal, toItem: containerWebView, attribute: .width, multiplier: 1, constant: 0)
let leftConstraint = NSLayoutConstraint(item: webView, attribute: .leftMargin, relatedBy: .equal, toItem: containerWebView, attribute: .leftMargin, multiplier: 1, constant: 0)
let rightConstraint = NSLayoutConstraint(item: webView, attribute: .rightMargin, relatedBy: .equal, toItem: containerWebView, attribute: .rightMargin, multiplier: 1, constant: 0)
let bottomContraint = NSLayoutConstraint(item: webView, attribute: .bottomMargin, relatedBy: .equal, toItem: containerWebView, attribute: .bottomMargin, multiplier: 1, constant: 0)
containerWebView.addConstraints([height, width, leftConstraint, rightConstraint, bottomContraint])
webView_BottomFullScreenConstraint = NSLayoutConstraint(item: self.webView, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.containerWebView, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1, constant: 0)
webView_TopFullScreenConstraint = NSLayoutConstraint(item: self.webView, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.containerWebView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 0)
}
func prepareWebView() {
//UIApplication.shared.statusBarStyle = preferredStatusBarStyle
self.webView.options = webViewOptions
self.webView.prepare()
if (browserOptions?.hideUrlBar)! {
self.urlField.isHidden = true
self.urlField.isEnabled = false
}
if (browserOptions?.toolbarTop)! {
if browserOptions?.toolbarTopBackgroundColor != "" {
self.toolbarTop.backgroundColor = color(fromHexString: (browserOptions?.toolbarTopBackgroundColor)!)
}
}
else {
self.toolbarTop.isHidden = true
self.toolbarTop_BottomToWebViewTopConstraint.isActive = false
self.containerWebView_TopFullScreenConstraint.isActive = true
self.webView_TopFullScreenConstraint.isActive = true
}
if (browserOptions?.toolbarBottom)! {
if browserOptions?.toolbarBottomBackgroundColor != "" {
self.toolbarBottom.backgroundColor = color(fromHexString: (browserOptions?.toolbarBottomBackgroundColor)!)
}
self.toolbarBottom.isTranslucent = (browserOptions?.toolbarBottomTranslucent)!
}
else {
self.toolbarBottom.isHidden = true
self.toolbarBottom_TopToWebViewBottomConstraint.isActive = false
self.containerWebView_BottomFullScreenConstraint.isActive = true
self.webView_BottomFullScreenConstraint.isActive = true
}
if browserOptions?.closeButtonCaption != "" {
closeButton.setTitle(browserOptions?.closeButtonCaption, for: .normal)
}
if browserOptions?.closeButtonColor != "" {
closeButton.tintColor = color(fromHexString: (browserOptions?.closeButtonColor)!)
}
self.modalPresentationStyle = UIModalPresentationStyle(rawValue: (browserOptions?.presentationStyle)!)!
self.modalTransitionStyle = UIModalTransitionStyle(rawValue: (browserOptions?.transitionStyle)!)!
}
func loadUrl(url: URL, headers: [String: String]?) {
webView.loadUrl(url: url, headers: headers)
updateUrlTextField(url: (webView.currentURL?.absoluteString)!)
}
// Load user requested url
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
if textField.text != nil && textField.text != "" {
let url = textField.text?.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
let request = URLRequest(url: URL(string: url!)!)
webView.load(request)
}
else {
updateUrlTextField(url: (webView.currentURL?.absoluteString)!)
}
return false
}
func setWebViewFrame(_ frame: CGRect) {
print("Setting the WebView's frame to \(NSCoder.string(for: frame))")
webView.frame = frame
}
@objc func reload () {
webView.reload()
}
@objc func share () {
let vc = UIActivityViewController(activityItems: [webView.currentURL ?? ""], applicationActivities: [])
present(vc, animated: true, completion: nil)
}
@objc func close() {
//currentURL = nil
weak var weakSelf = self
if (weakSelf?.responds(to: #selector(getter: self.presentingViewController)))! {
weakSelf?.presentingViewController?.dismiss(animated: true, completion: {() -> Void in
self.tmpWindow?.windowLevel = UIWindow.Level(rawValue: 0.0)
UIApplication.shared.delegate?.window??.makeKeyAndVisible()
})
}
else {
weakSelf?.parent?.dismiss(animated: true, completion: {() -> Void in
self.tmpWindow?.windowLevel = UIWindow.Level(rawValue: 0.0)
UIApplication.shared.delegate?.window??.makeKeyAndVisible()
})
}
if (self.navigationDelegate != nil) {
self.navigationDelegate?.browserExit(uuid: self.uuid)
}
}
@objc func goBack() {
if canGoBack() {
webView.goBack()
updateUrlTextField(url: (webView?.url?.absoluteString)!)
}
}
func canGoBack() -> Bool {
return webView.canGoBack
}
@objc func goForward() {
if canGoForward() {
webView.goForward()
updateUrlTextField(url: (webView?.url?.absoluteString)!)
}
}
func canGoForward() -> Bool {
return webView.canGoForward
}
@objc func goBackOrForward(steps: Int) {
webView.goBackOrForward(steps: steps)
updateUrlTextField(url: (webView?.url?.absoluteString)!)
}
func canGoBackOrForward(steps: Int) -> Bool {
return webView.canGoBackOrForward(steps: steps)
}
func updateUrlTextField(url: String) {
urlField.text = url
}
//
// On iOS 7 the status bar is part of the view's dimensions, therefore it's height has to be taken into account.
// The height of it could be hardcoded as 20 pixels, but that would assume that the upcoming releases of iOS won't
// change that value.
//
func getStatusBarOffset() -> Float {
let statusBarFrame: CGRect = UIApplication.shared.statusBarFrame
let statusBarOffset: Float = Float(min(statusBarFrame.size.width, statusBarFrame.size.height))
return statusBarOffset
}
// Helper function to convert hex color string to UIColor
// Assumes input like "#00FF00" (#RRGGBB).
// Taken from https://stackoverflow.com/questions/1560081/how-can-i-create-a-uicolor-from-a-hex-string
func color(fromHexString: String, alpha:CGFloat? = 1.0) -> UIColor {
// Convert hex string to an integer
let hexint = Int(self.intFromHexString(hexStr: fromHexString))
let red = CGFloat((hexint & 0xff0000) >> 16) / 255.0
let green = CGFloat((hexint & 0xff00) >> 8) / 255.0
let blue = CGFloat((hexint & 0xff) >> 0) / 255.0
let alpha = alpha!
// Create color object, specifying alpha as well
let color = UIColor(red: red, green: green, blue: blue, alpha: alpha)
return color
}
func intFromHexString(hexStr: String) -> UInt32 {
var hexInt: UInt32 = 0
// Create scanner
let scanner: Scanner = Scanner(string: hexStr)
// Tell scanner to skip the # character
scanner.charactersToBeSkipped = CharacterSet(charactersIn: "#")
// Scan hex value
scanner.scanHexInt32(&hexInt)
return hexInt
}
func setOptions(newOptions: InAppBrowserOptions, newOptionsMap: [String: Any]) {
let newInAppWebViewOptions = InAppWebViewOptions()
newInAppWebViewOptions.parse(options: newOptionsMap)
self.webView.setOptions(newOptions: newInAppWebViewOptions, newOptionsMap: newOptionsMap)
if newOptionsMap["hidden"] != nil && browserOptions?.hidden != newOptions.hidden {
if newOptions.hidden {
self.navigationDelegate?.hide(uuid: self.uuid)
}
else {
self.navigationDelegate?.show(uuid: self.uuid)
}
}
if newOptionsMap["hideUrlBar"] != nil && browserOptions?.hideUrlBar != newOptions.hideUrlBar {
self.urlField.isHidden = newOptions.hideUrlBar
self.urlField.isEnabled = !newOptions.hideUrlBar
}
if newOptionsMap["toolbarTop"] != nil && browserOptions?.toolbarTop != newOptions.toolbarTop {
self.containerWebView_TopFullScreenConstraint.isActive = !newOptions.toolbarTop
self.webView_TopFullScreenConstraint.isActive = !newOptions.toolbarTop
self.toolbarTop.isHidden = !newOptions.toolbarTop
self.toolbarTop_BottomToWebViewTopConstraint.isActive = newOptions.toolbarTop
}
if newOptionsMap["toolbarTopBackgroundColor"] != nil && browserOptions?.toolbarTopBackgroundColor != newOptions.toolbarTopBackgroundColor && newOptions.toolbarTopBackgroundColor != "" {
self.toolbarTop.backgroundColor = color(fromHexString: newOptions.toolbarTopBackgroundColor)
}
if newOptionsMap["toolbarBottom"] != nil && browserOptions?.toolbarBottom != newOptions.toolbarBottom {
self.containerWebView_BottomFullScreenConstraint.isActive = !newOptions.toolbarBottom
self.webView_BottomFullScreenConstraint.isActive = !newOptions.toolbarBottom
self.toolbarBottom.isHidden = !newOptions.toolbarBottom
self.toolbarBottom_TopToWebViewBottomConstraint.isActive = newOptions.toolbarBottom
}
if newOptionsMap["toolbarBottomBackgroundColor"] != nil && browserOptions?.toolbarBottomBackgroundColor != newOptions.toolbarBottomBackgroundColor && newOptions.toolbarBottomBackgroundColor != "" {
self.toolbarBottom.backgroundColor = color(fromHexString: newOptions.toolbarBottomBackgroundColor)
}
if newOptionsMap["toolbarBottomTranslucent"] != nil && browserOptions?.toolbarBottomTranslucent != newOptions.toolbarBottomTranslucent {
self.toolbarBottom.isTranslucent = newOptions.toolbarBottomTranslucent
}
if newOptionsMap["closeButtonCaption"] != nil && browserOptions?.closeButtonCaption != newOptions.closeButtonCaption && newOptions.closeButtonCaption != "" {
closeButton.setTitle(newOptions.closeButtonCaption, for: .normal)
}
if newOptionsMap["closeButtonColor"] != nil && browserOptions?.closeButtonColor != newOptions.closeButtonColor && newOptions.closeButtonColor != "" {
closeButton.tintColor = color(fromHexString: newOptions.closeButtonColor)
}
if newOptionsMap["presentationStyle"] != nil && browserOptions?.presentationStyle != newOptions.presentationStyle {
self.modalPresentationStyle = UIModalPresentationStyle(rawValue: newOptions.presentationStyle)!
}
if newOptionsMap["transitionStyle"] != nil && browserOptions?.transitionStyle != newOptions.transitionStyle {
self.modalTransitionStyle = UIModalTransitionStyle(rawValue: newOptions.transitionStyle)!
}
self.browserOptions = newOptions
self.webViewOptions = newInAppWebViewOptions
}
func getOptions() -> [String: Any]? {
if (self.browserOptions == nil || self.webView.getOptions() == nil) {
return nil
}
var optionsMap = self.browserOptions!.getHashMap()
optionsMap.merge(self.webView.getOptions()!, uniquingKeysWith: { (current, _) in current })
return optionsMap
}
}