diff --git a/README-ko.md b/README-ko.md index c9bacba..e338434 100644 --- a/README-ko.md +++ b/README-ko.md @@ -10,321 +10,6 @@ podFile에 다음을 추가 pod 'FlexHybridApp' ``` -***iOS Deployment Target은 9.0 입니다.*** +***iOS Deployment Target은 10.0 입니다.*** -# Flex Framework 인터페이스 주요 특징 -기본적으로 WKWebView userContentController에 여러가지 기능이 추가되었습니다. -1. Web에서 Native 함수 호출시, **Native함수의 Return이 Web에 Promise로** 전달됩니다. -2. Native에서 Web함수 호출시, **Web에서 Native로 Async**하게 반환값을 전달 할 수 있습니다. -3. WKWebViewConfiguration 대신, FlexComponent를 사용해야 합니다. FlexComponent는 WKWebViewConfiguration를 포함하고 있습니다. -4. userContentController와는 다르게, 각 인터페이스의 **네이티브 동작을 별도의 코드 블럭(Clouser)** 으로 지정할 수 있습니다. -5. Web에서 Native 호출시, **Native 코드 블럭은 Background(DispatchQoS.background)** 안에서 Concurrent하게 동작합니다 -6. FlexWebView에 BaseUrl을 지정하여, **타 사이트 및 페이지에서 Native와 Interface하는 것을 방지**할 수 있습니다. - -# Flex 인터페이스 구현 -## 전달 가능한 데이터 타입 -Array와 Object형식의 데이터를 전송할 때 안에 포함된 데이터는 **반드시 아래 자료형 중 하나여야 합니다**. - -| JS | Swift | -|:--:|:--:| -| Number | Int, Float, Double | -| String | String, Character | -| Boolean | Bool | -| Array [] | Array | -| Object {} | Dictionary | -| undefined (Single Argument Only), null | nil | -| Error | BrowserException | - -## FlexData -Web에서 Native로 전달되는 모든 데이터는, `FlexData` 클래스로 변환되어 전달됩니다. -`FlexData` 클래스는 Web의 데이터를 Type-Safe하게 사용하도록 도와줍니다. -```js -// in web javascript -... -const res = await $flex.CallNative("Hi Android", 100.2,[false, true]]); -// res is "HiFlexWeb" -``` -```swift -flexComponent.stringInterface("CallNative") // "CallNative" becomes the function name in Web JavaScript. -{ arguments -> String in - // arguments is Arguemnts Data from web. Type is Array - let hello = arguments[0].asString() // hello = "Hi Android" - let number: Double = arguments[1].reified() // number = 100.2 - let array: [FlexData] = arguments[2].reified() // array = [FlexData(false), FlexData(true)] - return "HiFlexWeb" // "HiFlexWeb" is passed to web in Promise pattern. -} -``` -`FlexData`는 기본적으로 아래의 타입 변환 함수를 제공합니다. -```swift -func asString() -> String? -func asInt() -> Int? -func asDouble() -> Double? -func asFloat() -> Float? -func asBoolean() -> Bool? -func asArray() -> Array? -func asDictionary() -> Dictionary? -func asErr() -> BrowserException? -func toString() -> String? -``` -또한, `reified` 함수를 통해 명시된 Type으로 자동 형변환하여 데이터를 가져올 수 있습니다. -```swift -func reified() -> T? -``` -이때 사용 가능한 데이터 타입은 `String, Int, Float, Double, Bool, Array, Dictionary, BrowserException` 입니다. - -## WebToNative 인터페이스 -WebToNative 인터페이스는 다음의 특징을 지닙니다. -1. 함수 return으로 값을 전달하는 Normal Interface, Method 호출로 값을 전달하는 Action Interface 2가지 종류 -2. Clouser형태로 인터페이스 코드 블럭을 추가 -3. Clouser는 별도의 Background(DispatchQoS.background)에서 동작 -4. 추가된 인터페이스는 Web에서 $flex.함수명 형태로 호출 가능 -5. $flex Object는 window.onFlexLoad가 호출된 이후 사용 가능 - -### ***Nomal Interface*** -Normal Interface는 기본적으로 다음과 같이 사용합니다. -```swift -// in Swfit -flexComponent.stringInterface("Normal") // "Normal" becomes the function name in Web JavaScript. -{ arguments -> String in - // arguments is Arguemnts Data from web. Type is Array - // ["data1", 2, false] - return "HiFlexWeb" // "HiFlexWeb" is passed to web in Promise pattern. -} -flexWebView = FlexWebView(frame: self.view.frame, component: flexComponent) -``` -```js -// in web javascript -... -const res = await $flex.Normal("data1",2,false); -// res is "HiFlexWeb" -``` -`stringInterface`의 첫 인자로 웹에서의 함수 이름을 지정하고 이어지는 Clouser는 함수가 동작하는 코드 블럭이 됩니다. -Clouser로 전달되는 arguments는 Array 객체로서 web에서 함수 호출시 전달된 값들이 담겨 있습니다. -Normal Interface의 종류는 web에 리턴하는 타입에 따라 나뉘어져 있으며, 그 종류는 다음과 같습니다. -```swift -public func voidInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Void) -public func intInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Int) -public func doubleInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Double) -public func floatInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Float) -public func boolInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Bool) -public func stringInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> String) -public func arrayInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Array) -public func dictionaryInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Dictionary) -``` - -### ***Action Interface*** -Action Interface는 Normal Interface와 거의 비슷하나, Web으로의 값 리턴을 action객체의 `promiseReturn` 메소드를 호출하는 시점에 전달합니다. -```swift -// in Kotlin -var mAction: FlexAction? = nil -... -flexComponent.setAction("Action") -{ (action, arguments) -> Void in -// arguments is Array, ["Who Are You?"] -// action is FlexAction Object - mAction = action -} -flexWebView = FlexWebView(frame: self.view.frame, component: flexComponent) -... -// Returns to the Web when calling promiseReturn. -mAction?.promiseReturn(["FlexAction!!!",100]); -mAction = nil -``` -```js -// in web javascript -.... -const res = await $flex.Action("Who Are You?"); // Pending until promiseReturn is called... -// res is ["FlexAction!!!", 100] -``` -`promiseReturn`의 파라미터는 [전달 가능한 데이터 타입](#전달-가능한-데이터-타입)만 사용 가능합니다. -`promiseReturn`메소드가 호출되지 못하면, web에서 해당 함수는 계속 pending된 상태가 되기 때문에 Action Interface를 사용시 `promiseReturn`를 ***반드시*** 호출할 수 있도록 주의가 필요합니다. -전달값이 없다면, `resolveVoid()`를 대신 호출할 수 있습니다. 이는 `promiseReturn(nil)`과 동일합니다. -또한 이미 `promiseReturn`가 호출되었던 FlexAction 객체는 `promiseReturn`이 중복 호출되어도 Web 함수에 파라미터가 전달되지 않습니다. - -### ***Error Interface*** -Normal Interface의 경우 Clouser 내에서 Exception을 발생시키면 Web에 에러사항을 전달할 수 있습니다. -```swift -// in swift -flexComponent.voidInterface("errorTest") -{ arguments -> Void in - throw Error //some Error -} -``` -```js -// in js -... -try { - const result = await $flex.errorTest(); -} catch(e) { - // error occurred -} -``` -`FlexAction`에서는, `promiseReturn`에 `BrowserException`객체를 전달하거나, `reject`함수를 호출하여 손쉽게 에러사항을 전달할 수 있습니다. -```swift -// in swift -flexComponent.setAction("errorAction") -{ (action, arguments) -> Void in - action.reject("errorAction") // = action.promiseReturn(BrowserException("errorAction")) -} -``` -```js -// in js -... -try { - const result = await $flex.errorAction(); -} catch(e) { - // e is Error("errorAction") -} -``` - -## NativeToWeb 인터페이스 -NativeToWeb 인터페이스는 다음의 특징을 지닙니다. -1. Web의 $flex.web Object 안에 함수를 추가하면, Native(FlexWebView, FlexComponent)에서 `evalFlexFunc` 메소드를 통해 해당 함수를 호출할 수 있습니다. -2. window.onFlexLoad 호출 후($flex 생성 후) $flex.web에 함수 추가가 가능합니다. -3. $flex.web 함수는, 일반 return 및 Promise return을 통해 Native에 값을 전달 할 수 있습니다. - -```js -window.onFlexLoad = () => { - $flex.web.webFunc = (data) => { - // data is ["data1","data2"] - return data[0]; // "data1" - } - $flex.web.Retrun = () => { - return Promise.resolve("this is promise") - } -} -``` -```swift -... -// call function, send data, get response -mFlexWebView.evalFlexFunc("webFunc",["data1","data2"]) // same as $flex.web.webFunc(["data1","data2"]) -{ res -> Void in - // res is "data1" -} -component.evalFlexFunc("Retrun") // same as $flex.web.Retrun() -{ res -> Void in - // res is "this is promise" -} -// just call function -component.evalFlexFunc("Retrun") -// call function and send data -mFlexWebView.evalFlexFunc("webFunc",["data1","data2"]) -``` - -# Native Class -FlexWebView를 비롯한 프레임워크의 Native class를 설명합니다. -## FlexWebView -FlexWebView는 다음의 특징을 지닙니다. -1. WKWebView 상속하여 제작되었습니다. -2. 비동기 인터페이스를 위해선 FlexComponent를 사용해야 합니다. FlexComponent는 WKWebViewConfiguration를 포함하고 있습니다. -3. 기존 WKWebView의 userContentController와 혼용하여 사용할 수 있습니다. (이 경우, $flex를 사용한 Promise pattern interface 사용 불가.) -4. evalFlexFunc 메소드를 통해, $flex.web 안의 함수들을 호출할 수 있습니다. - -### FlexWebView 구성요소 -아래 구성 요소를 제외하면, WKWebView와 동일합니다. -```swift -let component: FlexComponent // readOnly -var parentViewController: UIViewController? // readOnly -init (frame: CGRect, configuration: WKWebViewConfiguration) -init (frame: CGRect, component: FlexComponent) -func evalFlexFunc(_ funcName: String) -func evalFlexFunc(_ funcName: String, _ returnAs: @escaping (_ data: FlexData) -> Void) -func evalFlexFunc(_ funcName: String, sendData: Any) -func evalFlexFunc(_ funcName: String, sendData: Any, _ returnAs: @escaping (_ data: FlexData) -> Void) -``` -evalFlexFunc 사용법은 [NativeToWeb 인터페이스](#NativeToWeb-인터페이스)를 참조하세요. - -## FlexComponent -FlexComponent는 WKWebViewConfiguration를 대체하며, 다음의 특징을 지닙니다. -1. WKWebViewConfiguration를 포함하고 있으며, FlexComponent의 WKWebViewConfiguration는 FlexWebView에 적용됩니다. -2. Normal Interface, setAction을 통해 FlexWebView에 Native 와 Web간의 비동기 인터페이스를 추가합니다. -3. BaseUrl을 설정하여, 지정된 페이지에서만 네이티브와 인터페이스 하도록 설정할 수 있습니다. -4. $flex Object에 여러 설정값을 추가 할 수 있습니다. - -### BaseUrl 설정 -설정한 BaseUrl이 포함된 Page에서만 $flex Object 사용이 가능합니다. -BaseUrl을 설정하지 않으면, 모든 페이지에서 $flex Object를 사용할 수 있습니다. -한번 설정한 BaseUrl은 다시 수정할 수 없습니다. -```swift -func setBaseUrl(_ url: String) -var BaseUrl: String? // readOnly -``` - -### Url Access in File -file:// 등의 url을 사용할 때, UrlAccess등을 허용하기 위한 기능입니다. -allowFileAccessFromFileURLs, allowUniversalAccessFromFileURLs을 한번에 설정합니다. -```swift -public func setAllowsUrlAccessInFile(_ allow: Bool) -``` - -### InterfaceTimeout -FlexInterface가 실행된 후, return이 발생할 때 까지 기다리는 시간을 설정합니다. -해당 시간이 지나면, 인터페이스로 생성된 Promise는 강제 reject 처리됩니다. -0으로 설정 시, timeout이 발생하지 않습니다. -기본값은 1분(6000) 입니다. -```swift -func setInterfaceTimeout(_ timeout: Int) -``` - -### WebToNative Interface Setting -FlexWebView에 인터페이스를 추가합니다. -상세한 사항은 [WebToNavite 인터페이스](#WebToNative-인터페이스) 항목을 참고하세요. -```swift -public func voidInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Void) -public func intInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Int) -public func doubleInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Double) -public func floatInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Float) -public func boolInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Bool) -public func stringInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> String) -public func arrayInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Array) -public func dictionaryInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Dictionary) -func setAction(_ name: String, _ action: @escaping (_ action: FlexAction, _ arguments: Array?) -> Void) -``` - -### call NativeToWeb Interface -NativeToWeb 인터페이스를 호출합니다. -```swift -func evalFlexFunc(_ funcName: String) -func evalFlexFunc(_ funcName: String, _ returnAs: @escaping (_ data: FlexData) -> Void) -func evalFlexFunc(_ funcName: String, sendData: Any) -func evalFlexFunc(_ funcName: String, sendData: Any, _ returnAs: @escaping (_ data: FlexData) -> Void) -``` -evalFlexFunc 사용법은 [NativeToWeb 인터페이스](#NativeToWeb-인터페이스)를 참조하세요. - -### 기타 FlexComponent 구성요소 -```swift -var FlexWebView: FlexWebView? // readOnly -var configration: WKWebViewConfiguration // readOnly -var parentViewController: UIViewController? // readOnly -``` - -## FlexAction -setAction로 추가된 WebToNative 인터페이스가 호출될 시 생성됩니다. -사용 가능한 메소드는 아래와 같으며, promiseReturn 함수만 Web으로 return값을 전달하는 역할을 합니다. -resolveVoid는 nil 값을 전달하며(promiseReturn(nil)과 동일) -reject함수는 BrowserException 객체를 자동으로 생성하여 전달합니다.(promiseReturn(BrowserException)와 동일) -```swift -func promiseReturn(_ response: [Transferable Data Type]) -func resolveVoid() -func reject(reason: BrowserException) -func reject(reason: String) -func reject() -``` -위 함수중 하나라도 호출했다면, 다음에 어떤 함수를 호출하더라도 Web에 값이 전달되지 않습니다. -FlexAction Class를 직접 생성 및 사용하면 아무런 효과도 얻을 수 없으며, 오직 인터페이스상에서 생성되어 전달되는 FlexAction만이 효력을 가집니다. - -# $flex Object -\$flex Object는 FlexWebView를 와 Promise 형태로 상호간 인터페이스가 구성되어있는 객체입니다. -$flex Object는 [Android FlexHybridApp](https://github.com/Kyun-J/FlexHybridApp-Android)에 적용될 때와 동일한 코드로 사용할 수 있습니다. -$flex는 액세스 가능한 모든 하위 프레임에서도 사용 할 수 있습니다. (Ex)Cross-Origin을 위반하지 않는 iframe) -$flex Object의 구성 요소는 다음과 같습니다. -```js -window.onFlexLoad // Called after the $flex load completes. When overriding onFlexLoad, the overridden function is called immediately. -$flex // Object that contains functions that can call Native area as WebToNative -$flex.version // get Library version -$flex.web // Object used to add and use functions to be used for NativeToWeb -$flex.device // Currnet Device Info -$flex.isAndroid // false -$flex.isiOS // true -``` -상세한 사용법은 [Flex 인터페이스 구현](#Flex-인터페이스-구현) 항목을 참고하세요. +# 리드미 재 작성중 \ No newline at end of file diff --git a/README.md b/README.md index 9fac67d..a7e0164 100644 --- a/README.md +++ b/README.md @@ -14,320 +14,6 @@ Add the following to podFile pod 'FlexHybridApp' ``` -***iOS Deployment Target is 9.0.*** +***iOS Deployment Target is 10.0.*** -# Key features of Flex Framework interface -Basically, various functions have been added to WKWebView userContentController. -1. When the Native function is called on the Web, **Native function return is delivered to the Web as a Promise**. -2. When calling the Web function from Native, **the return value can be delivered to Async** from Web to Native. -3. Instead of WKWebViewConfiguration, you should use FlexComponent. FlexComponent includes WKWebViewConfiguration. -4. Unlike userContentController, **native behavior of each interface can be designated as a separate code block (Clouser)**. -5. When calling Native from Web, **Native code block operates concurrently in Background(DispatchQoS.background)** -6. By assigning BaseUrl to FlexWebView, **it is possible to prevent interface with other sites and pages**. - -# Flex interface implementation -## Transferable Data Type -When transferring data in the form of Array and Object, **the data contained in it must be one of the following data types**. - -| JS | Swift | -|:--:|:--:| -| Number | Int, Float, Double | -| String | String, Character | -| Boolean | Bool | -| Array [] | Array | -| Object {} | Dictionary | -| undefined (Single Argument Only), null | nil | -| Error | BrowserException | - -## FlexData -All data transferred from Web to Native is converted to `FlexData` class and transferred. -The `FlexData` class helps you use Web data in a type-safe way. -```js -// in web javascript -... -const res = await $flex.CallNative("Hi Android", 100.2,[false, true]]); -// res is "HiFlexWeb" -``` -```swift -flexComponent.stringInterface("CallNative") // "CallNative" becomes the function name in Web JavaScript. -{ arguments -> String in - // arguments is Arguemnts Data from web. Type is Array - let hello = arguments[0].asString() // hello = "Hi Android" - let number: Double = arguments[1].reified() // number = 100.2 - let array: [FlexData] = arguments[2].reified() // array = [FlexData(false), FlexData(true)] - return "HiFlexWeb" // "HiFlexWeb" is passed to web in Promise pattern. -} -``` -`FlexData` basically provides the following type conversion functions. -```swift -func asString() -> String? -func asInt() -> Int? -func asDouble() -> Double? -func asFloat() -> Float? -func asBoolean() -> Bool? -func asArray() -> Array? -func asDictionary() -> Dictionary? -func asErr() -> BrowserException? -func toString() -> String? -``` -You can also use the data by automatically casting it to an advanced type through the `reified` function. -```swift -func reified() -> T? -``` -The available data types are `String, Int, Float, Double, Bool, Array, Dictionary, BrowserException`. - -## WebToNative interface -The WebToNative interface has the following features. -1. Two types of normal interface, which passes values by function return, and action interface, which passes values by method call -2. Add interface code block in Clouser form -3. Clouser run on a separate Background (DispatchQoS.background) -4. The added interface can be called in the form of $flex.function on the web. -5. $flex Object can be used after window.onFlexLoad is called - -### ***Nomal Interface*** -Normal Interface is basically used as follows. -```swift -// in Swfit -flexComponent.stringInterface("Normal") // "Normal" becomes the function name in Web JavaScript. -{ arguments -> String in - // arguments is Arguemnts Data from web. Type is Array - // ["data1", 2, false] - return "HiFlexWeb" // "HiFlexWeb" is passed to web in Promise pattern. -} -flexWebView = FlexWebView(frame: self.view.frame, component: flexComponent) -``` -```js -// in web javascript -... -const res = await $flex.Normal("data1",2,false); -// res is "HiFlexWeb" -``` -Specify the function name on the web as the first argument of `stringInterface`, and the following Clouser becomes the block of code where the function operates. -The arguments passed to Clouser are Array objects and contain the values passed when calling the function on the web. -The types of Normal Interface are divided according to the type returned to the web, and the types are as follows. -```swift -public func voidInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Void) -public func intInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Int) -public func doubleInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Double) -public func floatInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Float) -public func boolInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Bool) -public func stringInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> String) -public func arrayInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Array) -public func dictionaryInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Dictionary) -``` - -### ***Action Interface*** -The Action Interface is almost the same as the Normal Interface, but it sends the return value to the Web at the time of calling the `promiseReturn` method of the action object. -```swift -// in Kotlin -var mAction: FlexAction? = nil -... -flexComponent.setAction("Action") -{ (action, arguments) -> Void in -// arguments is Array, ["Who Are You?"] -// action is FlexAction Object - mAction = action -} -flexWebView = FlexWebView(frame: self.view.frame, component: flexComponent) -... -// Returns to the Web when calling promiseReturn. -mAction?.promiseReturn(["FlexAction!!!",100]); -mAction = nil -``` -```js -// in web javascript -.... -const res = await $flex.Action("Who Are You?"); // Pending until promiseReturn is called... -// res is ["FlexAction!!!", 100] -``` -The parameters of `promiseReturn` are only available for [Transferable Data Type](#Transferable-Data-Type). -If the `promiseReturn` method is not called, the function in the web will be in a pending state, so be careful to call `promiseReturn` ***must*** when using the Action Interface. -If there is no passing value, you can call `resolveVoid()` instead. This is equivalent to `promiseReturn(nil)`. -Also, the FlexAction object that had already called `promiseReturn` does not pass parameters to the web function even if `promiseReturn` is called repeatedly. - -### ***Error Interface*** -In the case of Normal Interface, if an exception occurs within the Clouser, error information can be delivered to the Web. -```swift -// in swift -flexComponent.voidInterface("errorTest") -{ arguments -> Void in - throw Error //some Error -} -``` -```js -// in js -... -try { - const result = await $flex.errorTest(); -} catch(e) { - // error occurred -} -``` -In `FlexAction`, you can easily deliver an error by passing a `BrowserException` object to `promiseReturn` or calling the `reject` function. -```swift -// in swift -flexComponent.setAction("errorAction") -{ (action, arguments) -> Void in - action.reject("errorAction") // = action.promiseReturn(BrowserException("errorAction")) -} -``` -```js -// in js -... -try { - const result = await $flex.errorAction(); -} catch(e) { - // e is Error("errorAction") -} -``` - -## NativeToWeb Interface -The NativeToWeb interface has the following features. -1. If you add a function in the web's $flex.web Object, you can call the function through the `evalFlexFunc` method in Native FlexWebView. -2. After calling window.onFlexLoad (after creating $flex), you can add a function to $flex.web. -3. The $flex.web function can pass values to Native through regular return and promise return. - -```js -window.onFlexLoad = () => { - $flex.web.webFunc = (data) => { - // data is ["data1","data2"] - return data[0]; // "data1" - } - $flex.web.Return = () => { - return Promise.resolve("this is promise") - } -} -``` -```swift -... -// call function, send data, get response -mFlexWebView.evalFlexFunc("webFunc",["data1","data2"]) // same as $flex.web.webFunc(["data1","data2"]) -{ res -> Void in - // res is "data1" -} -mFlexWebView.evalFlexFunc("Return") // same as $flex.web.Return() -{ res -> Void in - // res is "this is promise" -} -// just call function -mFlexWebView.evalFlexFunc("Return") -// call function and send data -mFlexWebView.evalFlexFunc("webFunc",["data1","data2"]) -``` - -# Native Class -Describes the native classes of the framework including FlexWebView. -## FlexWebView -FlexWebView has the following features. -1. It was produced by inheriting WKWebView. -2. For asynchronous interface, FlexComponent should be used. FlexComponent includes WKWebViewConfiguration. -3. It can be used in combination with the existing WKWebView userContentController. (In this case, you cannot use the Promise pattern interface using $flex.) -4. Through the evalFlexFunc method, you can call functions in $flex.web. - -### FlexWebView component -Same as WKWebView, except for the components below. -```swift -let component: FlexComponent // readOnly -var parentViewController: UIViewController? // readOnly -init (frame: CGRect, configuration: WKWebViewConfiguration) -init (frame: CGRect, component: FlexComponent) -func evalFlexFunc(_ funcName: String) -func evalFlexFunc(_ funcName: String, _ returnAs: @escaping (_ data: FlexData) -> Void) -func evalFlexFunc(_ funcName: String, sendData: Any) -func evalFlexFunc(_ funcName: String, sendData: Any, _ returnAs: @escaping (_ data: FlexData) -> Void) -``` -For usage of evalFlexFunc, refer to [NativeToWeb interface](#NativeToWeb-interface). - -## FlexComponent -FlexComponent replaces WKWebViewConfiguration and has the following features. -1. It includes WKWebViewConfiguration, and WKWebViewConfiguration of FlexComponent is applied to FlexWebView. -2. Add asynchronous interface between Native and Web to FlexWebView through Normal Interface, setAction. -3. By setting the BaseUrl, you can set the interface to native only on the specified page. -4. You can add multiple settings to the $ flex Object. - -### BaseUrl Setting -$flex Object can be used only in the page containing the configured BaseUrl. -If you don't set BaseUrl, you can use $flex Object on any page. -Once set, the BaseUrl cannot be modified again. -```swift -func setBaseUrl(_ url: String) -var BaseUrl: String? // readOnly -``` - -### Url Access in File -This is a function to allow UrlAccess when using url such as file://. -Set allowFileAccessFromFileURLs and allowUniversalAccessFromFileURLs at once. -```swift -public func setAllowsUrlAccessInFile(_ allow: Bool) -``` - -### InterfaceTimeout -Set the time to wait for return after FlexInterface is executed. -After that time, the Promise created by the interface is forcibly rejected. -When set to 0, no timeout occurs. -The default is 1 minute (6000). -```swift -func setInterfaceTimeout(_ timeout: Int) -``` - -### WebToNative Interface Setting -Add an interface to the FlexWebView. -For details, refer to [WebToNavite interface](#WebToNative-interface). -```swift -public func voidInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Void) -public func intInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Int) -public func doubleInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Double) -public func floatInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Float) -public func boolInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Bool) -public func stringInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> String) -public func arrayInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Array) -public func dictionaryInterface(_ name: String, _ interface: @escaping (_ arguments: Array) throws -> Dictionary) -func setAction(_ name: String, _ action: @escaping (_ action: FlexAction, _ arguments: Array?) -> Void?) -``` - -### call NativeToWeb Interface -Call the NativeToWeb interface. -```swift -func evalFlexFunc(_ funcName: String) -func evalFlexFunc(_ funcName: String, _ returnAs: @escaping (_ data: FlexData) -> Void) -func evalFlexFunc(_ funcName: String, sendData: Any) -func evalFlexFunc(_ funcName: String, sendData: Any, _ returnAs: @escaping (_ data: FlexData) -> Void) -``` -For usage of evalFlexFunc, refer to [NativeToWeb interface](#NativeToWeb-interface). - -### Other FlexComponent components -```swift -var FlexWebView: FlexWebView? // readOnly -var configration: WKWebViewConfiguration // readOnly -var parentViewController: UIViewController? // readOnly -``` - -## FlexAction -Generated when the WebToNative interface added by setAction is called. -The available methods are as follows, and only the promiseReturn function is responsible for passing the return value to the web. -resolveVoid passes a nil value (same as promiseReturn(nil)) -The reject function automatically creates and passes a BrowserException object (same as promiseReturn(BrowserException)). -```swift -func promiseReturn(_ response: [Transferable Data Type]) -func resolveVoid() -func reject(reason: BrowserException) -func reject(reason: String) -func reject() -``` -If any of the above functions is called, the next time any function is called, the value is not passed to the Web. -If you directly create and use FlexAction Class, there is no effect. Only FlexAction created and delivered on the interface is effective. - -# $flex Object -\$flex Object is an object composed of interfaces between FlexWebView and Promise. -$flex Object can be used with the same code as applied to [Android FlexHybridApp](https://github.com/Kyun-J/FlexHybridApp-Android). -$flex can also be used in any accessible frames. (Ex) iframe that does not violate Cross-Origin) -The components of $flex Object are as follows. -```js -window.onFlexLoad // Called after the $flex load completes. When overriding onFlexLoad, the overridden function is called immediately. -$flex // Object that contains functions that can call Native area as WebToNative -$flex.version // get Library version -$flex.web // Object used to add and use functions to be used for NativeToWeb -$flex.isAndroid // false -$flex.isiOS // true -``` -For details, refer to [Flex Interface Implementation](#Flex-Interface-Implementation). +# Readme being rewritten \ No newline at end of file diff --git a/demo/flex-demo/ViewController.swift b/demo/flex-demo/ViewController.swift index 732232f..9fab61c 100644 --- a/demo/flex-demo/ViewController.swift +++ b/demo/flex-demo/ViewController.swift @@ -142,6 +142,8 @@ class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHan // setBaseUrl component.setBaseUrl("file://") + component.setAllowUrl(".*.facebook.com", canFlexLoad: false) + component.setAllowUrl(".*.google.com", canFlexLoad: true) component.setInterfaceTimeout(0) // component.setFlexOnloadWait(0) component.setAllowsUrlAccessInFile(true) diff --git a/demo/flex-demo/test.html b/demo/flex-demo/test.html index df8d962..99229b0 100644 --- a/demo/flex-demo/test.html +++ b/demo/flex-demo/test.html @@ -58,6 +58,15 @@ const z = await $flex.modelTest3(); console.log(z); } + const t10 = () => { + window.location.href = "https://google.com"; + }; + const t11 = () => { + window.location.href = "https://facebook.com"; + }; + const t12 = () => { + window.location.href = "https://microsoft.com"; + }; $flex.web.help = function(data) { console.log('Received by Native ---- ' + String(data)); return Promise.resolve(['Thanks Flex!', false]) @@ -80,6 +89,15 @@ + + + diff --git a/framework/flexhybridapp.xcodeproj/project.pbxproj b/framework/flexhybridapp.xcodeproj/project.pbxproj index 5fd2bfe..f9f66fb 100644 --- a/framework/flexhybridapp.xcodeproj/project.pbxproj +++ b/framework/flexhybridapp.xcodeproj/project.pbxproj @@ -83,7 +83,7 @@ DF5F181B2444737D00BE44F4 /* flexhybridapp.h */, DF5F181C2444737D00BE44F4 /* Info.plist */, DF5F1823244473A700BE44F4 /* FlexWebView.swift */, - DFF4991C274F7B860031E85E /* FlexUtil */, + DFF4991C274F7B860031E85E /* util */, DF85F11925CD22820020C9BE /* FlexEvent.swift */, DFA5032824FE4461002DDFBE /* FlexAction.swift */, DFA5032A24FE4521002DDFBE /* BrowserException.swift */, @@ -103,7 +103,7 @@ path = js; sourceTree = ""; }; - DFF4991C274F7B860031E85E /* FlexUtil */ = { + DFF4991C274F7B860031E85E /* util */ = { isa = PBXGroup; children = ( DFF4991D274F7BA10031E85E /* FlexError.swift */, @@ -113,7 +113,7 @@ DFF49925274F7C600031E85E /* DeviceInfo.swift */, DFF49927274F7D5A0031E85E /* CodableExtension.swift */, ); - path = FlexUtil; + path = util; sourceTree = ""; }; /* End PBXGroup section */ diff --git a/framework/flexhybridapp/FlexWebView.swift b/framework/flexhybridapp/FlexWebView.swift index e2ccee8..ee5e1f2 100644 --- a/framework/flexhybridapp/FlexWebView.swift +++ b/framework/flexhybridapp/FlexWebView.swift @@ -9,7 +9,7 @@ import Foundation import WebKit -private let VERSION = Bundle(identifier: "app.dvkyun.flexhybridapp")?.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown" +fileprivate let VERSION = Bundle(identifier: "app.dvkyun.flexhybridapp")?.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown" @IBDesignable open class FlexWebView : WKWebView { @@ -93,6 +93,9 @@ open class FlexComponent: NSObject, WKNavigationDelegate, WKScriptMessageHandler private var jsString: String? = nil private var baseUrl: String? = nil + private var allowUrlMap: [String: Bool] = [:] + private var recentConfigRuleString: String? = nil + private var showWebViewConsole = true private var userNavigation: WKNavigationDelegate? = nil @@ -101,7 +104,7 @@ open class FlexComponent: NSObject, WKNavigationDelegate, WKScriptMessageHandler internal var config: WKWebViewConfiguration = WKWebViewConfiguration() - private var beforeFlexLoadEvalList : Array = [] + private var beforeFlexLoadEvalList : [BeforeFlexEval] = [] private var isFlexLoad = false private var isFirstPageLoad = false @@ -161,7 +164,7 @@ open class FlexComponent: NSObject, WKNavigationDelegate, WKScriptMessageHandler baseUrl } - public var FlexWebView: FlexWebView? { + public var webView: FlexWebView? { flexWebView } @@ -183,6 +186,61 @@ open class FlexComponent: NSObject, WKNavigationDelegate, WKScriptMessageHandler } } + @available(iOS 11.0, *) + public func setAllowUrl(_ urlString: String, canFlexLoad: Bool = false) { + allowUrlMap[urlString] = canFlexLoad + configAllowContentRule() + } + + @available(iOS 11.0, *) + public func removeAllowUrl(_ urlString: String) { + allowUrlMap.removeValue(forKey: urlString) + if allowUrlMap.count == 0 { + config.userContentController.removeAllContentRuleLists() + } else { + configAllowContentRule() + } + } + + @available(iOS 11.0, *) + private func configAllowContentRule() { + var rule = [["trigger":["url-filter":".*"],"action":["type":"block"]]] + if let _baseUrl = baseUrl { + rule.append(["trigger":["url-filter":"\(_baseUrl)"],"action":["type":"ignore-previous-rules"]]) + } + for _urlString in allowUrlMap.keys { + rule.append(["trigger":["url-filter":"\(_urlString)"],"action":["type":"ignore-previous-rules"]]) + } + + guard let jsonData = try? JSONSerialization.data(withJSONObject: rule, options: []) else { + return + } + let ruleString = String(data: jsonData, encoding: String.Encoding.utf8) + + recentConfigRuleString = ruleString + + WKContentRuleListStore.default()?.compileContentRuleList( + forIdentifier: "ContentRuleList", + encodedContentRuleList: ruleString + ) { (contentRuleList, error) in + if self.recentConfigRuleString != ruleString { + return + } + + if error != nil { + return + } + guard let contentRuleList = contentRuleList else { + return + } + + let configuration = self.config + + configuration.userContentController.removeAllContentRuleLists() + configuration.userContentController.add(contentRuleList) + } + } + public func setShowWebViewConsole(_ enable: Bool) { showWebViewConsole = enable } @@ -591,16 +649,27 @@ open class FlexComponent: NSObject, WKNavigationDelegate, WKScriptMessageHandler /* WKNavigationDelegate */ - public func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { - if baseUrl == nil || (baseUrl != nil && webView.url != nil && webView.url!.absoluteString.contains(baseUrl!)) { - isFlexLoad = false - flexInterfaceInit() - evalJS(jsString!) - dependencies.forEach { (js) in - evalJS(js) + if let url = webView.url { + var needFlexLoad = false + if baseUrl == nil || (baseUrl != nil && url.absoluteString.contains(baseUrl!)) { + needFlexLoad = true + } + for (pattern, canLoadFlex) in allowUrlMap { + if url.absoluteString.range(of: pattern, options: .regularExpression) != nil { + needFlexLoad = canLoadFlex + break + } + } + if needFlexLoad { + isFlexLoad = false + flexInterfaceInit() + evalJS(jsString ?? "") + dependencies.forEach { (js) in + evalJS(js) + } + evalJS("window.$FCheck = true;") } - evalJS("window.$FCheck = true;") } userNavigation?.webView?(webView, didCommit: navigation) } @@ -609,11 +678,23 @@ open class FlexComponent: NSObject, WKNavigationDelegate, WKScriptMessageHandler if #available(iOS 11.0, *), isAutoCookieManage { saveCookie() } - if baseUrl == nil || (baseUrl != nil && webView.url != nil && webView.url!.absoluteString.contains(baseUrl!)) { - evalJS("if(typeof window.$flex === 'undefined' || window.$flex.isScript) { \(jsString!) }") - evalJS("const evalFrames=e=>{for(let o=0;o{const evalFrames=e=>{for(let o=0;o{var e="f"+String(Math.random()).substr(2,8);return void 0===$flex.flex[e]?Promise.resolve(e):Promise.resolve(u())},c=(e,o)=>{i.forEach((r=>{r.e===e&&"function"==typeof r.c&&r.c(o)}))};Object.keys(r).forEach((e=>{("timeout"===e&&"number"==typeof r[e]||"flexLoadWait"===e&&"number"==typeof r[e])&&Object.defineProperty(a,e,{value:r[e],writable:!1,enumerable:!0})}));var s=function(e){if("boolean"==typeof e)return function(e){var o=e;return(e={})[t]=o,e}(e);if("object"==typeof e&&e){for(var o=Object.keys(e),r=0;r{void 0===$flex[e]&&Object.defineProperty($flex,e,{value:function(){for(var r=arguments.length,n=new Array(r),t=0;t{u().then((i=>{var f,u=0;void 0!==o[e]&&0!==o[e]?u=o[e]:0!==a.timeout&&(u=a.timeout),0!==u&&(f=setTimeout((()=>{$flex.flex[i]=()=>{delete $flex.flex[i],console.error("Function ".concat(e," was returned after a specified timeout."))},setTimeout((()=>{delete $flex.flex[i]}),10*u),t("timeout error"),$flex.flexTimeout(e,location.href),c("timeout",{function:e})}),u)),$flex.flex[i]=(o,n,a)=>{var u;delete $flex.flex[i],void 0!==f&&clearTimeout(f),o?(r(a),l.includes(e)||($flex.flexSuccess(e,location.href,a),c("success",{function:e,data:a}))):(u="string"==typeof n?Error(n):Error("$flex Error occurred in function -- $flex."+e),t(u),l.includes(e)||($flex.flexException(e,location.href,u.toString()),c("error",{function:e,err:u})))};try{webkit.messageHandlers[e].postMessage({funName:i,arguments:s(n)})}catch(e){$flex.flex[i](!1,e.toString())}}))}))},writable:!1,enumerable:!1})})),console.log=function(){$flex.flexlog(...arguments),f.log(...arguments)},console.debug=function(){$flex.flexdebug(...arguments),f.debug(...arguments)},console.error=function(){$flex.flexerror(...arguments),f.error(...arguments)},console.info=function(){$flex.flexinfo(...arguments),f.info(...arguments)},setTimeout((()=>{var e=()=>{};"function"==typeof window.onFlexLoad&&(e=window.onFlexLoad),Object.defineProperty(window,"onFlexLoad",{set:function(e){window._onFlexLoad=e,"function"==typeof e&&Promise.resolve(e()).then((e=>{setTimeout((()=>{$flex.flexload()}),a.flexLoadWait)}))},get:function(){return window._onFlexLoad}});var o=e=>{for(var r=0;r{var e="f"+String(Math.random()).substr(2,8);return void 0===$flex.flex[e]?Promise.resolve(e):Promise.resolve(u())},c=(e,o)=>{l.forEach((r=>{r.e===e&&"function"==typeof r.c&&r.c(o)}))};Object.keys(r).forEach((e=>{("timeout"===e&&"number"==typeof r[e]||"flexLoadWait"===e&&"number"==typeof r[e])&&Object.defineProperty(a,e,{value:r[e],writable:!1,enumerable:!0})}));var s=function(e){if("boolean"==typeof e)return function(e){var o=e;return(e={})[t]=o,e}(e);if("object"==typeof e&&e){for(var o=Object.keys(e),r=0;r{void 0===$flex[e]&&Object.defineProperty($flex,e,{value:function(){for(var r=arguments.length,n=new Array(r),t=0;t{u().then((l=>{var f,u=0;void 0!==o[e]&&0!==o[e]?u=o[e]:0!==a.timeout&&(u=a.timeout),0!==u&&(f=setTimeout((()=>{$flex.flex[l]=()=>{delete $flex.flex[l],console.error("Function ".concat(e," was returned after a specified timeout."))},setTimeout((()=>{delete $flex.flex[l]}),10*u),t("timeout error"),$flex.flexTimeout(e,location.href),c("timeout",{function:e})}),u)),$flex.flex[l]=(o,n,a)=>{var u;delete $flex.flex[l],void 0!==f&&clearTimeout(f),o?(r(a),i.includes(e)||($flex.flexSuccess(e,location.href,a),c("success",{function:e,data:a}))):(u="string"==typeof n?Error(n):Error("$flex Error occurred in function -- $flex."+e),t(u),i.includes(e)||($flex.flexException(e,location.href,u.toString()),c("error",{function:e,err:u})))};try{webkit.messageHandlers[e].postMessage({funName:l,arguments:s(n)})}catch(e){$flex.flex[l](!1,e.toString())}}))}))},writable:!1,enumerable:!1})})),console.log=function(){$flex.flexlog(...arguments),f.log(...arguments)},console.debug=function(){$flex.flexdebug(...arguments),f.debug(...arguments)},console.error=function(){$flex.flexerror(...arguments),f.error(...arguments)},console.info=function(){$flex.flexinfo(...arguments),f.info(...arguments)},setTimeout((()=>{var e=()=>{};"function"==typeof window.onFlexLoad&&(e=window.onFlexLoad),Object.defineProperty(window,"onFlexLoad",{set:function(e){window._onFlexLoad=e,"function"==typeof e&&Promise.resolve(e()).then((e=>{setTimeout((()=>{$flex.flexload()}),a.flexLoadWait)}))},get:function(){return window._onFlexLoad}});var o=e=>{for(var r=0;r