跨越边界:从前端切图仔走进iOS开发(Swift版--上集)

本文简介

点赞 + 关注 + 收藏 = 学会了

本文将以前端开发者的视角,和各位工友进入iOS开发的世界。

无论你是想要扩展技能领域,还是对iOS开发充满好奇,花一个下午学习本文都能打开iOS开发这扇门(画饼)。

学完本文你会掌握一点 swift 基础语法,并且开发出一款iOS应用。

阅读本文前你至少需要掌握一门编程语言,如果你是前端开发者,最好懂一点 typescript,这对学习 swift 基础语法来说有点帮助。

准备工作

首先,你需要有一台苹果电脑。

其次,你还需要有一个苹果账号,可以在 苹果官网 申请。

最后,下载一个 Xcode,在 App Store 可以找到。

01.png

虽然评分有点低,但你还是得用它。

swift基础语法

swift 语法规则很多,但初学阶段我认为没必要学全。初学阶段更重要的是建立信心,所以**“快”是关键**。

如果一开始就把语法全部啃一遍,学到后面大概率会忘掉前面。

在对iOS开发有一定的了解(能通过cv捣鼓一下)再回头把语法和原理补回去,这样可能会更顺。

正如俗话说的那样,回头是岸。所以学完皮毛后一定要回头!

综上所述,本文不会对 swift 基础语法做深入讲解。只跟各位工友粗略过一遍。

学习 swift 语法时,我们可以创建一个空模板进行练习。

创建空模板的流程:打开 Xcode -> File -> New -> Playground -> Blank

02.png

03.png

然后选择一个位置存放好就能开始学习了。

04.png

Xcode 里使用 Playground 文件模板,可以在最后测的面板直接看到当前代码执行的结果。

注释

在学习新语言的时候,我习惯第一时间学会狡辩(注释)。

良好的注释习惯可以让其他开发者更轻易了解我的意图。

swift 的注释和 JS 一样,单行用 //,多行用 /**/

// 单行注释

/*
  多行注释
	多行注释
	多行注释
*/

变量

swift 中使用 var 声明变量。

swift 声明变量的语法和 ts 很像,声明变量时最好注明变量类型再赋值(注意,这里说的是“最好”)。

如果没有明确写明变量类型,XCode编辑器 也会自动推导。

// 声明变量时注明变量类型
var sayHi: String = "雷猴"

// 自动推导变量类型
var sayHello = "带尬猴"

从上面的例子可以看出,如果要手动说明变量类型的话,在变量名后面使用冒号再加上变量类型即可。

swift 支持哪些变量类型稍后会提到。

变量的值是可以修改的。

var sayHi: String = "雷猴"
sayHi = "雷猴啊"

变量的类型一旦确定了就不能再修改。以下这种写法会报错(不能将数值型数据赋给字符型变量)。

var sayHi: String = "雷猴"
sayHi = 123

变量不允许重复声明,以下写法会报错。

var sayHi: String = "雷猴啊234"
var sayHi: String = "雷猴"

常量

swift 中使用 let 声明常量。

常量可以在声明时赋值,也可以在声明后再赋值。

// 声明时赋值
let sayHi: String = "雷猴"

// 声明后复制
let sayHello: String
sayHello = "带尬猴"

常量的值一旦确定了就不能再修改。

// 这种写法会报错!

let sayHi: String = "雷猴"
sayHi = "雷猴啊"

变量、常量命名规则

简单总结一下变量和常量的命名规则

  1. 支持字母、数字、下划线,甚至中文和emoji表情,但不建议使用中文和emoji表情作为变量和常量名。
  2. 不支持数字开头,但可以用下划线开头。
  3. 不支持特殊字符。
  4. 不建议使用 swift 保留字作为变量名,实在要用就使用两个"`" 包起来。
// 使用保留字作为变量名的用法(日常开发中不建议这么做!)

var `var` = "雷猴"

print(`var`)

打印输出

前面声明过变量和常量,如果需要打印,可以使用 print() 方法。

var sayHi: String = "雷猴"

print(sayHi)

此时在控制台就会打印出 sayHi 的值。

05.png

数据类型

整型、浮点型、布尔行、字符型

元组

数组、集合、字典

整型 Int

整型可以理解为整数,1、2、3、100这些都是整数。在 swift 中使用 Int 表示整型。

var num: Int = 123

浮点型 Float 和 Double

浮点型可以存储带小数点的数值。swift 分 单精度Float 和 双进度 Double

双精度 Double 比 单精度 Float 有更高的精度。在日常开发中通常使用双精度Double

var pi: Double = 3.14

swifthjs 一样,在进行浮点型数值计算时可能会出现精度丢失问题。

var num1: Double = 0.1
var num2: Double = 0.2

print(num1 + num2) // 输出 0.30000000000000004

字符串 String

swift 里的字符串需要使用双引号包裹着。单引号不行🙅

var str: String = "雷猴"

swift 字符串的概念和 ts 相似,所以在这里就不过多讲解了。接下来主要介绍一下 swift 字符串的常用方法。

判断空字符串 isEmpty

使用 isEmpty 可以判断字符串是否为空,如果是空的话会返回 true,反之返回 false

var str = ""
print(str.isEmpty) // true

获取字符串长度 count

使用 count 可以获取字符串的长度,这个有点像 JS 里的 length

var str = "雷猴"
print(str.count) // 2

其实用 count 也可以判断空字符串,如果返回结果为0就是空字符串,对不对~

拼接字符串 +

JS 一样,拼接字符串可以用加号 + 进行拼接。

var str1 = "hello"
var str2 = "world"

print(str1 + str2) // "helloworld"

在末尾追加字符串

var str = "hello"
str.append(Character("!")) // hello!

除了使用 append 方法外,还可以使用前面的拼接字符串的方式实现。

var str = "hello"
str = str + "!"

字符串的方法暂时先讲到这,开发中遇到实际问题去查一下就行了。

布尔型 Bool

布尔型用 Bool 表示,这个数据类型是用来表示真假的。

布尔型的值只有 truefalse

var isValid1: Bool = true

var isValid2: Bool = false

元组

我第一份编程工作是做前端,接触的是 JS(当时还没有 TS),一开始对元组是没任何概念的。

后来接触别的编程语言,开始有元组概念了。元组给我的感觉就是一个“不可变数组”(我是拿 switfJS 的数组对比)。

switf 中,元组可以将不同类型的数据组合在一起,比如保存部门基础信息时也可以用元组。

// 定义部门基础信息,包含部门名称和员工人数
var department:(name: String, numberOfStaff: Int) = ("销售部", 1000)

如果要访问元组里的元素信息,可以用“点语法”来访问。

// 定义部门基础信息,包含部门名称和员工人数
var department:(name: String, numberOfStaff: Int) = ("销售部", 1000)

// 获取部门名称
var name = department.name
// 获取部门人数
var number = department.numberOfStaff

上面的写法其实有点像 JS 里的对象,在声明元组的时候制定了元素的名称,访问的时候又用了“点语法”。

其实元组还支持不指定元素名称的写法。如果不指定元素名称,访问的时候就要用“下标”的方式去访问了,有点像 JS 的数组,但这也只是像,真正要访问元素的时候还是会用“点语法”。

// 定义部门基础信息,包含部门名称和员工人数
var department:(String, Int) = ("销售部", 1000)

// 获取部门名称
var name = department.0
// 获取部门人数
var number = department.1

用下表访问元组内容的这种方式还可以用在指定了元素名称的情况。

// 定义部门基础信息,包含部门名称和员工人数
var department:(name: String, numberOfStaff: Int) = ("销售部", 1000)

// 获取部门名称
var name = department.0
// 获取部门人数
var number = department.1

除了用下标获取没指定名称的元组内容外,还可以用解构的方式去获取元组里的内容。

var department:(String, Int) = ("销售部", 1000)

