As per the apple doc, we can create xcprivacy property file and add required keys and values to fix this issue.
Wednesday, March 20, 2024
Monday, March 4, 2024
Swift Rule Engine using Javascript for your iOS/MacOS application
In this way, we can keep the core business logic must be the same over multiple platform and can control without updating the application.
Form your app, you can pass values as parameters and the script can evaluate it and can return the result as plain text or JSON text if needed.
First load the script using scripRunner.loadWebViewWithScript(script), and we can invoke a function using scripRunner.evaluateFunction(fnName: "function")
See the example code of passing a contact object to a script function and that function validate the contact object and returning a json object with true or false value.
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
struct ContentView: View { | |
let scripRunner = JavascriptHandler() | |
var body: some View { | |
VStack { | |
Button { | |
Task { | |
let c1 = Contact(name: "JP", age: 14, knowSwimming: false) | |
let result = try await scripRunner.evaluateFunction(fnName: "validate('\(c1.toJSONString)')") | |
let obj = try JSONDecoder().decode(Result.self, from: Data(result.utf8)) | |
print("obj.result = \(obj.result)") | |
} | |
} label: { | |
Text("Click me") | |
} | |
} | |
.onAppear { | |
Task { | |
try await scripRunner.loadWebViewWithScript(script) | |
} | |
} | |
.padding() | |
} | |
} | |
#Preview { | |
ContentView() | |
} | |
struct Contact: Encodable { | |
let name: String | |
let age: Int | |
let knowSwimming: Bool | |
} | |
struct Result: Decodable { | |
var result: Bool | |
} | |
let script = """ | |
<script type="text/javascript"> | |
function validate(contact) { | |
const contactObj = JSON.parse(contact); | |
if (contactObj.age > 12 && contactObj.knowSwimming == true){ | |
return '{\"result\" : true}'; | |
} | |
return '{\"result\" : false}'; | |
} | |
</script> | |
""" |
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
import WebKit | |
public class JavascriptHandler: NSObject { | |
enum JSError: Error { | |
case evaluation | |
} | |
private var jsWebView: WKWebView? | |
private var libraryPath: URL? | |
private var continuation: CheckedContinuation<Bool, Never>? | |
public init(_ libraryPath: URL? = nil) { | |
self.libraryPath = libraryPath | |
} | |
private func makeWebView() -> WKWebView { | |
let webView = WKWebView(frame: CGRect.zero, configuration: webViewConfiguration) | |
webView.navigationDelegate = self | |
webView.allowsLinkPreview = true | |
webView.allowsBackForwardNavigationGestures = true | |
webView.uiDelegate = self | |
return webView | |
} | |
private var webViewConfiguration: WKWebViewConfiguration { | |
let config = WKWebViewConfiguration() | |
let source = "delete window.SharedWorker;" | |
let script = WKUserScript(source: source, injectionTime: WKUserScriptInjectionTime.atDocumentStart, forMainFrameOnly: false) | |
config.userContentController.addUserScript(script) | |
return config | |
} | |
@discardableResult | |
@MainActor | |
public func loadWebViewWithScript(_ script: String) async throws -> Bool { | |
jsWebView = makeWebView() | |
if !script.isEmpty { | |
jsWebView?.loadHTMLString(script, baseURL: libraryPath) | |
let result = await withCheckedContinuation { sendable in | |
continuation = sendable | |
} | |
return result | |
} | |
return false | |
} | |
@MainActor | |
public func evaluateFunction(fnName: String) async throws -> String { | |
guard let result = try await jsWebView?.evaluateJavaScript(fnName) else { | |
throw JSError.evaluation | |
} | |
return String(format: "%@", result as! CVarArg) | |
} | |
} | |
extension JavascriptHandler: WKNavigationDelegate, WKUIDelegate { | |
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { | |
continuation?.resume(returning: true) | |
} | |
public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { | |
continuation?.resume(returning: false) | |
} | |
} | |
extension Encodable { | |
var toJSONString: String { | |
guard let data = try? JSONEncoder().encode(self) else { return "" } | |
return String(decoding: data, as: UTF8.self) | |
} | |
} |
In this example, the loading of script content is done with onAppear and evaluating on the button click. In this way, we can do the load once and evaluate any times.
Subscribe to:
Posts (Atom)