目录

从零开始的iOS开发-20-计算器APP

从零开始的iOS开发: 20 | 计算器APP

目录


一、开发环境

  • 开发工具: Xcode 12.2(集成开发平台)、Simulator(模拟器,Xcode自带)
  • 开发语言: Swift 5
  • 界面搭建: Storyboard(故事板)

二、基础知识

1.往期知识点

2.简易自动布局——Stack View

Stack View(堆栈视图)类似于HTML的盒模型,你可以把它理解为一个容器。我们可以利用Stack View在水平(或垂直)方向堆叠多个子视图。

一个Stack View可以用来封装各种UI控件以及其他Stack View,通过添加对Stack View的约束(设置该View到父View的距离)可以实现UI控件的定位和布局。

1)添加Stack View

https://i-blog.csdnimg.cn/blog_migrate/58e2b7d4592013cd47400c53167258ea.jpeg

2)添加约束

https://i-blog.csdnimg.cn/blog_migrate/ecc074a00094822c0eab84d77a5d807a.jpeg

3)查看约束

https://i-blog.csdnimg.cn/blog_migrate/26578e723c625fa450adf7b9953d26fa.jpeg

4)设置Stack View的属性

https://i-blog.csdnimg.cn/blog_migrate/5633e59aecf565769eb270b5aa4348ba.jpeg

三、实验步骤

在MVC架构下,用故事板来开发一个简易计算器APP需要以下步骤:

  • 搭建界面——添加必要的UI控件,并实现UI控件的自动布局
  • 连接UI控件与代码
  • 不断完善代码,实现需求的功能

应用界面的设计如下,要求计算器至少能实现整数和浮点数的加减乘除运算。

https://i-blog.csdnimg.cn/blog_migrate/78ca3b62c97d6b440e1c3aad9607652a.png


1.先在Xcode建立一个APP项目

https://i-blog.csdnimg.cn/blog_migrate/910289ceb2e176eb271eab052d2996ea.jpeg

https://i-blog.csdnimg.cn/blog_migrate/ba4d65dfd3a9f88df375453d8b9dc3ce.jpeg

https://i-blog.csdnimg.cn/blog_migrate/f6d38ec2917267a144690d40559dc5b0.jpeg

2.界面搭建

1)打开Main.storyboard主故事板

同时,模拟器和View视图统一选择iPhone 8 Plus

https://i-blog.csdnimg.cn/blog_migrate/27027947cc7589e5aa12bfb85ee5b802.jpeg

2)先添加一个Button(按钮)控件

https://i-blog.csdnimg.cn/blog_migrate/4c63331d30083ec5b6f8ea77adc5aede.jpeg

通过Inspector设置Button的属性

https://i-blog.csdnimg.cn/blog_migrate/8c8d4c5e110a10f45efd71ef80876191.jpeg

将Button显示的文字“Button”修改为“1”,文字大小Font设置为30,文字颜色Text Color暂不修改

https://i-blog.csdnimg.cn/blog_migrate/0ffb9620607403d5d01e6ef067f7164c.jpeg https://i-blog.csdnimg.cn/blog_migrate/620bfb45e151a6520f767ea601ce7575.jpeg

将Button的背景颜色Background修改为Yellow(或其他你喜欢的颜色)

https://i-blog.csdnimg.cn/blog_migrate/f1c18778d44673e7d985ad08e4e8ff70.jpeg https://i-blog.csdnimg.cn/blog_migrate/6c0faafb252da6370f602f263c8f807d.jpeg

修改Button的宽和高为50

https://i-blog.csdnimg.cn/blog_migrate/b499e558ebf083bdd257ff99ced25916.jpeg

3)添加容器

方法是按住鼠标左键框选要放入容器里的控件,完成后点击左下角的Stack View

https://i-blog.csdnimg.cn/blog_migrate/ae6e1d5618cf858063fabe52c4194d30.jpeg

https://img-blog.csdnimg.cn/b3970ff54e3f4ceba2a90a83f3b04268.gif

