某个工作日的傍晚,临近饭点,随手打开掘金,发现吹弹可爱的iOS桌面组件居然可以用 Javascript 编写!👇
快使用Scriptable自己开发一个iPhone小组件吧
然而 Hello World 是学会了,在编写自己所想的组件过程中发现对其还是了解甚微,官方文档也较为简陋。
因此想把我理解、收获的内容分享出来,便于大家更好地上手编写自己的理想组件。
(建议先到上面那篇文章了解一下用法)
容器
Scriptable 里的容器相当于 Html 的标签元素,想展示内容都必须放在容器中才能呈现,主要有以下:
- ListWidget:最基本、最外层的容器,相当于
<body>
- WidgetStack:一个区块,相当于
<div>
/<span>
- WidgetText:文本容器
- WidgetSpacer:空行元素,可弹性伸缩
- WidgetImage:图片容器
- WidgetDate:日期容器,自动刷新(Scriptable 中的其他内容一般是不会实时刷新的,因此日期时间内容需要放在特殊的容器中)
布局
ListWidget
作为整个组件的基本容器,有三种可选的 Size:
需要判定获取时,可通过 config.widgetFamily 获得
WidgetStack
通过 ListWidget 的 addStack()
方法新建。
直接从 ListWidget 上创建得到的是“行”,在此基础上再创建得到的是“列”向的小区块,如下:
const widget = new ListWidget()
const row = widget.addStack() // 相当于 <div>
const cell = row.addStack() // 相当于 <span>
一个8行10列表格布局的小例子:
const widget = new ListWidget()
widget.spacing = 2 // 制造点padding
for (let i=0; i<8; i++) {
const row = widget.addStack()
row.spacing = 2 // 制造点padding
for (let j=0; j<10; j++) {
const cell = row.addStack()
cell.size = new Size(12, 12)
cell.cornerRadius = 100 // border-radius: 100%
cell.backgroundColor = new Color(`#ff${i}6${j}6`) // 让其颜色跟随i、j序号渐变
}
}
WidgetSpacer
用于制造空元素,如:
widget.addSpacer(len: number)
、row.addSpacer(len)
当 len = 0
时,它就是用于制造弹性空间的利器。
比如我们可以在上面的表格例子中,每行的中间插入一个 WdigetSpacer:
const widget = new ListWidget()
widget.spacing = 2 // 制造点padding
for (let i=0; i<8; i++) {
const row = widget.addStack()
row.spacing = 2 // 制造点padding
for (let j=0; j<10; j++) {
// ...
if (j === 4) {
row.addSpacer()
}
}
}
开发框架
单纯在 IDE 上进行代码编写,然后复制粘贴到 Scriptable 上去跑的话,调试过程是非常低级且耗时的,因此这里推荐一个有意思的小件件开发框架:
它会将 PC 与 Scriptable 相连,然后我们就可以直接在 VSCode 上进行代码的编写,保存后会自动同步到手机上并自动运行。
具体的操作方法在作者的文档上写的很清楚了,这里不多赘述。
简单说一下其实现思路的解析:
- PC 端开启 Node 服务,移动端填入服务器地址(PC 的 ip 地址)后,把文件
POST
给 Node 端 - Node 端获取后保存到本地,记录文件更新时间,用
child_process.exec
通过系统命令打开 VSCode 并打开编辑 - 客户端
while(1)
不断发送 GET 请求,node 端对比文件最新更新时间与记录时间,更新则发送文件给客户端 - 最后,客户端解析、更新文件,执行
这其中有一个有意思的点:
客户端虽然通过 while(1)
重复发送请求,但其获取请求内容后会有 await
:
const _res = await _req.loadString()
而 node 端在收到请求后的响应是放在一个1s的 setTimeout
里的:
setTimeout(() => {
// 判断文件时间
const _time = fs.statSync(WIDGET_FILE).mtimeMs
if (_time === FILE_DATE) {
res.send("no").end()
return
}
// 同步
res.sendFile(WIDGET_FILE)
console.log('[+] 同步到手机完毕')
FILE_DATE = _time
}, 1000)
因此是通过 node 端的延迟响应,来达到控制客户端 while
节流的效果。
不足与期望
- 布局单一,无法获取容器宽高(想做不同屏幕上的布局适配就不可能了)
- 背景无法透明(设置透明度0,结果是黑色底)
- 更新频率无法设置(只能设置比默认频率更晚...)
- 无法数据驱动视图更新(也许可用接口/本地存储)
示例
最后po一下我的b站榜单组件,点击可直接跳转到B站App的相应视频:
(左上角是一个乱入的土味情话小组件,约会前滑开手机看看...)