深入浅出 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 吧。