可以看到Button1已经被放入Stack View里了:

https://i-blog.csdnimg.cn/blog_migrate/f24f687a8e964009fba763f13752235a.jpeg

4)批量添加Button(包括它外面的容器)

一个比较快捷的方法是,框选你要复制的控件,然后按住option键往旁边拖拽

https://i-blog.csdnimg.cn/blog_migrate/70d1859caec46d20e75d28ea11d991d3.gif

如果不清楚这个方法,一个Button一个容器地添加也可以,就是没上面的方法快。

5)为一行的Button添加容器

框选+Stack View

https://i-blog.csdnimg.cn/blog_migrate/a9d814fd5309ad78d3a2e82ac6a45fab.jpeg

https://i-blog.csdnimg.cn/blog_migrate/63adaab742b12341f7cc8fc8fb5ba9be.jpeg

6) 继续往下添加Button

我们继续往下复制,总共需要4*5个Button。依然使用 框选+按住option+拖拽 的方法

https://i-blog.csdnimg.cn/blog_migrate/b95a34584f6659b833a39c38cd2041a4.jpeg

7)把所有Button封装进一个容器

https://i-blog.csdnimg.cn/blog_migrate/6b14872e4c619b265b7303166254f6eb.jpeg

8)在顶部添加一个Lable(标签)控件

https://i-blog.csdnimg.cn/blog_migrate/b2757a2c244bf2e6a7bf95497fcd8925.jpeg

同时,也将Lable封装进一个Stack View

9)将Lable和Button都封装进一个容器中

https://i-blog.csdnimg.cn/blog_migrate/7226c0fa4fbe8a22e7691c66ee5ff9ac.jpeg

https://i-blog.csdnimg.cn/blog_migrate/d1bf805d64729a18d29110c96be39f1f.jpeg

到了这一步,所有的控件和容器都以添加完成。接下来要做的就是为各个容器添加约束,实现自动布局。

10)为最外层的Stack View设置约束

添加4条约束:将其上下左右四个方向的距离设置为0

https://i-blog.csdnimg.cn/blog_migrate/8c3a1881e003df9dc4a62cb5b7cefab7.jpeg

https://i-blog.csdnimg.cn/blog_migrate/696b2ded08625aaa88a1ddd043a54c02.jpeg

11)设置包含所有Button的Stack View

这一步是实现自动约束的关键步骤,在左侧的Documen Outline中选择包含所有Button的Stack View,设置Stack View的属性

https://i-blog.csdnimg.cn/blog_migrate/7663f507e19c557b4380fecd2f4e16c4.jpeg

Alignment 设置为Fill,Distribution设置为Fill Equally,Spacing设置为10

https://i-blog.csdnimg.cn/blog_migrate/56e3b586544bd00a355343ff6e20ff78.jpeg

12)为每一行的Button添加约束

添加2个约束:左右距离为0

https://i-blog.csdnimg.cn/blog_migrate/1a9a7765112358c0032f8d3e92031a6e.jpeg

完成后结果如下:

https://i-blog.csdnimg.cn/blog_migrate/d3141982c033cd30a5ce852dbbcfce07.jpeg

显然,我们需要设置包含一行Button的Stack View的属性

13)设置包含一行Button的Stack View的属性

https://i-blog.csdnimg.cn/blog_migrate/5b3315f853fe7c068821d7f4429296d7.jpeg

Alignment 设置为Fill,Distribution设置为Fill Equally,Spacing设置为10

https://i-blog.csdnimg.cn/blog_migrate/68a70e8c865ced352287ea83d6d6d270.jpeg

将所有包含一行Button的Stack View的属性Alignment 设置为Fill,Distribution设置为Fill Equally,Spacing设置为10

https://i-blog.csdnimg.cn/blog_migrate/c4a704ed0dd7f7daa83de831000de7de.jpeg

14)设置Lable的高

https://i-blog.csdnimg.cn/blog_migrate/cb7a4273ac2b4a4038f79d0a214a69f8.jpeg

