您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
使用Observable-Swift构建复杂iOS UI
 
来源:网络 发布于: 2017-5-5
   次浏览      
 

对于移动应用而言,用户体验非常重要,在开发App的过程中往往很大一部分精力都是用于UI界面的构建与优化。对于交互逻辑比较简单的页面,实现思路比较简单直白。但如果交互较复杂,涉及诸多变量、事件、UI组件,而且它们之间还会相互依赖而变化的话,写起代码就比较棘手了。按常规的思路不是不能写,只是写起来非常繁琐,而且可维护性很差。

遇到难以解决的问题,有时候是问题本身很难,但也有时候是因为思考问题的模式不对,才造成问题看起来“难以解决”。其实这种复杂交互问题,早已在Web前端开发中被大家所注意到。从jQuery到Angular、再到React,就是人们在尝试解决复杂交互开发难题的过程中的产物。

jQuery是典型的命令式的思路,每次用户触发了事件,就在事件handle中去直接改写所有涉及到的变量、DOM元素等。然而随着需求的增加,handle中需要处理的东西会越来越多,最终导致基本无法维护。

Angular、React则采用了另外一种思路,它们都主张声明式的UI。命令式编程和声明式编程的区别如下:

1.命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。

2.声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。

在Angular、React中,对数据的修改会自动反映到UI上。这样一来就无需写那些繁琐的UI控制代码,有点儿WYSIWYG(What You See Is What You Get)的感觉。回到iOS来讲,我们可不可以借鉴这种做法呢?让UI随着model的变动而变动?从而大幅减少直接操作UI组件的代码?

Objective-C的KVO(Key-Value Observing)就是非常适合实现这种效果的特性。它源于设计模式中的观察者模式,当某个对象的keypath值变化时,可以主动通知其他的对象。利用KVO,我们可以监听model的变化以自动更新UI。

不过Swift没有KVO的直接实现,需要引入Objective-C运行时来实现:继承NSObject,并在要观察的属性前添加dynamic关键字。不过这样总感觉别扭,而且KVO的那一套API用起来也比较麻烦。那么,有没有其他的解决方法?Observable-Swift利用了Swift的属性观察 (Property Observers) 达到了类似的效果。

Observable-Swift

Observable-Swift是Google的一个员工slazyk开发的swift库,它主要提供了2个东西:

1.Observable<T>:提供了value observing的功能

2.Event<T>:提供了subscribable events的功能

具体的用法直接参考其文档即可,很容易就可以理解。

案例

空说无益,来看一个需求。比如有个App,需要用户填写他最喜欢的三个中国城市,效果图如下:

本文配套的示例工程放在GitHub上了:https://github.com/hustlzp/Observable-Swift-Example,建议可以下载下来,pod install --no-repo-update安装下依赖库,然后编译安装到手机上试试。纯用文字描述需求还是很累的,用一下App就什么都清楚了...

还是分析一下需求吧,这个页面需要实现的功能要点如下:

1.搜索城市:输入文字,可搜索城市,搜索结果以列表的形式列在下方

2.添加城市:点击搜索结果的某一行,即可添加城市,添加后,城市出现在textField的左侧

3.删除城市:在textField为空时,按下键盘的backspace键,最新添加的tag被选中;再按一下backspace,即可删除;如果按下的是字符,则取消删除

4.完成:输入1-3个城市后,可点击右上角的“完成”按钮

同时,为了提升用户体验,在交互的过程中,还需要实现如下的交互细节:

5.textField的placeholder:在尚未添加城市时,显示“输入城市名称”;在添加的城市小于3个时,显示“最多三个”;在是

6.右上角的“完成”按钮:在添加城市后、textField没有文字、且未处于预删除状态时,enable;否则disable

7.提示文字:在尚未添加城市时,textField下方显示“添加城市,最多不超过三个”;添加城市后,隐藏

8.标签过长时,向左滚动:当某城市过长时,需要向左滚动,以让textField可见

单看文字的表述是不是觉得比较复杂?建议安装一下示例项目的DEMO App,用一用,就知道上面一大排文字到底在说什么了。