var (name, numberOfStaff) = department

print(name)
print(numberOfStaff)

这里需要注意的是,解构时顺序和元组的元素顺序是一一对应的,就是要求解构时的变量数量要和元组数量一样。

有没有可能,我只需要获取元组中其中一个元素?有!

这时可以请 _ 出来帮忙(下划线)。

var department:(String, Int, String) = ("销售部", 1000, "这是销售部,卖东西贼厉害")

// 只获取部门人数
var (_, number, _) = department

print(number)

swift 中,_ 表示匿名的意思,在一些特殊场景会用到它。

数组 Array

数组是 swift 的其中一种集合类型。

数组是有序的,数组的每个元素都有一个数字下标,通过下标可以访问数组指定元素。

数组可以存放任意数据类型,但在 swift 中,数组存放的数据类型必须统一。

比如说,整型数组里的每个元素都必须是整形。

创建数组

定义数组的语法:

var 变量名:[数组类型]

// 举例
var arr:[Int]

又或者可以这样写

var 变量名:Array<数组类型>

// 举例
var arr:Array<String>

创建数组的写法

// 创建空数组
var arr1:[Int] = []
var arr2 = Array()

// 创建字符串数组
var arr3 = ["雷", "猴"] // XCode会自行推导数组类型
var arr4:[String] = ["雷", "猴"]
var arr5:Array<String> = ["雷", "猴"]

// 还可以这样写
var arr6 = Array(arrayLiteral: "雷", "猴")

填充数组

如果数组的元素相同,在 JS 中可以用 fill() 方法快速填充数组。在 swift 中也有类似的方法。

// 快速填充10个“猴”字
var arr1 = Array(repeating: "猴", count: 10)

// 还可以注明数组元素类型
var arr2 = [String](repeating: "猴", count: 10)

访问/修改数组元素

数组元素可以用下标进行访问,下标从0开始。和 JS 一样,通过 [下标] 来访问数组元素。

需要注意:可以访问 varlet 声明的数组,但不允许修改 let 声明的数组!!!

var arr = ["雷", "猴"]

// 访问数组元素
print(arr[0]) // 雷
print(arr[1]) // 猴


// 修改数组元素
arr[0] = "带尬"

print(arr) // ["带尬", "猴"]

获取/修改数组区间

通过 [起始下标...结束下标] 的方式可以获取数组区间,并返回一个新的数组。

var arr = ["h", "e", "l", "l", "o"]

// 访问数组区间,并返回一个新的数组
print(arr[1...3]) // ["e", "l", "l"]
print(arr) // ["h", "e", "l", "l", "o"]

// 修改数组区间
arr[1...3] = ["a", "a", "a"]
print(arr) // ["h", "a", "a", "a", "o"]

如果修改区间时元素数量和原本的数量对不上也是没问题的。比如

// 少于原有数量
var arr1 = ["h", "e", "l", "l", "o"]

arr1[1...3] = ["a"]
print(arr1) // ["h", "a", "o"]



// 大于原有数量
var arr2 = ["h", "e", "l", "l", "o"]

arr2[1...3] = ["a", "a", "a", "a", "a", "a", "a", "a"]
print(arr2) // ["h", "a", "a", "a", "a", "a", "a", "a", "a", "o"]

根据这个特性,如果我们想把 ["h", "e", "l", "l", "o"] 里的两个 l 删掉的话可以这样写

var arr = ["h", "e", "l", "l", "o"]

arr[2...3] = []
print(arr) // ["h", "e", "o"]

修改数组区间 replaceSubrange

除了上面说的方法,还可以使用 replaceSubrange() 方法修改数组指定区间的元素。

var arr = ["h", "e", "l", "l", "o"]
arr.replaceSubrange(1...3, with: ["a", "a", "a"])

print(arr) // ["h", "a", "a", "a", "o"]

数组是否为空 isEmpty

使用 isEmpty 可以判断数组是否为空,为空返回 true,否则返回 false

var arr1 = [1, 2, 3]
var arr2:[Int] = []

print(arr1.isEmpty) // false
print(arr2.isEmpty) // true

获取数组元素数量 count

使用 count 可以获取数组元素数量,当 count 返回 0 时也可以认为该数组为空。和 JSlength 差不多。

var arr1 = [1, 2, 3]
var arr2:[Int] = []

print(arr1.count) // 3
print(arr2.count) // 0

获取数组第一个元素 first

使用 first 可以获得数组的第一个元素。

var arr = ["h", "e", "l", "l", "o"]

print(arr.first) // "he"

获取数组最后一个元素 last

使用 last 可以获得数组的最后一个元素。

var arr = ["h", "e", "l", "l", "o"]

print(arr.last) // "o"

判断数组是否包含某个元素 contains

使用数组的 contains() 方法可以判断数组中是否包含某个元素,如果包含就返回 true,否则返回 false

var arr = ["h", "e", "l", "l", "o"]
print(arr.contains("e")) // true
print(arr.contains("a")) // false

追加元素 append

向数组末尾追加一个元素可以使用 append() 方法,直接传入一个元素即可。

注意:append只能修改 var 声明的数组,不能修改 let 声明的数组!!!

var arr = ["h", "e", "l", "l", "o"]
arr.append("!")

print(arr) // ["h", "e", "l", "l", "o", "!"]

如果需要追加一组元素,还需要配合 contentsOf 一起使用。

var arr = ["h", "e", "l", "l", "o"]
arr.append(contentsOf: [" ", "w", "o", "r", "l", "d"])

print(arr) // ["h", "e", "l", "l", "o", " ", "w", "o", "r", "l", "d"]

移除首个或前几个元素 removeFirst

使用 removeFirst() 方法可以移除数组的首个元素。

var arr = ["h", "e", "l", "l", "o"]
arr.removeFirst()
print(arr) // ["e", "l", "l", "o"]

注意:如果数组为空时使用 removeFirst() 会报错!所以使用移除操作前必须判断数组是否为空!接下来讲到的其他数组的移除方法都要遵循这个规则。

removeFirst() 还可以穿入一个整型参数,用来指定要移除的元素个数。

比如我想移除数组的前2位就可以传个 2 进去。

var arr = ["h", "e", "l", "l", "o"]
arr.removeFirst(2)
print(arr) // ["l", "l", "o"]

注意:传入的数不能大于数组的长度,不然会报错!

移除末尾1个或多位元素 removeLast

使用 removeLast() 可以移除数组末尾一个或多个元素。

var arr = ["h", "e", "l", "l", "o"]
arr.removeLast() // 移除数组末尾一个元素
print(arr) // ["h", "e", "l", "l"]

如果要移除数组末尾多个元素可以传入一个整数,但这个数不能大于数组的长度。

var arr = ["h", "e", "l", "l", "o"]
arr.removeLast(3) // 移除数组末尾3个元素
print(arr) // ["h", "e"]

移除指定范围内的元素 removeSubrange

使用 removeSubrange() 方法可以移除指定范围内的元素。

比如我要移除下标1~3的元素可以这样写:

var arr = ["h", "e", "l", "l", "o"]
arr.removeSubrange(1...3) // 移除1~3的元素
print(arr) // ["h", "o"]

清空数组 removeAll

使用 removeAll 可以清空数组。

var arr = ["h", "e", "l", "l", "o"]
arr.removeAll()

print(arr) // []

这样操作数组会报错!!!

可以使用 varlet 声明数组,根据前面提到的知识我们知道使用 var 是声明变量,使用 let 是声明常量。

如果访问数组(查询操作)的话是可以不区分 varlet 的。

但要修改数组(增、删、改)的话,只能操作 var 声明的数组。一旦操作 let 声明的数组就会报错!

以下操作都会报错!

// 错误🙅
let arr1 = ["h", "e", "l", "l", "o"]
arr1[0] = "雷"

// 错误🙅
let arr2 = ["h", "e", "l", "l", "o"]
arr2[0, 3] = []