https://i-blog.csdnimg.cn/blog_migrate/d626e1838714cc249c54badddd23afdf.jpeg

15)修饰背景和UI控件

将Lable的文字设置为0,颜色Color设置为白色,文字大小Font设置为50

https://i-blog.csdnimg.cn/blog_migrate/c600923527d8e2cf9bce26873214a966.jpeg

在Documen Outline中选择最外层的Stack View,将背景颜色Background设置为黑色

https://i-blog.csdnimg.cn/blog_migrate/45fbb1fbdfb444bff93c94bd57077e6c.jpeg

修改Lable文字的对齐方式(改为右对齐)

https://i-blog.csdnimg.cn/blog_migrate/787f310226ae32721a4f41d74ad10b27.jpeg

https://i-blog.csdnimg.cn/blog_migrate/db6ad2b34d1a7f00dc5b487eab29a9e4.jpeg

修改各个Button的文字

https://i-blog.csdnimg.cn/blog_migrate/b89025c4d954bc61eef9ca900c05ac68.jpeg

iOS支持特殊符号(例如π对应快捷键option+P,平方根对应快捷键option+V)

https://i-blog.csdnimg.cn/blog_migrate/c49f757ec32fea660016b9dd8a743f14.jpeg

https://i-blog.csdnimg.cn/blog_migrate/032f263b0d8da6c4130aa6f0b3e1c6b3.jpeg

修改Button的文字颜色和背景颜色

https://i-blog.csdnimg.cn/blog_migrate/7046da898c7c48714702306957091113.jpeg

https://i-blog.csdnimg.cn/blog_migrate/6112fcb139b5c59e36704016be68749b.jpeg

完成后:

https://i-blog.csdnimg.cn/blog_migrate/9cd8068cb6f14faf0cfef24a145957ce.jpeg

到了这一步,页面的搭建就已经完成了,可以试运行一下,看看有没有bug。

3.连接控件与代码

添加一个新的窗口,同时打开主故事板和ViewController.swift页面

https://i-blog.csdnimg.cn/blog_migrate/087afdb90642c07da52b63be427015b0.jpeg

由于这里不需要viewDidLoad函数,直接删掉

https://i-blog.csdnimg.cn/blog_migrate/28298d4ae8f3925d020b706f449d6915.jpeg

连接控件与代码,方法是选中控件,按住Ctrl+拖拽

将Lable连接到代码区:

https://i-blog.csdnimg.cn/blog_migrate/55e58c64fb18d9c5aeb733519bc69cd4.jpeg

将某个Button连接到代码区:

https://i-blog.csdnimg.cn/blog_migrate/4d91e1eb7e4e0496e932c55c678c7e38.jpeg

接着把所有Button都连接到touch代码块

https://i-blog.csdnimg.cn/blog_migrate/c73068ea5c18880e25ef0a4d456a3fb8.gif

通过鼠标悬停在代码区左侧的实心圆点上,可以查看连接的情况:

https://i-blog.csdnimg.cn/blog_migrate/e9beee46c7e707be6b6f58848e2ae695.jpeg

删除连接、重建连接请参考 的结尾部分

将所有控件与代码连接完可以试运行一下,看连接有无错误

4.补充代码,完善功能

1)添加代码,实现Lable的数字显示功能

https://i-blog.csdnimg.cn/blog_migrate/2d7803c6fbe750abd2daeef606741d88.jpeg

试运行一下,我们发现,每按一个数字键,之前的数字就被覆盖掉了,没办法显示2位及以上的数值。为了解决这个问题,我们采用下面的代码:

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var display: UILabel!
    
    @IBAction func touch(_ sender: UIButton) {
        let digit = sender.currentTitle!    //获取按钮值
        let textInDisplay = display.text!   //获取标签值
                
        if let mathematicalSymbol = sender.currentTitle {
            switch mathematicalSymbol {
                case "0","1","2","3","4","5","6","7","8","9":
                    if textInDisplay != "0" {
                        display.text = textInDisplay + digit
                    } else {
                        display.text = digit
                    }
                default:
                    break
            }
        }
    }
    

}

