继续上一话中的计算器Demo。上一话讲到类必须被初始化。类中的属性也必须被初始化,所以你不能仅仅声明而不给它一个处置,那么问题来了,我们从storyboard中拖拽的@IBOutlet为什么仅仅有声明而不须要初始化呢,这是由于它的类型依然是一个optional,在你初始化之前已经被赋值为nil了,这也就是为什么你不须要再初始化它的原因。
@IBOutlet weak var display: UILabel!
然而既然它是一个optional类型的,那为什么UILabel后面是“!”而不是“?”呢。对于实际语言而言。它们没有差别,都是可能为一个UILabel的意思。可是实际的使用方法不同,这都是编译器帮你做的,假设我们把后面的感叹号改成问号会怎么样?以下的部分会报错,提示它没有text这个成员:
我们当然能够给display加入一个。来解包
display!.text = digit
由于拖拽生成的原因。左边的xib界面初始化之后和右边的viewcontroller关联,那么这些@IBOutlet就已经被初始化了而且是永久初始化,假设我们每次用的时候都要加一个“!
”。那实在太耽误事了。所以拖拽生成的变量类型后面是自己主动添加的“。”表达了这个变量尽管是一个optional,可是它已经被解包了,我们在使用这个变量的时候就能够不加。
如今我们须要一个returnbutton,用来表示输入完了一个待操作的数,我们复制一个button,改变它的值。
凡是Unicode的字符都能够被我们使用,包含汉字和表情。
把这个button与vc相关联,它不须要传參数,所以參数类型能够是AnyObject的,方法取名enter
@IBAction func enter() {
}
enter的作用是把我们label现实的数字存入栈中。以待兴许的操作。
代码中该怎样写呢?点击了回车键,那么我们之后的输入将是一个新的数,所以须要把标志位改动:
@IBAction func enter() {
userIsInTheMiddleOfTypingANumber = false
}
执行看看:
它清除显示屏的功能实现了可是为什么回车会出如今label中呢。相信你已经猜到了由于我们之前复制button的关系。回车键依然关联在appendDigit方法中,我们仅仅须要右键点击button在弹出的菜单中点“X”取消这个关联就OK了:
如今我们创建一个数组来存储运算的值。你能够看到怎样定义以及初始化一个数组
var appendStack:Array<Double> = Array<Double>()
由于Swift语言是强语言类型。所以我们能够不声明类型:
var appendStack = Array<Double>()
你想要在点击回车的时候把当前label的值增加栈中。假设你这样写:
appendStack.append(display.text!)
你会发现报错。由于appendStack是Double类型的,而display.text即便拆封之后依然是个String类型的,那么怎么办呢。
这里要引入一个新的东西,我们叫它计算属性:
var displayValue:Double {
get{
}
set{
}
}
在定义的时候后面加一对花括号,然后在当中在get和set方法,这个属性的作用是计算display.text的值转成Double,完整的displayValue代码:
var displayValue:Double {
get{
return NSNumberFormatter().numberFromString(display.text!)!.doubleValue
}
set{
display.text = "\(newValue)"
userIsInTheMiddleOfTypingANumber = false
}
}
如今我们在enter方法中做改动:
@IBAction func enter() {
userIsInTheMiddleOfTypingANumber = false
appendStack.append(displayValue)
println("appendStack = \(appendStack)")
}
执行来试试:
按回车已经不再显示了,中控台信息:
如今我们来添加运算符button。它们依然公用一个action方法:
和回车一样。我们依然能够在特殊符号中找到数学运算符:
拖拽定义一个新方法operate,记得sender类型写UIButton,获取一下button的值:
@IBAction func operate(sender: UIButton) {
let operation = sender.currentTitle!
}
接下来展示swift中的操作流,老头说swift的操作流是很强大的,比其它语言强大太多,这一点我深有体会,我敲swift代码已经上万行了。控制流的确很好用和高效。并且很直观。
首先展示一下乘法运算:
@IBAction func operate(sender: UIButton) {
let operation = sender.currentTitle!
switch operation{
case "×":
if appendStack.count >= 2 {
displayValue = appendStack.removeLast() * appendStack.removeLast()
enter()
}
// case "÷":
// case "+":
// case "−":
default: break
}
}
removeLast()方法会删除数组的最后一个元素。同一时候返回它的值,我们能够执行一下。输入顺序每输入一个操作数,点击一下回车,然后这个数就会被加到栈里,输入两个数之后点击乘号button进行运算。结果例如以下:
switch operation{
case "×":
if appendStack.count >= 2 {
displayValue = appendStack.removeLast() * appendStack.removeLast()
enter()
}
case "÷":
if appendStack.count >= 2 {
displayValue = appendStack.removeLast() * appendStack.removeLast()
enter()
}
case "+":
if appendStack.count >= 2 {
displayValue = appendStack.removeLast() * appendStack.removeLast()
enter()
}
case "−":
if appendStack.count >= 2 {
displayValue = appendStack.removeLast() * appendStack.removeLast()
enter()
}
default: break
}
能够看到数组中保存了最后的运算结果,之后完好后面几个button的代码,你会发现复制过去的时候代码结构是相似的。假设这样写那么显然很不美观,我们须要新建一个方法把这些反复的方法写进去:
func performOperation(operation:(Double,Double)-> Double)
{
if appendStack.count >= 2 {
displayValue = operation(appendStack.removeLast() , appendStack.removeLast())
enter()
}
}
能够看到有些有意思的东西,我们这种方法的參数operation的类型是一个函数,没错,在swift中函数常常作为參数类型被传递。那么在case中我们该怎样写呢,可能会让你大吃一惊:
case "×":
performOperation({
$0 * $1
})
这里应用了闭包的简化形式,我们无须指定參数类型。由于Swift是强类型的,它能够自己主动识别类型,而假设你没有取參数名的话,那么$0和$1代表前两个參数,闭包中的结构是一个方法,有两个參数。它的返回值是两个參数的乘积,这也符合performOperation的定义。
还有更酷的!假设你有一个类似于performOperation的方法,那么在调用时方法中的最后一个參数能够写到括号外面,比方:
case "×":
performOperation( ){$0 * $1}
假设前面有其它參数,能够继续写到括号里,假设仅仅有这一个參数,那么圆括号也能够省略了:
performOperation {$0 * $1}
完整的方法代码:
@IBAction func operate(sender: UIButton) {
let operation = sender.currentTitle!
switch operation{
case "×":
performOperation {$0 * $1}
case "÷":
performOperation {$1 / $0}
case "+":
performOperation {$0 + $1}
case "−":
performOperation {$1 - $0}
default: break
}
}
能够看到在-和/这样的不满足交换律的运算中我们调换了位置,这是希望栈中的最后一个元素总是能够被运算的。你能够回想最早的代码。如今的写法相当的简洁!
如今我们新增一排button,首先是平方根:
我们仅仅须要在控制流中添加一个case就好了,例如以下:
case "√":
performOperation {sqrt($0)}
sqrt是swift自己的求根方法,你会发现报错了。原因是我们之前的performOperation中的闭包都是有两个參数的,而这个case中仅仅有一个參数。那么该怎样改动呢,我们须要复制performOperation的代码然后做例如以下改动:
func performOperation(operation:Double-> Double)
{
if appendStack.count >= 1 {
displayValue = operation(appendStack.removeLast() )
enter()
}
}
你会发现这两个方法的名字全然一样。可是我们不用操心,由于swift会依据參数的不同自己主动调用应该调用的方法,如今我们来试验一下,输入36,点击开方button:
实现了。就是这么简单。
如今还对界面做一些优化,你会发如今竖屏状态屏幕浪费了太多的空间。而你不可能拖动每个button去设置相互之间的间距,这样太浪费时间。
首先左下角有点空。我们新增一个空button来占位。而且保证它不触发不论什么action
我们希望这些button能依据屏幕的大小自己主动稀疏布局,保持button群到各边框的距离相等。我们应用布局button。这排button在屏幕下方:
点开第一个button我们能够看到一些选项:
我们能够选择左对齐、上对齐、居中等等,可是这不是我们想要的选项,我们点开第二个button:
我们选中这两项标示全部button都有同样的宽和高:
如今来设置间距:
还记得我们对齐时候看到的蓝线么,这个8像素点得间距就是蓝线对齐的默认像素点间距,另外要保证红线是亮的状态而不是虚线状态,这样约束才干被加上。加上约束的界面:
哇偶。是不是眼花了。点开视图大纲,能够看到这里的小黄圈,假设它是红色的证明我们加入的约束有冲突的地方。
还记得我们上一话中介绍的么?这里的黄色标示有些约束和我们预想的不同,那么点开黄色小圆圈。随便点击一个条目。作例如以下设置:
记得勾选以下的Apply to all views in container。这样全部的警告都会被施以同样的操作。
如今警告没有了。看起来非常不错呦。执行一下看看:
在Iphone6上显得非常修长。
切换到横屏:
非常赞对不正确?
假设要清除约束使用第三个button:
上面部分是清除所选项的约束。以下部分是清除全部的约束。
可是实际我们的计算器是有问题的。由于我们的处理引擎这部分是独立的,这就牵涉到了MVC设计模式的问题,之后我们全部的代码都要满足MVC设计模式,如今来了解一下MVC:
MVC是一个基本机制,用于分类。左边的Model(模型层)包含我们的模型,在计算当中,计算是模型。
控制层控制视图怎样显示,而视图是控制要用到的模型,在view中用到的时相当常规的界面元素。我们用道路上的指示线来作比:
控制器和模型之间是虚线说明同意暂时跨越。可是过去之前你须要观察一下。控制器(C)必须全然掌握模型(M),由于控制器的职责就是展示模型给用户。所以控制器拥有全然的訪问权限,这是个单向的箭头
相同的控制器(C)也能够到达视图(V),由于控制器要向视图发送命令,由于控制器设计视图,我们在单向箭头上加了一行绿色小字,outlet。由于当我们在控制器中有一个属性指向视图,这个属性的名字就是Outlet。
那么模型(M)和视图(V)之间呢?答案是永远不能!
由于模型是全然独立于UI的。这也是为什么他们之间採用了双黄线隔开。
那么视图(V)向控制器(C)发送信息么?它们之间是一种盲通信。并非随意的通信,他们之间的建立通信的方式是控制器生成一个Target。然后视图使用action向控制器反馈信息。比方我们点击了一个button或者其它页面上的操作。页面并不知道控制器是一个什么样的控制器,它仅仅知道页面上产生了动作。并反馈给控制器,这是一种盲目的、简单的、结构化的通信方式。
那么有一些复杂的操作怎么办呢。比方should、will和did。我拖动屏幕这是一个did的动作,我按住了屏幕准备拖动它的时候这是一个will的动作,遇到这样复杂的动作时怎么办呢。视图的做法是它把这些问题抛给了它的代理,代理依然是控制器中的东西,这些代理来回答will、did、should怎么做这种问题。
另外一个非常重要的点是:视图不应该持有它所展示的数据。数据不能作为它的属性。比方你的iphone中有一万首歌。你不能期望它持有一万首歌展示给你看。第一,这样做非常低效,第二,这一万首歌应该在模型层。一些控制器来负责选择展示哪些歌曲。然后从模型中取到并在视图层中展示。
那么当我们滑动屏幕期望能获取很多其它歌曲的时候我们须要怎么做呢?这是第二种代理,我们叫它数据源DataSource。数据源并不去处理诸如will、should这种处理,他回答有多少歌曲并把数量返回给视图这种工作。此时视图为这一万首歌开辟空间。所以控制器(C)的作用就是给视图(V)解释并格式化这些模型(M)提供的数据。
那么问题又来了,模型能够和控制器通信么?显然不行。可是假设数据改变了怎样通知我们的控制器呢?它依然使用了这样的盲通信的方法。我们把模型想象成一个电台,它通过广播的方式告知别人自己的变化,IOS把这样的技术叫做Notification和Key Value Observing(KVO)。一旦控制器接收到了模型变化的消息,它会通过那个绿箭头向模型索取它的变化信息。
那么视图能接受模型的广播么?或许能够但不要这么做,这违背了MVC模式。
我们能够利用这些知识做些小应用,假设project非常复杂呢?我们须要多个MVC。多个MVC的叠加能够实现复杂功能。