IOS开发之多重MVC以及手势
继续学习IOS开发,这一次学习的是多重MVC架构以及手势识别。
把之前的View接口化
首先需要把之前的FaceIt程序的可以改变的参数单独提取出来。
//changable parameters
//1 means smile and -1 means sad.
var smileFactor: CGFloat = 1
var eyeOpen: Bool = false
var eyeBrowTiltFactor: CGFloat = -1
var lineColor: UIColor = UIColor.black
如果想要在main.storyboard里面实时可以看到view里面的情况,只需要在类前面加入如下的一行代码。
同样的,可以使用以下的放下把这些参数放到Interface里面。
这样子的话就可以在Storyboard的侧面的边栏里面了。
但是这个时候会发现,如果在FaceView里面直接改变变量的初始值,Storyboard的图像并不会重新渲染。要改变这个,需要使用变量的didSet属性,意思是如果变量在某种程度上更改了,那么就需要执行dieSet括号内的内容。
var smileFactor: CGFloat = 1 { didSet{setNeedsDisplay()}}
Model部分
接下来我们将会完成MVC中model的部分,我把它命名为FacialExpressionModel。这个时候问题来了,是使用class呢,还是使用struct呢?因为看到stanford的老师在这个demo里面使用了struct。后来查阅了相关的资料,发现了它们根本的区别在于:
struct传值,class传引用
结构(struct)是一种值类型。也就是说,结构实例是分配在线程堆栈上的,结构本身是包含有值的,而不是像类一样的引用类型,包含的是所指向堆当中的引用(指针)。也就是说,结构的生存周期与简单类型(int,double等)相同的。
你使用的是一个对class实例的引用。而你使用的不是对一个struct的引用。(而是直接使用它们)
当我们将class作为参数传给一个方法,我们传递的是一个引用。struct传递的是值而非引用。
紧接着会有一个让人非常困惑的问题,到底model写的内容是什么呢?其实目前来看,我们的这个小应用都只是在控制view,所以目前只需要用到view和viewcontroller,那么model只需要模拟把各种情况写出来就好了(计算机的是结果,因为要显示的不是情况)。
model返回的是view的各种情况类型,一般用enum。
关于enum有以下两点需要注意:
1. relative值的使用
可以如下方式定义relative值,然后如下方式使用。
//定义
enum Barcode {
case UPCA(Int, Int, Int)
case QRCode(String)
}
//使用
switch productBarcode {
case .UPCA(let numberSystem, let identifier, let check):
println("UPC-A with value of \(numberSystem), \(identifier), \(check).")
case .QRCode(let productCode):
println("QR code with value of \(productCode).")
}
- rawValue的相关使用
作为相关值的替代,枚举成员可以被默认值(称为原始值)预先填充,其中这些原始值具有相同的类型。
enum ASCIIControlCharacter: Character {
case Tab = "\t"
case LineFeed = "\n"
case CarriageReturn = "\r"
}
原始值可以是字符串,字符,或者任何整型值或浮点型值。每个原始值在它的枚举声明中必须是唯一的。当整型值被用于原始值,如果其他枚举成员没有值时,它们会自动递增。
enum Planet: Int {
case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
let possiblePlanet = Planet(rawValue: 7)
//这里就是possiblePlanet就是Uranus
关于这些类型的切换,有一个要处理的问题就是边界问题。比如如果我要从伤心、平静、开心三种状态之间切换,【更开心】这个操作有可能会向上跳一级,或者什么都不发生。以下的代码可以轻易的解决这个问题。
func moreSmile() -> mouth{
return mouth(rawValue: rawValue + 1) ?? .smile
}
func moreSad() -> mouth{
return mouth(rawValue: rawValue - 1) ?? .sad
}
这里插播一个swift的命名规范。
其中类名、协议名、结构体、枚举等类型的命名规范通常是,大写字母作为开始,其余单词首字母大写,例如类名HelloWorldApp。
完成controller调控两大板块
我们已经知道了model里面是一个struct,所以我们需要一个变量来装载它(class类似的)。然后我们需要让他时刻更新view。方法和上面很类似,这里把关键函数updateUI贴出来。
func updateUI(){
switch FacialExpression.eye{
case .open:
faceView.eyeOpen = true
case .close:
faceView.eyeOpen = false
default:
break
}
switch FacialExpression.mouth{
case .smile:
faceView.smileFactor = 1.0
case .relax:
faceView.smileFactor = 0.0
case .sad:
faceView.smileFactor = -1.0
default:
break
}
switch FacialExpression.eyebrow{
case .happy:
faceView.eyeBrowTiltFactor = 1.0
case .relax:
faceView.eyeBrowTiltFactor = 0.0
case .sad:
faceView.eyeBrowTiltFactor = -1.0
default:
break
}
}
手势
手势的基类是UIGestureReconizer,但是这是一个抽象的概念,真正的类是各种手势的GestureReconizer( 比如pinch和swipe)。而且必须View才能使用它们。一般来说是controller添加到view。不过一些基本的gesture可能本事就在view上。当识别成功,会使用handler来传输信息,如果不涉及和model沟通,那么只需要在view中,否则需要在controller中设立。
接下来尝试使用SwipeGestureRecognizer来改变Model的状况(请看下图对比改变Model和只是改变View的区别)。
let happyReconizer = UISwipeGestureRecognizer(target: self, action: #selector(FaceViewController.happier))
happyReconizer.direction = .down faceView.addGestureRecognizer(happyReconizer)
分别是创建reconizer、设置属性、以及添加进去view里面。
我们来看UIGestureReconizer的基本格式(target:self/view, action: #selector(controller/view.函数名))。
所有的对应的处理函数func前面都要加入@objc。
@objc func happier(){
FacialExpression.mouth = FacialExpression.mouth.moreSmile()
}
同样的reconizer也可以通过在storyboard拖拽加入然后使用ctrl在controller中建立联系。
@IBAction func eyeChange(_ recognizer: UITapGestureRecognizer) {
if recognizer.state == .ended{
switch FacialExpression.eye{
case .open:
FacialExpression.eye = .close
case .close:
FacialExpression.eye = .open
default:
break
}
}
}
多重MVC
这里我们使用xcode提供给我们的MVC,常用的分别有以下三种。
第一种是TabBar。
第二种是Split,常用在ipad。左边一般是master,右边叫做detail。potrait的时候一般master隐藏在左边,可以抽屉式抽取出来。
第三种是NavigationControl。
需要知道的是,ios在处理多级菜单的时候,是使用stack来存的。每一个页面push进去,然后返回就是pop。这一切是通过一个更高一级的controller(比如NavigationControl)来控制的。
在这个root controller,里面有一个变量(array)来记录旗下的所有MVC。
var viewControllers: [UIViewController]? {get set}