转载

UIController中Slider监听回调具体实现的分离

作为初学者,基础的几个UI控件必须要熟练,现在的我还处于打开Xcode在控件表里面瞎找,看着谁好像是我想要的就往StoryBoard里面拖的阶段……正式写了点东西以后,越来越感觉到,该认真把控件过一遍了。上周写了这么个UIView的Demo,本意是看看各种效果,结果各种状况,花了不少时间。最终效果如下:

UIController中Slider监听回调具体实现的分离

因为准备做不止一个控件的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 “……调试过程就不多提了,直接说结论吧:

  1. 错误信息” unrecognized selector sent to instance 0x7feecbe10260 “中的那个内存地址就是参数”target“的指针,系统会根据你传的那个参数,在相应的类里找你注册的action,通常这是一个Controller,这也是”代理“的意义所在
  2. 所以监听的回调函数要写在Controller的实现里,或者是另一个代理的实现里,第二种方式有点复杂,按下不表
  3. 注册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

正文到此结束
Loading...