-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[CHNL-11919] create native view to show web view (#212)
* added KlaviyoWebViewController.swift * added FileIO.swift implemented `FileIO` removed `internal` access control fixed `getFileContents(...)` * initial KlaviyoWebViewController implementation removed config parameter added `createWebView` comments * added `configureSubviewConstraints()` * added preview added preview title * added `createWebViewConfiguration()` method added `createWebViewConfiguration` comments * added `WKNavigationEvent` enum added documentation to `WKNavigationEvent` enum * inject URL * added `FileIO.getFileUrl(...)` method * added placeholder code for WKNavigationDelegate methods * added placeholder code to handle WKScriptMessage * refactored webView initialization * added KlaviyoWebViewModel * inject viewModel into viewController * added `handleNavigationEvent` method added // MARK * call `handleNavigationEvent` from viewController * added `hanldeScriptMessage` method * added `configureScripts()` renamed `configureScripts` and added documentation renamed `scripts` to `loadScripts` added TODO * added resources to Package.swift added bridge.js file * added // MARK labels added // MARK labels added MARK * added script execution logic * changed script executor injection to enable callbacks * made `loadScripts` optional * created `KlaviyoWebViewModeling` protocol * implemented protocol * reorganized files * added `return` to preview * added swift version check * reverted Xcode CI build versions
- Loading branch information
Showing
8 changed files
with
270 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
136 changes: 136 additions & 0 deletions
136
Sources/KlaviyoUI/KlaviyoWebView/KlaviyoWebViewController.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
// | ||
// KlaviyoWebViewController.swift | ||
// klaviyo-swift-sdk | ||
// | ||
// Created by Andrew Balmer on 9/28/24. | ||
// | ||
|
||
import Combine | ||
import UIKit | ||
import WebKit | ||
|
||
class KlaviyoWebViewController: UIViewController, WKUIDelegate { | ||
var webView: WKWebView! | ||
private let viewModel: KlaviyoWebViewModeling | ||
private var cancellables = Set<AnyCancellable>() | ||
|
||
// MARK: - Initializers | ||
|
||
init(viewModel: KlaviyoWebViewModeling) { | ||
self.viewModel = viewModel | ||
super.init(nibName: nil, bundle: nil) | ||
} | ||
|
||
@available(*, unavailable) | ||
required init?(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
// MARK: - View loading | ||
|
||
override func loadView() { | ||
super.loadView() | ||
|
||
let config = createWebViewConfiguration() | ||
webView = createWebView(with: config) | ||
webView.navigationDelegate = self | ||
webView.uiDelegate = self | ||
|
||
view.addSubview(webView) | ||
|
||
configureLoadScripts() | ||
configureScriptEvaluator() | ||
configureSubviewConstraints() | ||
} | ||
|
||
override func viewDidLoad() { | ||
let request = URLRequest(url: viewModel.url) | ||
webView.load(request) | ||
} | ||
|
||
// MARK: - WKWebView configuration | ||
|
||
func createWebViewConfiguration() -> WKWebViewConfiguration { | ||
let config = WKWebViewConfiguration() | ||
// customize any WKWebViewConfiguration properties here | ||
// ex: config.allowsInlineMediaPlayback = true | ||
return config | ||
} | ||
|
||
func createWebView(with config: WKWebViewConfiguration) -> WKWebView { | ||
let webView = WKWebView(frame: .zero, configuration: config) | ||
// customize any WKWebView behaviors here | ||
// ex: webView.allowsBackForwardNavigationGestures = true | ||
return webView | ||
} | ||
|
||
// MARK: - Scripts | ||
|
||
/// Configures the scripts to be injected into the website when the website loads. | ||
func configureLoadScripts() { | ||
guard let scriptsDict = viewModel.loadScripts else { return } | ||
|
||
for (name, script) in scriptsDict { | ||
webView.configuration.userContentController.addUserScript(script) | ||
webView.configuration.userContentController.add(self, name: name) | ||
} | ||
} | ||
|
||
func configureScriptEvaluator() { | ||
viewModel.scriptSubject.sink { [weak self] script, callback in | ||
Task { [weak self] in | ||
do { | ||
let result = try await self?.webView.evaluateJavaScript(script) | ||
callback?(.success(result)) | ||
} catch { | ||
callback?(.failure(error)) | ||
} | ||
} | ||
}.store(in: &cancellables) | ||
} | ||
|
||
// MARK: - Layout | ||
|
||
func configureSubviewConstraints() { | ||
webView.translatesAutoresizingMaskIntoConstraints = false | ||
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true | ||
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true | ||
webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true | ||
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true | ||
} | ||
} | ||
|
||
extension KlaviyoWebViewController: WKNavigationDelegate { | ||
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { | ||
viewModel.handleNavigationEvent(.didStartProvisionalNavigation) | ||
} | ||
|
||
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: any Error) { | ||
viewModel.handleNavigationEvent(.didFailProvisionalNavigation) | ||
} | ||
|
||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { | ||
viewModel.handleNavigationEvent(.didFinishNavigation) | ||
} | ||
|
||
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: any Error) { | ||
viewModel.handleNavigationEvent(.didFailNavigation) | ||
} | ||
} | ||
|
||
extension KlaviyoWebViewController: WKScriptMessageHandler { | ||
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { | ||
viewModel.handleScriptMessage(message) | ||
} | ||
} | ||
|
||
// MARK: - Previews | ||
|
||
#if swift(>=5.9) | ||
@available(iOS 17.0, *) | ||
#Preview("Klaviyo.com") { | ||
let url = URL(string: "https://www.klaviyo.com")! | ||
let viewModel = KlaviyoWebViewModel(url: url) | ||
return KlaviyoWebViewController(viewModel: viewModel) | ||
} | ||
#endif |
41 changes: 41 additions & 0 deletions
41
Sources/KlaviyoUI/KlaviyoWebView/KlaviyoWebViewModel.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// | ||
// KlaviyoWebViewModel.swift | ||
// klaviyo-swift-sdk | ||
// | ||
// Created by Andrew Balmer on 9/30/24. | ||
// | ||
|
||
import Combine | ||
import Foundation | ||
import WebKit | ||
|
||
class KlaviyoWebViewModel: KlaviyoWebViewModeling { | ||
let url: URL | ||
let loadScripts: [String: WKUserScript]? | ||
|
||
/// Publishes scripts for the `WKWebView` to execute. | ||
let scriptSubject = PassthroughSubject<(script: String, callback: ((Result<Any?, Error>) -> Void)?), Never>() | ||
|
||
init(url: URL) { | ||
self.url = url | ||
loadScripts = KlaviyoWebViewModel.initializeLoadScripts() | ||
} | ||
|
||
private static func initializeLoadScripts() -> [String: WKUserScript] { | ||
var scripts: [String: WKUserScript] = [:] | ||
|
||
// TODO: initialize scripts | ||
|
||
return scripts | ||
} | ||
|
||
// MARK: handle WKWebView events | ||
|
||
func handleNavigationEvent(_ event: WKNavigationEvent) { | ||
// TODO: handle navigation events | ||
} | ||
|
||
func handleScriptMessage(_ message: WKScriptMessage) { | ||
// TODO: handle script message | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
Sources/KlaviyoUI/KlaviyoWebView/KlaviyoWebViewModeling.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// | ||
// KlaviyoWebViewModeling.swift | ||
// klaviyo-swift-sdk | ||
// | ||
// Created by Andrew Balmer on 10/1/24. | ||
// | ||
|
||
import Combine | ||
import Foundation | ||
import WebKit | ||
|
||
protocol KlaviyoWebViewModeling { | ||
var url: URL { get } | ||
|
||
/// Scripts to be injected into the ``WKWebView`` when the website loads. | ||
var loadScripts: [String: WKUserScript]? { get } | ||
|
||
/// Publishes scripts for the ``WKWebView`` to execute. | ||
var scriptSubject: PassthroughSubject<(script: String, callback: ((Result<Any?, Error>) -> Void)?), Never> { get } | ||
|
||
func handleNavigationEvent(_ event: WKNavigationEvent) | ||
func handleScriptMessage(_ message: WKScriptMessage) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// | ||
// Bridge.js | ||
// klaviyo-swift-sdk | ||
// | ||
// Created by Andrew Balmer on 10/1/24. | ||
// |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// | ||
// WKNavigationEvent.swift | ||
// klaviyo-swift-sdk | ||
// | ||
// Created by Andrew Balmer on 9/30/24. | ||
// | ||
|
||
enum WKNavigationEvent { | ||
/// Invoked when a main frame navigation starts. | ||
case didStartProvisionalNavigation | ||
|
||
/// Invoked when a server redirect is received for the main frame. | ||
case didReceiveServerRedirectForProvisionalNavigation | ||
|
||
/// Invoked when an error occurs while starting to load data for the main frame. | ||
case didFailProvisionalNavigation | ||
|
||
/// Invoked when content starts arriving for the main frame. | ||
case didCommitNavigation | ||
|
||
/// Invoked when a main frame navigation completes. | ||
case didFinishNavigation | ||
|
||
/// Invoked when an error occurs during a committed main frame navigation. | ||
case didFailNavigation | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// | ||
// FileIO.swift | ||
// KlaviyoSwiftUIWebView | ||
// | ||
// Created by Andrew Balmer on 9/27/24. | ||
// | ||
|
||
import Foundation | ||
|
||
enum FileIOError: Error { | ||
case notFound | ||
} | ||
|
||
enum FileIO { | ||
static func getFileUrl(path: String, type: String) throws -> URL { | ||
guard let fileUrl = Bundle.module.url(forResource: path, withExtension: type) else { | ||
throw FileIOError.notFound | ||
} | ||
|
||
return fileUrl | ||
} | ||
|
||
static func getFileContents(path: String, type: String) throws -> String { | ||
guard let path = Bundle.module.path(forResource: path, ofType: type) else { | ||
throw FileIOError.notFound | ||
} | ||
|
||
do { | ||
let contents = try String(contentsOfFile: path, encoding: String.Encoding.utf8) | ||
return contents | ||
} catch { | ||
throw error | ||
} | ||
} | ||
} |