// 错误🙅
let arr3 = ["h", "e", "l", "l", "o"]
arr3.append("!")

// 错误🙅
let arr4 = ["h", "e", "l", "l", "o"]
arr4.append(contentsOf: [" ", "w", "o", "r", "l", "d"])

// 错误🙅
let arr5 = ["h", "e", "l", "l", "o"]
arr5.removeFirst()

// 错误🙅
let arr6 = ["h", "e", "l", "l", "o"]
arr6.removeLast()

// 错误🙅
let arr7 = ["h", "e", "l", "l", "o"]
arr7.removeSubrange(1...3)

// 错误🙅
let arr8 = ["h", "e", "l", "l", "o"]
arr8.replaceSubrange(1...3, with: [])

// 错误🙅
let arr9 = ["h", "e", "l", "l", "o"]
arr9.removeAll()

再次提醒,在使用移除操作前先判断数组是否为空!数组为空时使用移除操作会报错!!!

本文关于 swift 数组的方法先讲到这,入门阶段不打算讲太多,以后需要用到哪些方法再查就行。

后面讲 swift 遍历的时候还会用到数组来举例。

集合

集合是一组无序且不重复的数据。

创建集合

集合的类型叫 Set ,创建集合时可以用 Set 给变量声明类型。也可以通过 Set() 方法创建集合,然后由 XCode 自行推断这个变量的类型。

var set1: Set<Int> = [1, 2, 3, 2, 1]
print(set1) // [1, 2, 3]

也可以这样创建

var set = Set(arrayLiteral: 1, 2, 3, 2, 1)
print(set) // [2, 3, 1]

因为集合是无序的,所以打印集合时可能每次输出的顺序都不一样。

获取集合元素数量 count

和数组一样,集合使用 count 可以获取元素数量。

var set: Set<Int> = [1, 2, 3, 4, 5, 6]
print(set.count) // 6

集合是否为空 isEmpty

使用 isEmpty 可以判断集合是否为空,如果为空会返回 true ,有元素会返回 false

var set1: Set<Int> = [1, 2, 3, 4, 5, 6]
print(set1.isEmpty) // false

var set2: Set<Int> = []
print(set2.isEmpty) // true

是否包含指个元素 contains

使用 contains() 方法可以判断集合是否包含指定元素,需要传入元素的值。

var set1: Set<Int> = [1, 2, 3, 4, 5, 6]
print(set1.contains(4)) // true

添加元素 insert

使用 insert() 方法可以向集合插入数据。

var set1: Set<Int> = [1, 2, 3, 4, 5, 6]
set1.insert(7)
print(set1) // [3, 1, 5, 2, 6, 7, 4]

删除元素 remove

使用 remove() 方法可以删除集合中指定的元素。如果要删除的元素不在集合里也不会报错。

var set1: Set<Int> = [1, 2, 3, 4, 5, 6]
set1.remove(2)
print(set1)


清空集合 removeAll

使用 removeAll() 方法可以清空集合,该方法不能传入参数。

var set1: Set<Int> = [1, 2, 3, 4, 5, 6]
set1.removeAll()
print(set1)

字典

字典可以理解为 JS 里的对象,就是一个键值对的数据类型。可以通过键(索引)找到对应的值。

因为字典是通过键来找到值的,所以键是不能重复的。就像真实世界的查字典那样。

创建字典

swift 中声明字典时是可以标明键和值的类型(不标明的话 XCode 也会自行推断)。

var dic: [String: String] = ["name": "雷猴"]

这里声明里键和值的类型都是字符串。

也可以使用这种方式创建字典:

var dic:Dictionary<Int, String> = [1: "雷猴"]

// 或者

如果你不显式声明类型,XCode 是可以自行推断的。

var dic = ["name": "雷猴"]

如果想创建空字典可以这样写:

var dic:[Int:Int] = [:]

// 也可以这样写
var dic:Dictionary<Int, Int> = Dictionary()

需要注意的是,创建空字典时一定要声明键值的类型,不然会报错。

// 错误🙅
var dic = [:]

// 错误🙅
var dic = Dictionary()

获取值

要获取字典中某个值也很简单,可以用中括号 [] 的方式访问键名 key

var dic = ["name": "雷猴"]
print(dic["name"]) // "雷猴"

如果键 key 不存在会返回 nil

修改值

可以通过上面取值的方式获取值,后面加个等号就可以重新赋值了。

var dic = ["name": "雷猴"]
dic["name"] = "鲨鱼辣椒"

print(dic) // ["name": "鲨鱼辣椒"]

更新值 updateValue

针对上面的“获取值”和“修改值”的操作,swift 还提供了另一种方式:updateValue()

updageValue() 接受2个参数,参数1是值的内容;参数2是 key,参数2需要使用 forKey 指明键名。

var dic = ["name": "雷猴"]
dic.updateValue("鲨鱼辣椒", forKey: "name")

print(dic) // ["name": "鲨鱼辣椒"]

如果 key 不存在就会新增元素。

var dic = ["name": "雷猴"]
dic.updateValue("鲨鱼辣椒", forKey: "name2")

print(dic) // ["name": "雷猴", "name2": "鲨鱼辣椒"]

添加新元素

通过直接赋值的方式可以给字典添加新元素,但如果字典中原本就有那个元素,直接赋值就会将原来的值给覆盖掉。

var dic = ["name": "雷猴"]
dic["job"] = "保安"

print(dic) // ["name": "雷猴", "job": "保安"]

其实修改元素和新增元素的操作都是一样的,这个过程可以理解为:如果键存在就更新值,如果键不存在就创建元素。

字典是否为空 isEmpty

使用 isEmpty 可以判断字典是否为空。字典为空返回 true,否则返回 false

var dic1 = ["name": "雷猴"]
print(dic1.isEmpty) // false

var dic2:[Int:Int] = [:]
print(dic2.isEmpty) // true

获取字典元素个数 count

使用 count 可以获取字典元素个数。

var dic = ["name": "雷猴"]
print(dic.count) // 1

删除元素 removeValue

可以使用 removeValue() 方法删除字典的元素

var dic = ["name": "雷猴", "job": "保安"]
dic.removeValue(forKey: "job")

print(dic) // ["name": "雷猴"]

清空字典 removeAll

使用 removeAll() 方法可以清空字典。

var dic = ["name": "雷猴", "job": "保安"]
dic.removeAll()

print(dic) // [:]

检查变量类型 type(of:)

js 里可以通过 typeof() 检查变量类型,在 swift 里也有类似的方法:type(of: )

var a = "雷猴"
print(type(of: a)) // String

var b = ["雷猴", "鲨鱼辣椒", "蟑螂恶霸", "蜘蛛侦探", "蝎子莱莱"]
print(type(of: b)) // Array<String>

大家可以自己试试输出其他数据类型。

运算符

运算符就是用来运算的,比如加减乘除。

swift 的运算符和 JS 的类似,在这里就不深入讲解了,我罗列出来记录一下,大家知道大概知道常用的有哪些运算符就行。

赋值运算符

赋值运算符就是一个 = 号,将右边的值赋给左边。

前面在讲变量常量的时候就已经用过了,这里不再多讲。

算数运算符

常用的算数运算符有加减乘除、取余,在这基础上还有“加等”、“减等”之类的运算符,这里简单列举一下。

算数运算符 说明
+
-
*
/
% 取余
+= 加等
-= 减等
*= 乘等
/= 除等
%= 余等

swift 里要关注一下取余 %,它只支持整数类型的运算,不支持浮点型!

// 错误🙅
var num = 11.1 // 浮点型
print(num % 3)

需要注意的是,在新版的 swift 里取消了自增 ++ 和自减 -- 这两种运算符。

我查到的资料说是从 swift 2.2版本之后取消的。据说是为了提高可读性,减少代码的歧义。

逻辑运算符

