作为初学者,基础的几个UI控件必须要熟练,现在的我还处于打开Xcode在控件表里面瞎找,看着谁好像是我想要的就往StoryBoard里面拖的阶段……正式写了点东西以后,越来越感觉到,该认真把控件过一遍了。上周写了这么个UIView的Demo,本意是看看各种效果,结果各种状况,花了不少时间。最终效果如下:
因为准备做不止一个控件的Demo测试,又不愿意每个控件开一个ViewController,想所有的Demo都重用同一个页面,于是妖蛾子就出来了。(背景:这个Demo的具体实现部分和ViewController是分开的两个File)
最主要的问题出在UISlider的监听上。网上UISlider的基本用法大把而且很简单,我照葫芦画瓢写了个包含一个Slider和Label的结构体,并且申明一个数组作config用:
class ConfigSlider { let label : UILabel let slider : UISlider let name : String init( n: Int, size : CGSize, max: Float, name: String, defaultValue: Float = 0) { // 各种计算初始化位置,略 var newFrame = CGRect(x: x, y: y, width: defaultLabelWidth, height: defaultSliderHeight) label = UILabel(frame: newFrame) label.text = name newFrame = CGRect(x: x+defaultLabelWidth, y: y, width: w - defaultLabelWidth, height: defaultSliderHeight) slider = UISlider(frame: newFrame) slider.minimumValue = 0 slider.maximumValue = Float(max) slider.setValue(defaultValue, animated: true) } } var sliderConfigs : Array <ConfigSlider> = []
我的设想是通过初始化一组这样的结构体,能够自动添加n项配置选项到View,包括配置的标题和当前的值,并且当滑动条变动的时候当前值的显示也会跟着变化。要做到这一点,就要监听Slider的变化。之前写过button的handler,很简单用addTarget就行,看看定义:
slider.addTarget(<#T##target: AnyObject?##AnyObject?#>, action: <#T##Selector#>, forControlEvents: <#T##UIControlEvents#>)
于是我就直接把监听的函数写到ConfigSlider的定义里面了:
func configChanged(sender: AnyObject?) { label.text = name + ":/(Int(slider.value))" }
而在addTarget的时候,犯了难:参数target指的是什么呢?所有的范例都是用的ViewController,这里也拿不到啊,看看这是个可选型,那么用nil试试看?编译完一跑,拖动Slider没有任何反应……好吧也许是需要一个sender,那么拿ViewController的试试?这次有反应了-直接挂掉,显示“ unrecognized selector sent to instance 0x7feecbe10260 “那么我把handler放到结构体外面试试、添加打印信息试试、用不同的参数试试……各种”试试“,各种” unrecognized selector sent to instance XXX “……调试过程就不多提了,直接说结论吧:
- 错误信息” unrecognized selector sent to instance 0x7feecbe10260 “中的那个内存地址就是参数”target“的指针,系统会根据你传的那个参数,在相应的类里找你注册的action,通常这是一个Controller,这也是”代理“的意义所在
- 所以监听的回调函数要写在Controller的实现里,或者是另一个代理的实现里,第二种方式有点复杂,按下不表
- 注册action的时候, 如果回调函数是有参数的,要加”:” !!!
这些测试,让我对”代理“的概念有了更深刻的理解,自学的新手就是这样啊,好多概念要慢慢摸索才会懂。但我的问题还没解决。之前说了,我的Controller是打算重用的,和几个Demo的具体实现分离,那么这个slider的回调,如果一定要写在Contoller中,那也太丑陋了……并且有几个Demo就得塞几个不同的回调,怎么能忍?!想了想,利用Swift中Function的特性(我并不知道OC中是不是也一样):
StepA: 在在ViewContoller中实现
//定义updateDemo(),具体写哪儿看着办 var updateDemo : (ShowController)-/>() = {_ in } if (某个判断条件A,判断出DemoA的情况) { //重定向到函数refreshUIViewTest updateDemo = refreshUIViewTest } //实现回调函数 func configChanged(sender: AnyObject?) { //将callback转成具体的实现,此时无需判断 updateDemo(self) }
StepB:在具体实现的File中
//注册监听函数 slider.addTarget(ctl, action: Selector("configChanged:"), forControlEvents: UIControlEvents.ValueChanged) func refreshUIViewTest(ctl: ShowController) { //TODO,任何具体的回调实现 }
这样一来,既实现了想要的功能,又做到了具体实现和ViewContoller分离。不过还有个小小问题:ConfigSlider作为一个小小的自定义控件,Label上的数值显示如果每次变化都要放在外面去修改刷新,那也太……不处女座了。想了想给它添加了这样的方法:
var value : Float{ get { label.text = name + ":/(Int(slider.value))" return slider.value } }
这样,每次当外界需要获取其变化后数值的时候,就自动刷新Label,而这个获取数值的操作(至少)必然是在监听handler中要用到的(不然难道做个slider摆看吗?:smile:)至此,这个歪门邪道解决的方案全部完成。
整个项目的代码在 这里 ,具体这个Demo的实现文件是 ShowTestsController.swift