https://i-blog.csdnimg.cn/blog_migrate/0774edb78a44a3a5ccff7d7d8045c8c0.jpeg

通过Lable标签原来显示的数值textInDisplay与数字键代表的数值digit的拼接,实现了多位数字的显示。

2)继续完善代码

//
//  ViewController.swift
//  Calculator
//
//  Created by Apple on 2021/12/5.
//

import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var display: UILabel!
    
    var num1: Double?   //操作数1
    var num2: Double?   //操作数2
    var ope: String?    //运算符
    var IsFloat = false //浮点运算标志位
    var displayValue : Double {
        get{
            return Double(display.text!)!
        }
        set{
            display.text = String(newValue)
        }
    }
    
    @IBAction func touch(_ sender: UIButton) {
        let digit = sender.currentTitle!    //获取按钮值
        let textInDisplay = display.text!   //获取标签值
                
        if let mathematicalSymbol = sender.currentTitle {
            switch mathematicalSymbol {
                case "0","1","2","3","4","5","6","7","8","9":
                    if textInDisplay != "0" {
                        display.text = textInDisplay + digit
                    } else {
                        display.text = digit
                    }
                case "AC": display.text = "0"
                case "π": display.text = String(Double.pi)
                case "√": display.text = String(sqrt(displayValue))
                case "sin": display.text = String(sin(displayValue))
                case "+","-","*","/":
                    if textInDisplay.contains(".") {
                        IsFloat = true
                    }
                    self.ope = mathematicalSymbol   //记录运算符
                    self.num1 = displayValue        //记录操作数1
                    display.text = "0"              //清空计算器显示
                case "=":
                    if textInDisplay.contains(".") {
                        IsFloat = true
                    }
                    self.num2 = displayValue
                    display.text = calculation(num1: num1, num2: num2, ope: ope, IsFloat: IsFloat)
                    IsFloat = false
                    case ".":
                        if textInDisplay != "0" {
                            display.text = textInDisplay + digit
                        } else {
                            display.text = "0."
                        }
                    default:
                        break
            }
        }
    }
    
    //calculation:执行计算操作
    func calculation(num1: Double?, num2: Double?, ope: String?, IsFloat: Bool) -> String {
        if ope == nil {
            return "EOF"
        }
        if num1 == nil || num2 == nil {
            return "0"
        }
        var result: Double
        switch ope {
            case "+": result = num1! + num2!
            case "-": result = num1! - num2!
            case "*": result = num1! * num2!
            case "/":
                if(num1 != 0) {
                    result = num1! / num2!
                }
                else {
                    return "EOF"
                }
            default:
                return "EOF"
        }
        if IsFloat {
            return String(Float(result))
        } else {
            return String(Int(result))
        }
    }

    
}

不建议将整块代码直接负责到项目中,因为这有可能影响原来的(控件与代码的)连接。

要么只复制代码中方法的实现部分;要么删除所有连接,重新连接控件与代码。

3)运行

https://i-blog.csdnimg.cn/blog_migrate/bf9230df24465dd62b79e3035cc6e4c4.jpeg

https://i-blog.csdnimg.cn/blog_migrate/5d351544eb55be58ff143121133d0a6d.gif

https://i-blog.csdnimg.cn/blog_migrate/77257053e8248e98ded0f9890e0102a2.gif

如果发现模拟器旋转之后,APP的页面没有跟着旋转(没有变成横屏显示),那么可能是APP的配置文件关闭了旋转功能。下面介绍解决办法:

https://i-blog.csdnimg.cn/blog_migrate/783d8f65cd059c4dcdee0838b1270bab.jpeg

https://i-blog.csdnimg.cn/blog_migrate/19f0465db4838d108e8ee78c32aa4ace.jpeg

确保勾选了 Landscape LeftLandscape Right