逻辑运算符就是“与”、“或”、“非”,用来判断真假和取反的。

swift 的逻辑运算只支持比较布尔类型的值,这点和 JS 是不一样的。

var t1 = true
var t2 = false

print(t1 && t2) // false

print(t1 || t2) // true

print(!t1) // false

JS 里支持 !! 这种写法,在 swift 里是不支持的。

// JS代码
var t1 = false
console.log(!!t1) // false

// swift
var t1 = false
print(!!t1) // 报错!!!

比较运算符

比较运算符主要用来比大小或者看看两个变量是否相等,结果是返回 true 或者 false

常用的比较运算符有以下这些

比较运算符 说明
== 比较两个值是否相等。
!= 不等于。
大于
>= 大于且等于
< 小于
<= 小于且等于

比较运算符的用法比较简单,和大多数主流的编程语言也没什么差异。这里我就不举例了。

条件运算符

条件运算符也叫“三元运算符”,也是用来比较的。

条件运算符可以简化 if...else 的操作,但不建议在复杂场景中使用。

条件运算符的用法是:

a ? b : c

如果 a 为真,执行或者返回 b ,否则执行或者返回 c

var a = true

print(a ? "雷猴" : "鲨鱼辣椒") // 返回

a ? print("雷猴") : print("鲨鱼辣椒") // 执行

区间运算符

区间运算符是 swift 里一个比较特殊的运算符,它通常会和if-else判断或者 for-in 循环一起使用。

区间运算符有 .....< ,分别表示闭区间和半开区间。

// >=0 且 <=10
var a = 0...10

// >=0 且 <10
var b = 0..<10

swift..<pythonrange 有点像。

和区间运算符配合的另一个符号叫 ~=,它可以检测某个数是否存在区间内。

var a = 0..<10
print(a ~= 2) // true
print(a ~= 11) // false

区间运算符的应用会在接下来的循环中讲解。

条件判断

if

swift 的条件判断使用 if 或者 if-else

用法如下:

var a = 10

// 单个if
if a < 11 {
  print(a)
}

// if-else
if a > 100 {
  print("a大过100")
} else {
  print("a小雨等于100")
}

这个用法比较简单,如果理解了前面的三元运算符就不难理解 if-else

swiftif 后面跟着的条件不需要用括号包起来,这点和 js 有点区别。

switch

如果匹配规则比较多的时候,可以使用 switch-case 来处理。

基础用法

比如将员工的绩效考核转成中文输出:

var grade = "B"

switch grade {
case "A":
    print("优秀")
case "B":
    print("良好")
case "C":
    print("合格")
default:
    print("不合格")
}

需要注意的是:

  • case 不需要使用 break 配合跳出。
  • 需要写上 default 那段。

一个case匹配多个条件

swiftswitchJS 的更强大,它可以一个 case 匹配多个条件。

var grade = "B"

switch grade {
case "A", "B", "C", "D":
    print("英文字母")
case "0", "1", "2", "3":
    print("数字")
default:
    print("其他")
}

循环

如果需要执行一些重复性的操作,可以使用循环。

swift 常用的循环有 for-inwhilerepeat-while

for-in

for-in 循环的用法很简单,有一点点编程基础的工友看代码就能直接看懂的~

for i in 0...3 {
    print(i)
}
// 输出 0、1、2、3

上面这个例子结合了前面讲到的区间运算符。

for-in 除了能循环区间,还能循环字符串、数组等其他类型。

// 循环字符串
for i in "hello" {
    print(i) // 输出 h、e、l、l、o
}

// 循环数组
// 循环时获取到的是数组的每一个元素,不是下标!
var arr1 = ["a", "b", "c", "d"]
for i in arr1 {
    print(i) // 输出 a、b、c、d
}

// 循环数组
// 循环时获取到的是数组下标,需要配合 indices 一起使用
var arr2 = ["a", "b", "c", "d", "e"]
for index in arr2.indices {
    print(index) // 输出 0、1、2、3、4
}

字典的循环会稍微特殊一点。

直接循环字典

var dic = ["name": "雷猴", "age": "18"]
for item in dic {
    print(item)
}

/*
	输出:
	(key: "name", value: "雷猴")
	(key: "age", value: "18")
*/

可以通过 item.keyitem.value 访问键和值

var dic = ["name": "雷猴", "age": "18"]
for item in dic {
    print(item.key)
    print(item.value)
}

如果循环时只需获取每次循环的键或者值,可以这样写:

var dic = ["name": "雷猴", "age": "18"]

// 直接获取键
for key in dic.keys {
    print(key)
}

// 直接获取值
for value in dic.values {
  print(value)
}

还有一种方法:

var dic = ["name": "雷猴", "age": "18"]
for (key, value) in dic {
    print(key)
    print(value)
}

while

while 循环通过一个逻辑判断作为循环条件,当条件为真时继续循环。

var arr = ["a", "b", "c", "d"]
var i = 0
while i < arr.count {
    print(arr[i])
    i += 1
}

需要注意,通常在循环体内需要对循环条件做修改,以免死循环。

repeat-while

swiftrepeat-whileJSdo...while 差不多。

repeat-whilewhile 的区别是:repeat-while 不管循环条件是否成立,它至少会执行一次。

var arr = ["a", "b", "c", "d"]
var i = 0
repeat {
    print(arr[i])
} while arr.count > 10

// 输出:a

这个例子中,循环条件是肯定不成立的,但循环体还是执行了一次。

函数

简单来说,函数的作用是将一堆代码整理打包到一起,并给这个函数起个名字,下次想使用这段代码的时候直接“喊”这个函数名就行了。当然,还有匿名函数,这个迟点再讲。

一句话总结:函数就是特定功能的代码块。

swift 中,函数是有类型的,这取决于它的参数和返回值,这点和 TypeScript 类似。

函数的创建和调用

创建函数

swift 中使用 func 关键字来声明函数。

创建函数的语法格式如下:

func 函数名(参数1: 参数类型, 参数2: 参数类型, ...) -> 返回值的类型 {
  代码块
}

举个例子,判断一个学生成绩是否大于60分。

func isQualified(score: Int) -> Bool {
    return score >= 60
}

isQualified(score: 90) // true
isQualified(score: 59) // false

函数的参数、返回值其实都可以不指定。

// 不需要参数
func fn1() -> String {
    return "雷猴"
}
print(fn1()) // 输出:雷猴


// 不需要返回值
func fn2() {
    print("雷猴")
}
fn2() // 输出:雷猴

返回多个值

函数还可以返回多个值(以元组的方式),这个在异步请求的时候经常会这样写。

比如我们通过文章id去查询文章详情。

func fetchArticleDetails(id: String) ->(status: Bool, data: String) {
  	// 模拟请求的返回值
    let status = true
    let data = "文章内容"
    return (status, data)
}

// 当请求结果的 status 为 true 时再执行输出。
if fetchArticleDetails(id: "100").status {
    print(fetchArticleDetails(id: "100").data)
}

外部参数名

函数还支持外部参数名。

语法格式如下:

func 函数名(外部参数名 内部参数名: 参数类型) {
  代码块
}

这样做的好处是函数内部可以用一些名字更为简约的参数的同时能给外部起一个语义更强的参数名。

func fn(out1 p1: Int, out2 p2: Int) {
    print(p1, p2)
}

fn(out1: 1, out2: 2)

不传参数名(匿名参数)

可以使用匿名参数的方式,让调用函数传参时无需写明参数名。

func fn(_ p1: Int, _ p2: Int) {
    print(p1, p2)
}

fn(1, 2)

下划线 _匿名变量标识符

默认参数

默认参数的意思是:调用函数时可以传参也可以不传,传参的话就使用外部传进来的值,不传的话就使用默认值。

func fn(p1: Int, p2: Int = 10) {
    print(p1, p2)
}

fn(p1: 1, p2: 2) // 1 2

