feat: custom webview api

This commit is contained in:
Nikita Semenov 2022-12-26 10:36:58 +03:00
parent 6e442ce37f
commit d12e07484d
18 changed files with 785 additions and 0 deletions

View File

@ -11,6 +11,7 @@ let package = Package(
// MARK: - UIKit
.library(name: "TIUIKitCore", targets: ["TIUIKitCore"]),
.library(name: "TIUIElements", targets: ["TIUIElements"]),
.library(name: "TIWebView", targets: ["TIWebView"]),
// MARK: - SwiftUI
.library(name: "TISwiftUICore", targets: ["TISwiftUICore"]),
@ -55,6 +56,7 @@ let package = Package(
// MARK: - UIKit
.target(name: "TIUIKitCore", dependencies: ["TISwiftUtils"], path: "TIUIKitCore/Sources"),
.target(name: "TIUIElements", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIUIElements/Sources"),
.target(name: "TIWebView", dependencies: ["TIUIKitCore", "TISwiftUtils", "TILogging"], path: "TIWebView/Sources"),
// MARK: - SwiftUI
.target(name: "TISwiftUICore", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TISwiftUICore/Sources"),

View File

@ -0,0 +1,36 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TILogging
open class BaseWebViewErrorHandler: WebViewErrorHandlerProtocol {
let logger: TILogger?
public init(logger: TILogger? = nil) {
self.logger = logger
}
open func didRecievedError(_ error: WebViewErrorModel) {
logger?.error("%@", "\(error)")
}
}

View File

@ -0,0 +1,31 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public enum WebViewErrorConstants {
static var errorMessageName: String {
"error"
}
static var errorPropertyName: String {
"message"
}
}

View File

@ -0,0 +1,25 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public protocol WebViewErrorHandlerProtocol {
func didRecievedError(_ error: WebViewErrorModel)
}

View File

@ -0,0 +1,38 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
public struct WebViewErrorModel {
public let url: URL?
public let error: Error?
public let jsErrorMessage: String?
public init(url: URL? = nil,
error: Error? = nil,
jsErrorMessage: String? = nil) {
self.url = url
self.error = error
self.jsErrorMessage = jsErrorMessage
}
}

View File

@ -0,0 +1,60 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import enum WebKit.WKNavigationActionPolicy
open class BaseWebViewNavigator: WebViewNavigatorProtocol {
public typealias WebViewNavigationMap = [WebViewUrlComparator: NavigationResult]
public let navigationMap: WebViewNavigationMap
public init(navigationMap: WebViewNavigationMap) {
self.navigationMap = navigationMap
}
public convenience init() {
self.init(navigationMap: [:])
}
open func shouldNavigate(toUrl url: URL) -> WKNavigationActionPolicy {
guard !navigationMap.isEmpty else {
return .cancel
}
let decision = navigationMap.first { (comparator, _) in
url.compare(by: comparator)
}?.value
switch decision {
case let .closure(closure):
return closure(url)
case let .simpleResult(result):
return result
case .none:
return .cancel
}
}
}

View File

@ -0,0 +1,30 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import TISwiftUtils
import enum WebKit.WKNavigationActionPolicy
public enum NavigationResult {
case closure(Closure<URL, WKNavigationActionPolicy>)
case simpleResult(WKNavigationActionPolicy)
}

View File

@ -0,0 +1,49 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import WebKit
public protocol WebViewNavigationDelegate {
func navigationProgress(_ progress: Double, isLoading: Bool)
func didCommit(_ navigation: WKNavigation, forWebView webView: WKWebView)
func didFinish(_ navigation: WKNavigation!, forWebView webView: WKWebView)
func didFailProvisionalNavigation(_ navigation: WKNavigation!, withError error: Error, forWebView webView: WKWebView)
func didFail(_ navigation: WKNavigation!, withError error: Error, forWebView webView: WKWebView)
}
public extension WebViewNavigationDelegate {
func navigationProgress(_ progress: Double, isLoading: Bool) {
// empty implementation
}
func didCommit(_ navigation: WKNavigation, forWebView webView: WKWebView) {
// empty implementation
}
func didFinish(_ navigation: WKNavigation!, forWebView webView: WKWebView) {
// empty implementation
}
func didFail(_ navigation: WKNavigation!, withError error: Error, forWebView webView: WKWebView) {
// empty implementation
}
}

View File

@ -0,0 +1,28 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import enum WebKit.WKNavigationActionPolicy
public protocol WebViewNavigatorProtocol {
func shouldNavigate(toUrl url: URL) -> WKNavigationActionPolicy
}

View File

@ -0,0 +1,92 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
import class WebKit.WKWebView
open class BaseWebViewUrlInjector: WebViewUrlInjectorProtocol {
public typealias URLInjection = [WebViewUrlComparator: [WebViewUrlInjection]]
public var injection: URLInjection
public init(injection: URLInjection) {
self.injection = injection
}
public convenience init() {
self.init(injection: [:])
}
open func inject(onWebView webView: WKWebView) {
guard !injection.isEmpty, let url = webView.url else {
return
}
injection.forEach { (comparator, injections) in
guard url.compare(by: comparator) else {
return
}
injections.forEach { evaluteInjection(onWebView: webView, injection: $0) }
}
}
private func evaluteInjection(onWebView webView: WKWebView, injection: WebViewUrlInjection) {
let jsScript = makeJsScript(fromInjection: injection)
guard !jsScript.isEmpty else {
return
}
webView.evaluateJavaScript(jsScript, completionHandler: nil)
}
private func makeJsScript(fromInjection injection: WebViewUrlInjection) -> String {
switch injection {
case let .css(css):
return cssJsScript(css: css)
case let .cssForFile(file):
guard let path = Bundle.main.path(forResource: file, ofType: "css") else {
return ""
}
let css = try? String(contentsOfFile: path)
.components(separatedBy: .newlines)
.joined()
return cssJsScript(css: css ?? "")
case let .javaScript(script):
return script
}
}
private func cssJsScript(css: String) -> String {
"""
var style = document.createElement('style');
style.innerHTML = '\(css)';
document.head.appendChild(style);
"""
}
}

View File

@ -0,0 +1,50 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
public extension URL {
func compare(by comparator: WebViewUrlComparator) -> Bool {
switch comparator {
case .any:
return true
case let .absolutePath(path):
return absoluteString == path
case let .host(host):
return self.host == host
case let .query(query):
return (self.query ?? "").contains(query)
case let .regex(stringRegex):
guard let regex = try? NSRegularExpression(pattern: stringRegex) else {
return false
}
let range = NSRange(location: 0, length: absoluteString.utf16.count)
return regex.firstMatch(in: absoluteString, range: range) != nil
}
}
}

View File

@ -0,0 +1,29 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public enum WebViewUrlComparator: Hashable {
case any
case absolutePath(String)
case host(String)
case query(String)
case regex(String)
}

View File

@ -0,0 +1,27 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public enum WebViewUrlInjection {
case css(String)
case cssForFile(String)
case javaScript(String)
}

View File

@ -0,0 +1,27 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import class WebKit.WKWebView
public protocol WebViewUrlInjectorProtocol {
func inject(onWebView webView: WKWebView)
}

View File

@ -0,0 +1,132 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIUIKitCore
import WebKit
open class BaseInitializableWebView: WKWebView,
InitializableViewProtocol,
ConfigurableView,
WKNavigationDelegate {
public var viewModel: WebViewModelProtocol?
public var delegate: WebViewNavigationDelegate?
// MARK: - Init
public init() {
super.init(frame: .zero, configuration: .init())
initializeView()
}
@available(*, unavailable)
public required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - InitializableViewProtocol
public func addViews() {
// override in subviews
}
public func configureLayout() {
// override in subviews
}
public func bindViews() {
navigationDelegate = self
configuration.preferences.javaScriptEnabled = true
addObserver(self,
forKeyPath: #keyPath(WKWebView.estimatedProgress),
options: .new,
context: nil)
}
public func configureAppearance() {
// override in subviews
}
public func localize() {
// override in subviews
}
// MARK: - Overrided methods
open override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
if keyPath == #keyPath(WKWebView.estimatedProgress) {
delegate?.navigationProgress(estimatedProgress, isLoading: isLoading)
}
}
// MARK: - ConfigurableView
open func configure(with viewModel: WebViewModelProtocol) {
self.viewModel = viewModel
configuration.userContentController.add(viewModel, name: WebViewErrorConstants.errorMessageName)
}
// MARK: - WKNavigationDelegate
open func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
guard let url = navigationAction.request.url else {
return decisionHandler(.cancel)
}
let decision = viewModel?.shouldNavigate(toUrl: url) ?? .cancel
decisionHandler(decision)
}
open func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
delegate?.didCommit(navigation, forWebView: webView)
}
open func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
delegate?.didFinish(navigation, forWebView: webView)
viewModel?.makeUrlInjection(forWebView: webView)
}
open func webView(_ webView: WKWebView,
didFailProvisionalNavigation navigation: WKNavigation!,
withError error: Error) {
viewModel?.handleError(error, url: webView.url)
delegate?.didFailProvisionalNavigation(navigation, withError: error, forWebView: webView)
}
open func webView(_ webView: WKWebView,
didFail navigation: WKNavigation!,
withError error: Error) {
viewModel?.handleError(error, url: webView.url)
delegate?.didFail(navigation, withError: error, forWebView: webView)
}
}

