深入淺出 Fuse
在上次Fuse 15 分鐘入門教程 之后,用 Fuse 開發的第一個 AppProducter 也提交到了 App Store
代碼已經開源在了 Github 這篇文章將用來總結一些 Fuse 開發中的 “深坑” 和經驗。
引入 Javascript 庫
Fuse 的邏輯層都是由 Javascript 來處理,那么利用一些好用的庫會使得我們的開發更為有效,以 Moment.js 為例,這是前端常用的一個時間解析庫。
第一步就是到 http://momentjs.com 下載最新版本的庫,把 moment with locales 這庫存儲到項目目錄里,例如我存儲在了 assets/js 目錄下,那么在 MainView.ux 里,既可以這樣載入這個模塊
<JavaScript File="assets/js/moment.min.js" ux:Global="Moment"/>
如果要使用這庫,就可以在 Javascript 文件里這樣 require 它,require 里的字符串要和我們 ux:Global 里寫的一樣
var Moment = require('Moment');
接下來你就可以直接用 Moment 來解析時間了。
讀取本地文件
在 Producter App 里我用到了 Webview 來結合 JSON 和本地的 HTML 模版顯示文章,Fuse 的 Javascript 并沒有接口可以讀取本地文件,所以需要利用 Uno 來實現這個功能。
首先確保你的 .unoproj 里的 Includes 里有打包 *.html
"Includes": [ "*", "*.html" ]
隨后將下面這段代碼保存為 TextFile.uno
using Uno; using Uno.Collections; using Fuse; using Uno.UX; using Fuse.Scripting; using Fuse.Reactive; public class TextFile: NativeModule { readonly FileSource _file; [UXConstructor] public TextFile([UXParameter("File")] FileSource file) { AddMember(new NativeFunction("readSync", (NativeCallback)readSync)); _file = file; } object readSync(Context c, object[] args) { return _file.ReadAllText(); } }
在開始我們用 using 引用了一些我們會用到的庫,這些被引用的庫要確保存在于 .unoproj 里,你可以參考 Producter 的配置文件
"Packages": [ "Fuse.PushNotifications", "Fuse.Animations", "Fuse.BasicTheme", "Fuse.Themes", "Fuse.Controls", "Fuse.Designer", "Fuse.Drawing", "Fuse.Drawing.Primitives", "Fuse.Effects", "Fuse.Elements", "Fuse.Entities", "Fuse.Gestures", "Fuse.Navigation", "Fuse.Shapes", "Fuse.Triggers", "Fuse.Reactive", "Fuse.Android", "Fuse.Desktop", "Fuse.iOS", "FuseCore", "Fuse.Launcher", "Uno.Collections", "Uno.Geometry", "Fuse.Scripting", "Experimental.iOS", "ObjC" ]
TextFile 這個類后面還有一個 NativeModule 繼承,通過這個繼承就使得我們這個類可以通過 <TextFile /> 的形式放在 Mainview.ux 里,所有自定義的類都需要用這樣的方式加載到我們的 Fuse App 里。
更詳細的 NativeModule 使用細節可以參考官方的 Guide Working with Uno Code
UXConstructor 字段的修飾讓 <TextFile /> 在創建這個類的時候知道 <TextFile File="xxxxx"/> 對應 [UXParameter("File")] FileSource file 傳遞構造參數給這個類的構造方法。
在 readSync 方法里,通過 ReadAllText() 讀取了路徑文本,那么接下來還有一件事情就是要把這個方法暴露給 Javascript。
通過 AddMember(new NativeFunction("readSync", (NativeCallback)readSync)); 我們可以讓 Uno 完成方法到 Javascript 的橋接。
完成這兩步之后,在 Mainview.ux 加入文件的引用
<TextFile File="videoHTMLTemplate.html" ux:Global="videoHTMLTemplate" />
接下來就可以在 Javascript 里使用這個文件了
var videoHTMLTemplate = require("videoHTMLTemplate"); // Read Templates var videoHTMLTemplateString = videoHTMLTemplate.readSync();
最后 Webview 通過 Source 屬性將我們自己生成好的 HTML 填充進去, presentedArticleHTML 綁定到了我們自己生成的 HTML 數據上。
<WebView Source="{presentedArticleHTML}" > </WebView>
調用 iOS API
在后續的版本會舍棄現在的調用方法,引入 Foreigon Code 的模式,不過官方說還有幾周的時間,所以這個即將過時的方法就先帖出來吧。
這種模式依賴于 Uno,Uno 一面把接口暴露給 Javascript,一面去調用 iOS 的接口。
具體的例子可以參考 Producter 的 FuseStoreKit.uno 這個文件,里面的用法已經相當深入,觸及了 API 調用的瓶頸,這也正是為什么 Fuse 接下來要棄用 Uno 橋接的原因。
如果需要引用某些 iOS 的庫,確保 .unoproj 已經引入了 Objc 和 Experimental.iOS 這兩個庫,不確定話可以直接照搬 Producter 的配置文件。
例如我們需要引用 iOS 的 StoreKit 庫,那么可以用下面的語句
using global::iOS.StoreKit;
因為我們的 NativeModule 需要同時存在于 iOS 和 Android,所以 FuseStoreKit 其實是我們實際接口操作類的上一層代理,避免直接調用發生悲劇。
public class FuseStoreKit : NativeModule { extern(iOS) StoreKit storeKit = new StoreKit(); extern(iOS) Storage storage = new Storage(); extern(iOS) CloudKit cloudKit = new CloudKit(); }
extern 表示僅當是 iOS 平臺的時候才會創建下面的屬性。
而具體的方法在執行的時候,也用了 defined 來確保只有在正確的平臺上才會執行,如果不是這個平臺,Javascript 在調用的時候,就會 null。
object MakeSubscribe(Context c, object[] args) { if defined (iOS) { Subscribe(); } return null; }
具體創建 iOS 類的時候,在 Uno 里寫起來沒有 iOS 那么得心應手,init 方法和類方法都有著不同的調用規則,例如創建 NSUserDefaults 的時候,要這樣去寫
NSUserDefaults userDefaults = new NSUserDefaults(NSUserDefaults._standardUserDefaults());
NSUserDefaults 的 standardUserDefaults 是一個類方法,因此要加下劃線,然后通過 new NSUserDefaults() 來包住這個方法去創建。
如果是 init 方法,那么使用起來就更麻煩一些,先初始化,然后再 init。
NSString begin = new NSString(); begin.initWithString("producter_month_subscribe");
奇技淫巧參考 FuseStoreKit.uno 中我注釋掉的代碼。
Delegate
有些需要注意的是, .delegate = this 這種直接的屬性賦值通常是不能工作的,需要使用 request.setDelegate(this); 這種方法。
其次是 Objective C 的方法如果有多個參數一般是成段隔開的,在 Uno 里要合成一段然后再在括號里分別傳入參數,例如以下這段
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
那么在 Uno 里就要這樣寫
public void paymentQueueRestoreCompletedTransactionsFailedWithError (SKPaymentQueue queue, NSError error) { // Restore failed somewhere... }
目前 Objc 的閉包回調函數在 Uno 里還沒實現,有個叫 UXL 的可以做,但是…… 我們還是等 Foreigon Code 吧。