fn(p1: 3) // 1 10

在创建函数时可以给参数赋值,这种操作就时给参数指定一个默认值。

需要注意的是,如果使用匿名参数的方式创建函数,默认参数最好放在所有参数的后面,不然就有点失去默认参数的意义了。

比如下面这段代码:

func fn(_ p1: Int = 10, _ p2: Int) {
    print(p1, p2)
}

fn(1, 2)

fn(3) // 会报错的!

不定参

有些情况是函数参数的数量不确定,比如 print() 方法可以传入1个参数,也可以传入多个参数。

这种情况就可以使用 不定参 的方式创建函数了,在参数后面加上 ... 可以让这个参数变成不定参。

func fn(param: String...) {
    print(param)
}

fn(param: "雷猴")
fn(param: "鲨鱼辣椒", "蟑螂恶霸")

注意参数类型后面的三个点。

不定参是一个数组,可以通过 [index] 的方式访问数组的内容。

func fn(param: String...) {
    print(param[0])
}

fn(param: "鲨鱼辣椒", "蟑螂恶霸") // 打印:"鲨鱼辣椒"

如果和其他参数配合,需要指明参数名

func fn(param1: String..., param2: Int) {
    print(param1)
    print(param2)
}
fn(param1: "雷猴", "鲨鱼辣椒", "蟑螂恶霸", param2: 123)
/*
	输出:
	["雷猴", "鲨鱼辣椒", "蟑螂恶霸"]
	123
*/

如果使用匿名参数和不定参配合,这时候就只能使用一个不定参。

func fn(_ param1: Int, _ param2: String...) {
    print(param1)
    print(param2)
}
fn( 123, "雷猴", "鲨鱼辣椒", "蟑螂恶霸")

函数参数到底能不能修改?

JS 中,函数参数的值是可以修改的,但在 swift 里默认是不允许修改。

下面这种写法在 swift 里是错误的:

func fn1(a: Int) {
    a = 10
}

fn1(a: 1)

如果你就是要修改函数参数,可以这样写:

func fn1(a: inout Int) -> Int {
    a = 1
    return a
}

var para = 10

var b = fn1(a: &para)
print(b) // 1

先把参数在外部定义,调用函数传参时,在参数变量前面加上 &

将函数作为参数传入

某些情况需要将函数作为参数传入到另一个函数里,比如回调函数。

将函数作为参数传入另一个函数里,可以这样写:

func addFu(p1:Int, p2:Int) -> Int {
   return p1 + p2
}

func fn1(param:(Int,Int) -> Int) {
   print(param(10, 20))
}

fn1(param: addFu)

闭包

在了解闭包前,首先要知道 swift 的函数时可以嵌套的,比如这样:

func outerFn() -> Void {
    func innerFn() -> Void {
        print("雷猴")
    }
    
    innerFn()
}

outerFn()

outerFn 函数体里又创建一个函数(innerFn),然后执行 innerFn

这种写法就是函数的嵌套。

闭包有点像是一个自包含的函数,它可以在需要的时候被调用。它可以捕获并记住其所在的上下文中的变量和常量。

举个例子:

func outerFn() -> () -> Void {
    var counter = 0

    func innerFn() {
        counter += 1
        print("Counter: \(counter)")
    }

    return innerFn
}

let myFn = outerFn()
myFn()  // 输出 "Counter: 1"
myFn()  // 输出 "Counter: 2"

我们在外部是无法直接访问变量 counter 的,因为它是在 outerFn 里创建,在 innerFn 里被使用。

在执行 outerFn 后将 innerFn 返回出去给了 myFn,而 innerFn 的作用域内存在 counter 这个变量,所以 counter 也算是被 outerFn 一并返回出去了,但这个 counter 只能被同一个作用域的成员调用。所以在外层是无法直接使用 counter 的,但 counter 又没有被释放掉,所以在上面的代码中,每次调用 myFn 时都会执行 counter += 1counter 会在之前的值的基础上运算。

这就是闭包。

最后还需要补充一下闭包的另一种写法。

前面提到函数的嵌套,其实还有另一种写法:

// 定义函数类型
var addFn: (Int, Int) -> Int

// 赋值
addFn = {(p1: Int, p2: Int) in return p1 + p2}

// 调用
addFn(1, 2) // 返回:3

上面的代码从语法上来看是这样的结构:

{(参数1, 参数2, ...) -> 返回值类型 in 闭包体}

in 是关键字,in 的左边是闭包结构的参数和返回值的类型,语法和声明普通函数一样;in 的右边是闭包体,是具体功能代码。

枚举

swift 中使用 enum 关键字创建枚举类型数据。

创建和使用枚举的方式如下:

enum Role: String {
    case L = "雷猴"
    case S = "鲨鱼辣椒"
    case X = "蝎子莱莱"
}

var protagonist: Role = Role.L
print(protagonist) // 输出:L
print(protagonist.rawValue) // 输出:雷猴

rawValue 可以获取枚举的原始值。

枚举通常会和 switch 一起使用,在后面的实战案例中用到会讲解。

结构体

swift 中有结构体这两种数据结构,它们都可以定义物体的属性和行为,单它们的实现机制有着本质上的区别。

我们先简单了解一下结构体

swift 中使用 struct 关键字定义结构体。在结构体内可以创建变量、常量和函数。

举个例子

struct Monkey {
    // 姓名
    var name: String
    // 喜欢的食物
    var favoriteFood: String
    // 自我介绍
    mutating func introduce() {
        print("我叫\(name),我最喜欢的食物是\(favoriteFood)")
    }
}

var leihou = Monkey(name: "雷猴", favoriteFood: "黄焖鸡米饭")

leihou.introduce() // 我叫雷猴,我最喜欢的食物是黄焖鸡米饭

swift 中使用 class 声明类。

class Monkey {
    // 姓名
    var name: String
    // 喜欢的食物
    var favoriteFood: String
    
    // 构造方法
    init(name: String, favoriteFood: String) {
        self.name = name
        self.favoriteFood = favoriteFood
    }
    
    // 自我介绍
    func introduce() {
        print("我叫\(name),我最喜欢的食物是\(favoriteFood)")
    }
}

var leihou = Monkey(name: "雷猴", favoriteFood: "黄焖鸡米饭")

leihou.introduce()

可以看出,类和结构体的语法看上去是差不多的,但类需要使用 init 构造函数给自己的属性赋值;而结构体并不需要这步操作。

结构体和类的区别

**结构体(struct)**和 类(class) 最大的区别是:类属于引用类型,结构体属于值类型。

在数据传递的时候,值类型会复制一份出来再赋值给新变量,而引用类型是把值的地址赋值给新变量。

本文的出发点是从前端切图仔转型iOS开发,我先拿 JS 说明一下值类型和引用类型的区别。

// 以下是JS代码

// 值类型
var a = 1
var b = a
b = 2

console.log(a) // 1
console.log(b) // 2


// 引用类型
var c = {name: '雷猴'}
var d = c
d.name = "鲨鱼辣椒"

console.log(c.name) // 鲨鱼辣椒
console.log(d.name) // 鲨鱼辣椒

这个例子中,ab 属于值类型,就算写了 var b = a ,后面 b 修改了自己的值并不会影响 a

cd 的关系是它们手里都握着同一个房间的钥匙,d 挪动了房间里的物品后,c 也会受到影响。

大概就是这么一个关系。

回到 swift结构体(struct)类(class),结构体是值类型,类是引用类型。

值类型复制给另一个变量时,自身的内容不会受到影响。

// 结构体 struct
struct Role {
    var name: String
}

var a = Role(name: "雷猴")
var b = a
b.name = "鲨鱼辣椒"

print(a.name) // 雷猴
print(b.name) // 鲨鱼辣椒

类是引用类型,复制给其他变量时,复制的是“打开房间的钥匙”,也就是把存储值的内存地址复制给另一个变量。