分析

OK,现在我们来分析如何实现这个页面。

如果采用传统的方法,那么代码写起来会非常复杂。但如果采用“监听model的变化以自动更新UI”的思路来分析,会清晰很多。

我们可以把上面提到的东西划分为三类:Model、Action、UI。

1.Model:表示持有数据/状态的变量

2.Action:用户交互

3.UI:页面UI组件的变化

三者之间的关系是怎样的呢?

1.Model更新UI组件、也会更新其他Model

2.Action更新Model

根据以上原则,我们可以根据App的需求,做出如下的划分:

Model:

1.tags: 已经添加的城市标签

2.matchedTags:城市的搜索结果

3.textFieldText:textField的text值

4.prepareRemoveTag:是否处于预删除城市的状态

Action:

1.textField输入字符

2.textField有backspace键按下

3.tableView有row被按下

UI:

1.这个就不展开说了,太多了

示意图:

这样一分析,就清晰多了。Observable-Swift在上图中扮演的角色就是使用Observable<T>声明Model变量,然后在afterChange中添加代码即可。

代码的完整实现就不列出来了,只是把上面的分析用代码表达出来而已。举个小例子吧,tags的变化会影响到5个UI变化,同时会更新textFieldText的值,代码如下:

private var tags = Observable([String]())

tags.afterChange += { (_) in
// TextField placeholder
if self.tags^.count == 0 {
self.textField.placeholder = "输入城市名称..."
} else if self.tags^.count >= 3 {
self.textField.placeholder = ""
} else {
self.textField.placeholder = "最多三个"
}

// TextField left constraint
self.textField.snp_updateConstraints { (make) -> Void in
if self.tags^.count == 0 {
make.left.equalTo(self.tagsView.snp_right)
} else {
make.left.equalTo(self.tagsView.snp_right).offset(8)
}
}

// Update tagsView
self.tagsView.updateTags(self.tags^)

// scrollView滑动到最右边
self.scrollView.setNeedsLayout()
self.scrollView.layoutIfNeeded()
if self.scrollView.contentSize.width > self.scrollView.bounds.size.width {
let bottomOffset = CGPointMake(self.scrollView.contentSize.width - self.scrollView.bounds.size.width, 0)
self.scrollView.setContentOffset(bottomOffset, animated: true)
}

self.updateTableViewVisibility()
self.updateFinishButtonEnable()

// Empty textField
self.textFieldText <- ""

局限性

大家可能注意到,上面的图中出现了某个UI组件同时受多个Model影响的情况。比如tableView的hidden同时与tags和textFieldText相关。如何用Observable-Swift自然地表达这个过程呢?Observable-Swift提供了一个未写入文档的类PairObservable,用法如下:

(tags & textFieldText).afterChange += { (_) in
// Do something
}

tags & textFieldText的类型为PairObservable<Observable<[String]>, Observable<Bool>>。这种用法的缺点在于,需要引入一个类变量去保存tags & textFieldText的值以防其被dealloc。略显累赘。

如果相关的Model超过2个,你会下意识地这么写:

(tags & textFieldText & prepareRemoveTag).
afterChange += { (_) in // Do something
}

它生成的并不是PairObservable<Observable<A>, Observable<B>, Observable<C>>类型,而是PairObservable<PairObservable<Observable<A>, Observable<B>>, Observable<C>>,虽然可能达到监控变化的效果,但总感觉非常地hacky...

结语

当然还有其他的方法来应对复杂交互UI的开发问题。比如最近一直很火热的ReactiveCocoa,利用到了函数响应式编程的思想。比如还有人将Redux的思想引入iOS中,于是有了ReSwift这个项目。

   
次浏览       
 
相关文章

手机软件测试用例设计实践
手机客户端UI测试分析
iPhone消息推送机制实现与探讨
Android手机开发(一)
 
相关文档

Android_UI官方设计教程
手机开发平台介绍
android拍照及上传功能
Android讲义智能手机开发
相关课程

Android高级移动应用程序
Android系统开发
Android应用开发
手机软件测试