View File

@ -0,0 +1,77 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import WebKit
open class BaseWebViewModel: NSObject, WebViewModelProtocol {
public var injector: WebViewUrlInjectorProtocol
public var navigator: WebViewNavigatorProtocol
public var errorHandler: WebViewErrorHandlerProtocol
// MARK: - Init
public init(injector: WebViewUrlInjectorProtocol = BaseWebViewUrlInjector(),
navigator: WebViewNavigatorProtocol = BaseWebViewNavigator(),
errorHandler: WebViewErrorHandlerProtocol = BaseWebViewErrorHandler()) {
self.injector = injector
self.navigator = navigator
self.errorHandler = errorHandler
super.init()
}
// MARK: - Open methods
open func makeUrlInjection(forWebView webView: WKWebView) {
injector.inject(onWebView: webView)
}
open func shouldNavigate(toUrl url: URL) -> WKNavigationActionPolicy {
navigator.shouldNavigate(toUrl: url)
}
open func handleError(_ error: Error, url: URL?) {
errorHandler.didRecievedError(.init(url: url, error: error))
}
// MARK: - WKScriptMessageHandler
open func userContentController(_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage) {
if message.name == WebViewErrorConstants.errorMessageName,
let error = parseError(message){
let url = message.webView?.url
errorHandler.didRecievedError(.init(url: url, jsErrorMessage: error))
}
}
// MARK: - Private methods
private func parseError(_ message: WKScriptMessage) -> String? {
let body = message.body as? [String: Any]
let error = body?[WebViewErrorConstants.errorPropertyName] as? String
return error
}
}

View File

@ -0,0 +1,32 @@
//
// Copyright (c) 2020 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import WebKit
public protocol WebViewModelProtocol: WKScriptMessageHandler {
var injector: WebViewUrlInjectorProtocol { get }
var navigator: WebViewNavigatorProtocol { get }
func makeUrlInjection(forWebView webView: WKWebView)
func shouldNavigate(toUrl url: URL) -> WKNavigationActionPolicy
func handleError(_ error: Error, url: URL?)
}

View File

@ -0,0 +1,20 @@
Pod::Spec.new do |s|
s.name = 'TIWebView'
s.version = '1.30.0'
s.summary = 'Universal web view API'
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'petropavel13' => 'ivan.smolin@touchin.ru',
'castlele' => 'nikita.semenov@touchin.ru' }
s.source = { :git => 'https://github.com/TouchInstinct/LeadKit.git', :tag => s.version.to_s }
s.ios.deployment_target = '13.0'
s.swift_versions = ['5.3']
s.source_files = s.name + '/Sources/**/*'
s.dependency 'TIUIKitCore', s.version.to_s
s.dependency 'TISwiftUtils', s.version.to_s
s.dependency 'TILogging', s.version.to_s
end