class Role {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

var a = Role(name: "雷猴")
var b = a
b.name = "鲨鱼辣椒"

print(a.name) // 鲨鱼辣椒
print(b.name) // 鲨鱼辣椒

结构体和类其实还有很多知识点,比如类的继承。在iOS初学阶段我建议是尽可能快的学会界面开发,能快速建立信心。

更多的知识点在学完本文后可以再去网上查查。

iOS开发

创建项目

1、打开 Xcode,选择 Create a new Xcode project。

06.png

2、选择 App,然后点击 Next。

07.png

3、根据自己的需求填写好项目的基本信息。

08.png

其中,Interface可选 Storyboard 和 SwiftUI。

Storyboard 是传统的界面开发方式,SwiftUI只支持Swift语言。

我这里使用的是 Storyboard。SwiftUI 稍后再介绍。

填写完基础信息后点击 Next ,再选择项目的存放位置就能创建一个新的项目了。

运行项目

首先看看项目目录结构。

09.png

  • AppDelegate:应用程序入口。
  • SceneDelegate:多场景应用管理。
  • SceneDelegate:默认创建的视图控制器。
  • Main:可视化开发文件。
  • Assets:存放静态资源的文件夹,比如存放图片。
  • LaunchScreen:闪屏(App启动时的页面)。
  • Info:项目配置文件。

在大概了解了项目目录结构后,我们先试着运行项目。

在箭头所指的区域选择要运行的设备,然后点击左边的三角形按钮就可以运行项目了,iOS Simulators里的选项是你本机安装过的iOS模拟器。

10.png

我这里用的是模拟器运行,如果要真机运行需要用数据线将你的iPhone接到电脑上,然后点击左侧的三角形按钮即可。

11.png

但真机运行需要申请证书,这个后面再讲。在初学阶段先用模拟器运行就行了。

接下来了解一下iOS开发中的常用页面元素。

基础控件

标签 UILabel

在学 HTML + CSS 时会学一堆标签和一些基础的样式设置。

iOS开发也可以用这套流程来学习。在初学阶段先了解一下如何在页面渲染。

关于样式部分的用法初学者可以先不深究,这和我们刚学习 CSS 时是差不多的,用多几次就理解了。

基础用法

好,先来第一个例子。

我要在页面渲染“雷猴”两个字,要求如下:

  1. 字号:60
  2. 文本颜色:蓝色
  3. 文本阴影:绿色,有点偏移

出来的效果长这样子:

12.png

在这个例子中使用到标签 UILabel 控件。

XCode 根据前面的步骤创建一个项目,打开 ViewController ,在这个文件里编写代码。

13.png

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // 将背景设置成白色
        view.backgroundColor = UIColor.white
        
        // 创建UILabel控件
        let label = UILabel(frame: CGRect(x: 0, y: 100, width: 200, height: 60))
        // 设置文本内容
        label.text = "雷猴"
        // 设置文本字号大小
        label.font = UIFont.systemFont(ofSize: 60)
        // 设置文本颜色
        label.textColor = UIColor.blue
        // 设置文本阴影颜色
        label.shadowColor = UIColor.green
        // 设置文本阴影的位置偏移
        label.shadowOffset = CGSize(width: 4, height: 4)
        // 将label控件添加到当前视图上
        self.view.addSubview(label)
    }
}

上面这段代码,有一部分是在创建项目时就已经帮我们写好的:

import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

override 表示重写父类的方法。

viewDidLoad 是生命周期,在这个方法内写的代码会在视图控制器加载时调用。

所以我们会将创建元素的操作写在 viewDidLoad 里。

按照我们开发网页的习惯,页面背景默认是白色的。所以我使用 view.backgroundColor = UIColor.white 将页面背景设置成白色。

之后使用 let label = UILabel(frame: CGRect(x: 0, y: 100, width: 200, height: 60)) 创建一个 label 控件,这个控件可以理解为 HTML 里的 <div> ,但用法和 <div> 不同。

这句代码可以这样理解:

  • UILabel():创建Label控件
  • frame: CGRect():定义这个控件为矩形。
  • CGRect(x: 0, y: 100, width: 200, height: 60):设置这个控件的位置和宽高。

之后设置文本样式可以看上面的那段代码,关于什么是 UIFont.systemFont,什么是 CGSize 等东西暂时不用深究,初学阶段真的先死记硬背一下,深究对初学阶段没什么好处。

在设置好样式后,使用 self.view.addSubview(label) 将标签控件添加到页面。

需要注意的是,字号如果大于label的高度 或者 文本内容超出label宽度,文本都会显示不完整。

14.png

// 省略部分代码
let label = UILabel(frame: CGRect(x: 0, y: 100, width: 200, height: 30))
label.text = "雷猴"
label.font = UIFont.systemFont(ofSize: 60)

此时 label 的高度是30,字号是60。

同理,如果文字内容超出label的宽度,也会显示不完整

15.png

// 省略部分代码

let label = UILabel(frame: CGRect(x: 0, y: 100, width: 200, height: 30))
label.text = "雷猴,鲨鱼辣椒,蝎子莱莱,蟑螂恶霸,金龟次郎,卡布达,飞翔机器人,铁甲小宝"
label.font = UIFont.systemFont(ofSize: 30)

本文的内容远远超过 label 的宽度,此时会出现省略号的情况(如果还能放得下省略号的话)。

多行文本

如果希望内容能换行展示,可以给 label 设置行数 numberOfLine

16.png

// 省略部分代码

let label = UILabel(frame: CGRect(x: 0, y: 100, width: 300, height: 80))
label.text = "雷猴,鲨鱼辣椒,蝎子莱莱,蟑螂恶霸,金龟次郎,卡布达,飞翔机器人,铁甲小宝"

// 设置行数
label.numberOfLines = 6

label.font = UIFont.systemFont(ofSize: 20)

这里需要注意的是,label 的高度也要有足够的位置支持换行,不然还是会出现省略号,把超出的部分隐藏起来。

多行文本这里还有一个小技巧:将 numberOfLines 设为 0 可以自动换行,但前提是 label 的高度足以容纳所有文本。

// 省略部分代码

let label = UILabel(frame: CGRect(x: 0, y: 100, width: 300, height: 80))
label.text = "雷猴,鲨鱼辣椒,蝎子莱莱,蟑螂恶霸,金龟次郎,卡布达,飞翔机器人,铁甲小宝"

// 设置行数为0,表示不限制显示行数。
label.numberOfLines = 0

label.font = UIFont.systemFont(ofSize: 20)

使用 label 的多行文本需要注意几个点:

  • label的高度 height
  • 行数 numberOfLines
  • 字号 UIFont.systemFont(ofSize: 字号的值)

当高度小于字号,会把文字裁切掉一部分。

当高度大于字号,且高度小于可换行的量,会用省略号替换掉超出的文本内容。

工友们自己动手试试看会更容易理解。

特殊样式

如果想高亮一段文本的某几个字,可以配合 NSMutableAttributedString 一起使用。

比如这样,高亮了“鲨鱼辣椒”。

17.png

实现步骤:

  1. 创建 label 控件。
  2. 使用 NSMutableAttributedString 创建一段文字。
  3. 通过 addAttribute 设置指定文本的样式。
  4. 将文本添加到页面里。
// 省略部分代码

// 创建UILabel控件对象
let label = UILabel(frame: CGRect(x: 0, y: 100, width: 300, height: 200))

// 设置行数
label.numberOfLines = 0

// 创建特殊文本
let attri = NSMutableAttributedString(string: "雷猴,鲨鱼辣椒,蝎子莱莱,蟑螂恶霸,金龟次郎,卡布达,飞翔机器人,铁甲小宝")

