使用纯 JavaScript 编写 iOS 界面

这篇文章介绍 JSBox 这个应用里面的图形界面部分如何实现。

目标是什么

做任何事情都要有目的,实际上在早期的规划里面,JSBox 并没有打算让用户去写界面,我一直觉得能够用脚本做一些类似 CLI 的事情,同时提供一些简单的交互,例如 Alert, Action Sheet, Input 等等就够了。

这样也确实能够解决不少问题,然而这样做其实最后的结果无非就是提供了一个更适合程序员的 Workflow,能够做些更底层一些的事情,在最终效果上并没有比 Workflow 更强大。所以我之后就在考虑如何为 JSBox 提供一个界面框架,他要满足以下几个特点:

  • 纯 JavaScript 编写,无须使用别的技术
  • 最小化的程序是极简的,不需要像 React Native / Weex 那样引入过多的概念
  • 提供的组件基于 iOS UIKit 基本组件,或是多个组件的组合
  • 目的是为脚本提供界面,而不是打造一个通用的 UI 框架,所以可以有妥协,没必要在视觉效果和性能上追求极致
  • 把 iOS 开发里面一些麻烦的问题在内部解决掉,避免用户写过多的代码

有了这几个目标之后我设计了一个非常简单的框架,这个框架就是目前 JSBox 正在使用的界面系统,他可以用来构建还不错的用户界面,比如说这个样例:

image

image

image

image

这个例子可以在这里找到:https://xteko.com/install?id=76 欢迎体验。

要解决什么问题

鉴于这是一个我自己从零开发的系统,我非常认真的考虑了这个问题,最终我认为这个框架要解决下面几个问题:

  • Type: 是什么,有什么样的特性
  • Properties: 有哪些属性
  • Layout: 位置和大小
  • Events: 接受的用户事件或是发出的消息
  • Subviews: 隶属于它的子视图

虽然说各个框架要解决的问题会有大大小小的不同,但是从大的方向上来讲都是大同小异的,所以最终我们会得到这样一种构建界面的方式:

$ui.render({
  props: {
    title: "Example"
  },
  views: [
    {
      type: "button",
      props: {
        title: "Tap Me"
      },
      layout: (make, view) => {
        make.center.equalTo(view.super)
        make.size.equalTo($size(100, 40))
      },
      events: {
        tapped: sender => {
          $ui.toast("Tapped")
        }
      }
    }
  ]
})

这个代码运行之后会弹出一个新的 UIViewController,并在他的 view 的中间显示一个 100 * 40 的按钮,上面写着 "Tap Me",点击之后界面将显示 "Tapped"。

对于一个脚本工具而言,让用户知道太多 UI 细节是没有必要的事情,所以其实用户并不会接触 UIViewController 这个概念,他只需要知道用 $ui.render 这个方法可以绘制界面,用 $ui.push 这个方法可以推进来一个新的界面,这两个方法的参数是完全一样的,除此之外的细节都没必要再管了。

正如你所见,这套系统的本质是用 JSON 数据来描述一个 Native 组件,下面我们就简要的讲一下如果简单地实现这么一个系统。

类型

我上面已经提到了,JSBox 里面每一个组件其实都是 iOS 原生组件的透出,或是多个组件的组合,并不会给你太多自定义的机会。所以在内部实现的时候,这一部分很简单,就是维护一个表,比如:

{
  "view": BaseViewFactory.class,
  "button": ButtonFactory.class,
  "slider": SliderFactory.class,
  ""
  ...
}

读取到 Factory 类之后初始化对应的实例就可以了,当有新的组件加入的时候,就写一个新的 Factory 类并把它添加到配置里面就可以了。JSBox 现在支持很多种类型的控件,可以在这里找到:https://docs.xteko.com/#/component/label

属性

一个 View 可能会有很多的属性,比如 label 有 text, font, textColor 等属性,而 button 可以有 title, type 等等属性,对于这些属性我们要做这么两件事情:

  • 能够把 JSON 数据里面写的内容映射到 view
  • 能够从一个 JavaScript Object 里面读取到属性
  • 能够为 iOS 原生的属性提供别的名字
top Created with Sketch.