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

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

因为准备做不止一个控件的Demo测试,又不愿意每个控件开一个ViewController,想所有的Demo都重用同一个页面,于是妖蛾子就出来了。(背景:这个Demo的具体实现部分和ViewController是分开的两个File)

你看到的是非授权版本!爬虫凶猛,请尊重知识产权!

转载请注明出处:http://conanwhf.github.io/2016/01/03/UISlider/

访问原文「UIController中Slider监听回调具体实现的分离」获取最佳阅读体验并参与讨论


最主要的问题出在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摆看吗?😄)至此,这个歪门邪道解决的方案全部完成。
整个项目的代码在这里,具体这个Demo的实现文件是ShowTestsController.swift