// 设置指定文本的样式
attri.addAttribute(
	NSAttributedString.Key.foregroundColor, // 要设置的样式,这句代码表示要设置文本颜色
	value: UIColor.systemRed, // 要设置样式的值,这里表示红色,意思就是让文本变成红色。
	range: NSRange(location: 3, length: 4) // 要修改的范围,从下标为3的字开始修改,修改范围是4,也就是要修改下表为 3、4、5、6 这几个字的样式。下标是从0开始数起的。
)

label.attributedText = attri
self.view.addSubview(label)

addAttribute 需要传入3个参数,第1个参数是要设置的样式名;第2个参数是样式的值;第3个参数是修改范围。

详情请看上面的代码。

如果需要同时设置多个参数,可以将 addAttribute 改为 addAttributes 。注意看,只是多了个 s

addAttributes 需要传入2个参数,第1个参数是字典,字典里写名要修改的样式和值;第2个参数是修改范围。

18.png

// 省略部分代码

let label = UILabel(frame: CGRect(x: 0, y: 100, width: 300, height: 200))
label.numberOfLines = 0

let attri = NSMutableAttributedString(string: "雷猴,鲨鱼辣椒,蝎子莱莱,蟑螂恶霸,金龟次郎,卡布达,飞翔机器人,铁甲小宝")

// 下划线
let underlineStyle: NSUnderlineStyle = .single

// 设置一堆样式(下标为 3、4、5、6 的字)
attri.addAttributes(
    [
        NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 20), // 字号
        NSAttributedString.Key.foregroundColor: UIColor.red, // 文本颜色
        NSAttributedString.Key.backgroundColor: UIColor.black, // 文本背景色
        NSAttributedString.Key.underlineStyle: underlineStyle.rawValue, // 下划线
        NSAttributedString.Key.underlineColor: UIColor.yellow, // 下划线颜色
    ],
    range: NSRange(location: 3, length: 4)
)

// 创建一个阴影效果
let shadow = NSShadow()
shadow.shadowColor = UIColor.systemYellow // 阴影颜色
shadow.shadowOffset = CGSize(width: 2, height: 2) // 阴影偏移位置
shadow.shadowBlurRadius = 3 // 阴影的模糊度

// 设置一堆样式(下标为 8、9、10、11 的字)
attri.addAttributes(
    [
        NSAttributedString.Key.font:UIFont.systemFont(ofSize: 30), // 字号
        NSAttributedString.Key.foregroundColor: UIColor.blue, // 文本颜色
        NSAttributedString.Key.shadow: shadow // 阴影效果
    ],
    range: NSRange(location: 8, length: 4)
)


label.attributedText = attri
self.view.addSubview(label)

按钮 UIButton

按钮是最基础的交互控件,它通常和点击操作相关。

swift 中使用 UIButton 创建按钮。

创建按钮通常需要做以下几步操作:

  1. 创建UIButton实例。
  2. 设置按钮位置和尺寸。
  3. 设置按钮文本内容。
  4. 将按钮添加到视图。

19.gif

import UIKit

class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    // 将背景设置成白色
    view.backgroundColor = UIColor.white

    // 创建UIButton实例
    let btn = UIButton(type: UIButton.ButtonType.system)

    // 设置按钮位置与尺寸
    btn.frame = CGRect(x: 20, y: 100, width: 100, height: 30)

    // 设置按钮标题
    btn.setTitle("雷猴", for: .normal)

    // 添加到当前视图
    self.view.addSubview(btn)
  }
}

先看看 UIButton(type: UIButton.ButtonType.system) 这句,这个例子使用了 UIButton.ButtonType.system 这种类型,创建的是默认的系统按钮。

除了 system 类型外,swift 还提供了其他类型:

详情类型

  • detailDisclosure
  • infoLight
  • infoDark

20.png

// 省略部分代码
let btn = UIButton(type: UIButton.ButtonType.detailDisclosure)

添加按钮(带加号)

  • contactAdd

21.png

// 省略部分代码
let btn = UIButton(type: UIButton.ButtonType.contactAdd)

关闭按钮

  • close

22.png

import UIKit

class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    // 将背景设置成白色
    view.backgroundColor = UIColor.white

    // 创建UIButton实例
    let btn = UIButton(type: UIButton.ButtonType.close)

    // 设置按钮位置与尺寸
    btn.frame = CGRect(x: 20, y: 100, width: 30, height: 30)

    // 添加到当前视图
    self.view.addSubview(btn)
  }
}

自定义按钮

  • custom

自定义按钮比较可以自定义更多配置,比如点击时文本的颜色可以自己配置。这个配置在 system 类型的按钮是不生效的,只有在 custom 才起作用。

23.gif

import UIKit

class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    // 将背景设置成白色
    view.backgroundColor = UIColor.white

    // 创建UIButton实例【自定义按钮】
    let btn = UIButton(type: UIButton.ButtonType.custom)
    // 也可以这样写
    // let btn = UIButton(type: .custom)

    // 设置按钮位置与尺寸
    btn.frame = CGRect(x: 20, y: 100, width: 100, height: 30)
    
    // 设置按钮的背景色
    btn.backgroundColor = UIColor.blue
    
    // 设置按钮标题
    btn.setTitle("雷猴", for: .normal)
    
    // 按钮点击时的样式(要设置 custom)
    btn.setTitleColor(UIColor.systemPink, for: .highlighted)

    // 添加到当前视图
    self.view.addSubview(btn)
  }
}

在这个例子中,通过 setTitleColor 设置点击时按钮文本颜色。

图片按钮

如果要设置按钮的图片或者背景图,也需要使用自定义按钮 custom

要使用图片作为按钮内容有2个方法:setImagesetBackgroundImage

首先需要将图片添加到 Assets 文件夹中,可以把图片拖拽进去,也可以在空白处右键,选择 Import 进行导入。

24.png

我这里导入了一张 car 的图片。

然后在代码中可以这样写:

25.gif

// 省略部分代码

let btn = UIButton(type: .custom)

btn.frame = CGRect(x: 20, y: 100, width: 100, height: 30)


// 设置按钮图片内容
btn.setImage(UIImage(named: "car"), for: .normal)

// 或者 设置按钮背景图
btn.setBackgroundImage(UIImage(named: "car"), for: .normal)

UIImage(named: "car") 里的 named 要填写图片文件名称。

点击事件

作为一个按钮,它的本质工作就是被人点击。

所以理所当然要监听一下点击事件啦。

// 省略部分代码

override func viewDidLoad() {
  super.viewDidLoad()
  let btn = UIButton(type: UIButton.ButtonType.system)
  
  // 绑定一个点击事件:buttonTapped
  btn.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
  // 也可以这样写
  // btn.addTarget(self, action: #selector(touchBegin), for: UIControl.Event.touchUpInside)
}

// 点击事件
@objc func buttonTapped() {
  print("点击了")
}

addTarget() 方法的作用是给控件添加一个事件,需要传入3个参数:

  • 参数1: 要触发事件的对象,传入 self 就是当前控件的意思。
  • 参数2: 传入一个方法选择器 selector ,这个选择器决定要执行哪个方法。
  • 参数3: 触发方法的条件。

图片

本来打算把图片、视频音频放在往后一点的章节连着一起讲,它们都属于媒体控件。但在讲解按钮部分已经用过图片了,而且图片也是非常常用的控件,那就先放在这里讲解吧~

基础用法

要在视图上展示图片需要做以下几步:

  1. 把图片添加到 Assets 目录里。
  2. 使用 UIImage 控件加载图片素材。
  3. 使用 UIImageView 控件展示图片。
  4. UIImageView 控件添加到视图里。

还是拿回前面的汽车图片做例子

26.png

import UIKit

class ViewController: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()
    // 将背景设置成白色
    view.backgroundColor = UIColor.white
        
    // 【步骤2】创建图片控件
    let img = UIImage(named: "car")

    // 【步骤3】创建展示图片的控件
    let imgView = UIImageView(image: img)
    // 设置UIImageView控件的位置和尺寸
    imgView.frame = CGRect(x: 30, y: 100, width: 292, height: 146)

    // 【步骤4】将控件添加到视图上
    self.view.addSubview(imgView)
  }
}

获取图片尺寸

上面这个例子中我是怎么知道图片的宽高深292*146呢?(这句代码: CGRect(x: 30, y: 100, width: 292, height: 146) )

方法1就是手动查看图片文件信息。

方法2可以使用 img?.size 获取。

运行项目后,控制台就会输出当前图片的尺寸。

// 省略部分代码

let img = UIImage(named: "car")
let size = img?.size
print(size)

如果图片真实尺寸与控件设置的尺寸不一致,图片的宽高比就会被改变。

输入框 UITextField

基础用法

输入框也是比较常见的控件。创建输入框要用到 UITextField 控件,通常会做以下2步:

  1. 创建输入框控件(UITextField)。
  2. 设置输入框边框风格。
  3. 将输入框添加到视图。

27.gif

import UIKit

class ViewController: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()
    // 将背景设置成白色
    view.backgroundColor = UIColor.white
        
    // 创建输入框实例
    let textField = UITextField(frame: CGRect(x: 20, y: 100, width: 200, height: 30))

    // 设置输入框边框风格
    textField.borderStyle = UITextField.BorderStyle.roundedRect

    // 将控件添加到视图上
    self.view.addSubview(textField)
  }
}

在这个例子中设置边框风格时我用了 roundedRect 圆角矩形,除了这个之外还有其他风格:

  • none: 无边框
  • line: 直线边框
  • bezel: 贝塞尔风格边框
  • roundedRect: 圆角边框

这些风格工友们在自己的工程上试试就行,我这里就不展示了。

设置样式

简单的样式我就直接给代码了,要设置复杂点的样式再细致讲解。

设置本文颜色

28.png

// 省略部分代码

// 设置为蓝色
textField.textColor = UIColor.blue

文本对齐方式

29.png

// 省略部分代码

// 居中对齐
textField.textAlignment = NSTextAlignment.center

placeholder

30.png

// 省略部分代码

textField.placeholder = "随便输入点东西吧"

自定义边框样式

要自定义边框样式,首先需要将默认的边框去掉。

如果没配置过 borderStyle 那默认就是没边框了。

如果要写明不需要边框可以这样写:textField.borderStyle = .none

之后就可以用以下方法配置边框样式了。

31.png

// 省略部分代码

// 设置边框颜色
textField.layer.borderColor = UIColor.blue.cgColor

// 设置边框宽度
textField.layer.borderWidth = 4

// 设置边框圆角
textField.layer.cornerRadius = 10.0

内边距

要设置输入框的内边距(内边距是指文本与边框之间的距离),可以使用 UITextFieldeditingRect(forBounds:) 方法来自定义文本区域的位置和大小。

32.png

import UIKit

class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    // 将背景设置成白色
    view.backgroundColor = UIColor.white
    
    let textField = PaddedTextField(frame: CGRect(x: 50, y: 100, width: 200, height: 30))
    
    textField.borderStyle = .line
    
    view.addSubview(textField)
  }
}

class PaddedTextField: UITextField {
  override func editingRect(forBounds bounds: CGRect) -> CGRect {
    return bounds.insetBy(dx: 20, dy: 2) // 设置内边距
  }
}

在这个例子中我创建了 PaddedTextField 子类,它继承了 UITextField。在 PaddedTextField 中重写了 editingRect(forBornds:) 方法,并返回了调整过文本区域的位置的 CGRect

insetBy(dx:dy:) 方法用来设置文本区域相对于边框的内边距。

前置元素和后置元素

可以通过 leftView 配置输入框的前置元素,通过 rightView 配置后置元素。

先看看图

33.png

import UIKit

class ViewController: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()
    // 将背景设置成白色
    view.backgroundColor = UIColor.white
        
    // 创建输入框实例
    let textField = UITextField(frame: CGRect(x: 20, y: 100, width: 200, height: 30))

    // 设置输入框边框风格
    textField.borderStyle = .line
    
    // 设置前置元素
    let label1 = UILabel(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
    // 前置元素内容
    label1.text = "https://"
    // 将前置元素添加到输入框里
    textField.leftView = label1
    // 设置元素的显示模式
    textField.leftViewMode = UITextField.ViewMode.always


    // 设置后置元素
    let label2 = UILabel(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
    // 后置元素内容
    label2.text = ".com"
    // 后置元素字号
    label2.font = UIFont.systemFont(ofSize: 20)
    // 后置元素文本颜色
    label2.textColor = UIColor.blue
    // 将后置元素添加到输入框里
    textField.rightView = label2
    // 设置元素的显示模式
    textField.rightViewMode = UITextField.ViewMode.always
        
    self.view.addSubview(textField)
  }
}

除了可以设置文本外,还可以使用图片作为输入框的前置或者后置元素。

获取输入框内容

本例通过点击按钮获取输入框的内容。

33.png

import UIKit

class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    // 将背景设置成白色
    view.backgroundColor = UIColor.white

    // 创建输入框实例
    let textField = UITextField(frame: CGRect(x: 20, y: 100, width: 200, height: 30))

    // 设置输入框边框风格
    textField.borderStyle = .line
    
    self.view.addSubview(textField)
    
    // 按钮
    let button = UIButton(type: .system)
    button.setTitle("获取文本", for: .normal)
    button.addTarget(self, action: #selector(getText), for: .touchUpInside)
    button.frame = CGRect(x: 50, y: 150, width: 200, height: 30)
    self.view.addSubview(button)
  }
  
  @objc func getText() {
    if let textField = view.subviews.first(where: { $0 is UITextField }) as? UITextField {
      if let text = textField.text {
        print("输入的文本内容是:\(text)")
      }
    }
  }
}

getText() 方法中,首先通过 view.subviews.first(where:) 方法找到界面中的 UITextField 实例。然后,通过 textField.text 属性获取输入框的文本内容。

使用 text 属性可以获取用户在输入框中输入的文本内容。

事件委托

除了前面使用的方法获取到文本框输入到内容外,还可以使用事件委托去监听输入框的一些事件。

import UIKit

class ViewController: UIViewController,UITextFieldDelegate {

  override func viewDidLoad() {
    super.viewDidLoad()
    // 将背景设置成白色
    view.backgroundColor = UIColor.white
    // 创建输入框实例
    let textField = UITextField(frame: CGRect(x: 20, y: 100, width: 200, height: 30))
    // 设置输入框边框风格
    textField.borderStyle = .line
    textField.delegate = self
    self.view.addSubview(textField)
  }
    
  // 在输入框即将进入编辑状态时调用
  func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
    print("准备开始~")
    return true
  }
    
  // 在输入框已经开始编辑时调用
  func textFieldDidBeginEditing(_ textField: UITextField) {
    print("正在编辑")
  }
    
  // 在输入框即将结束编辑时被调用
  func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
    print("即将结束编辑")
    return true
  }
    
  // 在输入框已经结束编辑时调用
  func textFieldDidEndEditing(_ textField: UITextField) {
    print("已经结束编辑")
  }
    
  // 在输入框中的文本发生变化时调用
  func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    // 发生变化的位置
    print(range, range)
    // 输入的内容
    print(string)
    // 返回true表示允许当前的输入操作
    return true
  }
}

可以通过 textField() 监听输入的内容,如果内容不符合要求返回 false 就可以禁止当前的输入了。

推荐阅读

本文已经有点长了,下一篇再继续水吧~

下一篇会讲如何打包发布应用。

👍《p5.js 光速入门》

👍《StreamSaver.js入门教程:优雅解决前端下载文件的难题》

👍《Annotorious.js 入门教程:图片注释工具》

点赞 + 关注 + 收藏 = 学会了

posted @ 2023-08-23 21:35  德育处主任  阅读(22)  评论(0编辑  收藏  举报