21.3 小结

第21章 项目实战

21.1 发布流程

本章将会带领读者开发一个完整的应用程序,其中会使用之前章节讲到的很多知识。不过在此之前,需要介绍下iOS应用的发布流程:

1.首先要申请一个开发者账号

这个账号也是一个App ID账号,读者可以使用自己的App ID或者另外申请用于应用发布的App ID。有了App ID之后,可以访问https://developer.apple.com/programs/enroll/进行开发者账号的注册。对于普通开发者来说,可以选择个人账号或者公司账号,费用一年99美元,约700元人民币;有特殊需要不用上架App Store的开发者,则可以选择企业账号,一年299美元,约2000元人民币。

2.创建App ID

有了开发者账号之后,进入苹果的开发者中心https://developer.apple.com/account,然后点击“Certificates,Identifiers& Profiles”,在左侧边栏选择“App IDs”,点击加号新建一个新的App ID,将应用的信息填写清楚。这里要注意的是,Bundle ID使用与Xcode中一致的设定,比如“com.01kuaixue.*”,这个是提交之后就不可以再更改的,所以一定要注意。最后选择所需的App Services,比如支付、通知、iCloud等,在选择完成之后点击“Continue”便创建完成了。

3.申请发布证书

在创建完App ID之后就可以申请发布证书。首先在macOS的电脑上打开Keychain Access(钥匙串访问)应用,点击顶栏上的“Keychain Access”按钮,选择“Certificate Assistant(证书助手)”,会有如下选择框,点击图21.1.1中所示的选项:

图21.1.1 钥匙串访问的证书助理

接着按图21.1.2所示,根据自己的情况选择证书的信息,选择存储到磁盘这个选项,点击“Continue”选择需要保存证书的位置。

图21.1.2 填写证书信息

完成之后,回到苹果开发者中心的网站上,在左侧边栏中选择Certificates->All,再点击右上角的加号,这样就可以添加新的证书了。证书分为开发Development和发布Production。发布证书在Production下,读者可以选择App Store和Ad Hoc的版本,再点击“Continue”。最后需要上传一个CSR文件:选择刚刚用钥匙串访问生成的文件,然后点击网页上的“Generate”按钮即可。同样地,也可以选择创建开发者证书。

4.申请描述文件(provisioning profile)

在苹果开发者中心的网站上,在左侧边栏中选择Provisioning Profiles->All,再点击右上角的加号,这样就可以添加新的描述文件了。与证书类似,描述文件也分为开发Development和发布Distribution。发布描述文件在Distribution下,读者可以选择App Store版本,再点击“Continue”,之后需要选择一个App ID,也就是第一步中所创建的。接着选择证书,也就是上一步创建好的内容,如果是开发者的描述文件,必须再选择一个或者一个以上的iOS设备作为真机调试的对象,最后点击“Generate”按钮。证书和描述文件都可以下载下来,并且双击就可以进行安装了。

5.归档应用

在Xcode顶栏中,选择Product按钮下的“Archive”就可以对应用进行归档。有时候Archive按钮会被置灰,因此需要读者将Build Device指向“Generic iOS Device”,如图21.1.3所示:

图21.1.3 选择Build Device

Archive完成之后,会出现图21.1.4中所示的界面,如果读者已经登录了开发者账号,那么就可以点击“Upload to App Store”按钮了。

图21.1.4 Archive完成之后

6.发布应用

在点击上传到App Store前,需要读者确认自己是否已经创建了App。进入iTunes Connect网站https://itunesconnect.apple.com/,登录开发者账号,选择“我的App”,如果还没有App,需要新建一个App。等App创建完成之后,再点击“Upload to App Store”,之后就可以根据提示进行一系列操作了。在上传完应用之后,一般需要几个工作日的审核,应用才可以上架。

21.2 倒计时提醒应用

21.2.1 应用功能的确定

在正式开始动手开发应用之前,读者需要明确应用的主要功能点,这也是日常开发时首先需要考虑的内容。倒计时提醒应用主要用于告诉用户记录的重要事件还有多少天来临,应用打开时会有一个列表页展示所有的事件,可以使用UITableView控件。导航栏需要有一个添加按钮用于增加提醒事项,可以为Navigation Item增加Bar Button Item。点击事件之后可以进入一个详情页(应该和添加页一样),用户可以用它修改事件的内容和时间,需要构造一个新的视图控制器用于事件的添加或者修改。

21.2.2 创建项目

创建Practise项目,根据倒计时提醒应用的特性,应用需要一个导航控制器作为根视图控制器。那么在Main.storyboard中添加一个Navigation Controller,并将其导航至View Controller,如图21.2.1所示:

图21.2.1 使用导航控制器作为应用的根视图控制器

接着在View Controller里的Navigation Item中设置title为“倒计时”,如图21.2.2所示:

图21.2.2 为View Controller添加title

Xcode很快就将修改呈现在了界面上。如果要为导航栏添加按钮,可以在Object Library中找到“Bar Button Item”将其拖拽至View Controller的Navigation Item上,放在右边,如图21.2.3所示:

图21.2.3 为View Controller的Navigation Item添加Bar Button Item

可以在Xcode中选中“Bar Button Item”,点击“Attributes Inspector”简单设置样式,本例中使用系统提供的Add样式,如图21.2.4设置:

图21.2.4 设置Bar Button Item样式

Bar Button Item可以像UIButton那样,与View Controller建立属性和事件的关联。本例中添加事件的关联,代码如下:

接着创建一个新Cocoa Touch类,并定义为UIViewController的子类,产生相应的XIB文件,如图21.2.5所示:

图21.2.5 生成一个UIViewController的子类

细心的读者会发现这个视图控制器的命名中加了“LYKX”的前缀,这是苹果推荐的做法,可以防止与系统自带的类型产生命名的冲突。一般建议前缀是三到四个大写字母,根据读者自己的需要设置,双字母前缀为Apple的类预留。本例中使用“零壹快学”的拼音首字母作为前缀。

与LYKXEventViewController同时生成的还有LYKXEventViewController.xib,可以使用AutoLayout为其添加一个UITableView,如图21.2.6所示:

图21.2.6 为LYKXEventEditViewController.xib添加Table View

为其关联LYKXEventEditViewController的属性如下:

动手写21.2.1 Practise-> LYKXEventEditViewController.swift

现在应用需要的两个展示页面就创建好了,这是应用的主要结构。接着要为其添加具体的内容了。

21.2.3 创建自定义的Cell

添加事件的页面使用UITableView来作为控件承载视图,读者需要为这个UITableView增加各种自定义的Cell放置编辑控件。读者也可以直接在View Controller的视图中添加这些控件,但是出于灵活性和可复用性的考虑,本书还是建议将响应的控件单独作为一个Cell来使用。本例中,需要一个用于输入事件名称的Cell,一个用于展示时间的Cell,一个用于选择时间的Cell,以及一个用于输入备注信息的Cell。

从最基本的输入事件名称的Cell开始,创建一个LYKXTextInputTableViewCell,同时勾选上创建XIB文件,如图21.2.7所示:

图21.2.7 添加LYKXTextInputTableViewCell

然后往其XIB文件中拖入一个UITextField放置在其中间,使用AutoLayout约束其位置,如图21.2.8所示:

图21.2.8 为LYKXTextInputTableViewCell添加UITextField

读者可以在Attributes Inspector中设置这个TextField中文字的大小和边框样式等配置属性,这些配置属性的含义在第12章已经介绍过,这里就不多做解释。接着为这个TextField与LYKXTextInput TableViewCell的textField属性建立连接,代码如下:

动手写21.2.2 Practise-> LYKXTextInputTableViewCell.swift

接着添加一个用于展示时间的Cell,如图21.2.9所示:

图21.2.9 添加LYKXTimeDisplayTableViewCell

然后往其XIB文件中拖入三个UILabel,使用AutoLayout对这三个UILabel进行布局。如图21.2.10所示:

图21.2.10 为LYKXTimeDisplayTableViewCell添加UILabel

第一个展示“时间”的Label不需要与Cell中的属性进行连接,而Year Month Label及Time Label需要与Cell中的属性连接,代码如下:

动手写21.2.3 Practise-> LYKXTimeDisplayTableViewCell.swift

有时间展示的Cell就会有时间选择的Cell。创建Cell,如图21.2.11所示:

图21.2.11 添加LYKXTimePickerTableViewCell

然后往其XIB文件中拖入一个UIDatePicker,使用AutoLayout对这个UIDatePicker进行布局,如图21.2.12所示:

图21.2.12 为LYKXTimePickerTableViewCell添加UIDatePicker

将这个UIDatePicker与Cell中的属性进行连接,代码如下:

动手写21.2.4 Practise-> LYKXTimePickerTableViewCell.swift

最后添加一个可以输入多行文本的Cell,如图21.2.13所示:

图21.2.13 添加LYKXTextViewTableViewCell

然后往其XIB文件中拖入一个UITextView,使用AutoLayout对这个UITextView进行布局,如图21.2.14所示:

图21.2.14 为LYKXTextViewTableViewCell添加UITextView

将这个UITextView与Cell中的属性进行连接,代码如下:

动手写21.2.5 Practise-> LYKXTextViewTableViewCell.swift

创建完成之后就可以通过LYKXEventEditViewController将这些Cell组织起来了。

21.2.4 搭建添加事件的界面

在搭建LYKXEventEditViewController的界面之前,需要在View Controller中添加展示下个页面的逻辑,代码如下:

动手写21.2.6 Practise-> ViewController.swift

本例中使用UINavigationController作为初始的视图控制器,并且在Main.storyboard中设置了View Controller作为UINavigationController的根视图控制器。在展示界面时,需要借助视图控制器的navigationController属性,通过调用其present(_:animated:completion:)方法来展示新的界面。代码中创建了一个LYKXEventEditViewController的实例作为参数传入了present方法,这样添加事件的界面就可以在点击添加按钮之后浮现出来。

现在回到LYKXEventEditViewController的开发中,首先要在viewDidLoad中将editTableView的delegate和dataSource属性设置为视图控制器本身。此外还要将之前创建的自定义Cell注册到editTableView中,代码如下:

动手写21.2.7 Practise-> LYKXEventEditViewController.swift

接着补充LYKXEventEditViewController需要实现的协议,代码如下:

因为添加事件的界面中使用UITableView,所以需要遵循它的两个协议。在实现协议中的方法之前,读者需要提供一些固定的常量用于配置界面的一些展示信息。本例将Cell渲染中涉及的一些值都存放在View Controller的常量中,代码如下:

动手写21.2.8 Practise-> ViewController.swift

这里定义了输入内容的类型LYKXCellInputContentType、cell的类型LYKXEditCellType以及一些高度和读取配置的键。根据这些定义和常量,可以对UITableView进行配置,在LYKXEventEdit ViewController的类中添加如下代码:

动手写21.2.9 Practise-> LYKXEventEditViewController.swift

这里使用一个数组去装载每个tableView的section信息,而每个数组的元素中又用一个数组去装载当前section中每个row的信息。本例中的配置将tableView拆分成两个section,第一个section用于展示事件输入框和时间,第二个section用于展示备注信息的输入框。有了这个配置之后,就可以使用UITableViewDelegate和UITableViewDataSource的方法去展示。没有这个配置也可以直接在代码中进行展示的逻辑,这个配置只是辅助界面渲染的。下面是实现协议的方法,代码如下:

动手写21.2.10 Practise-> LYKXEventEditViewController.swift

本例中一共实现了五个函数,第一个用于指定section的数量,即editCellsConfig数组的个数;第二个用于指定特定section中的行数;第三个用于提供指定indexPath的Cell高度;第四个提供指定indexPath的Cell实例;第五个提供了一个便捷方法获取指定indexPath的配置字典。这里要注意的是,在LYKXEventEditViewController的xib中要将UITableView的Style设置为Grouped,如图21.2.15所示:

图21.2.15 设置UITableView的Style属性

此时读者运行模拟器,点击右上角的加号按钮,就会弹出一个简单的事件编辑页面,不过只是一个固定版本,如图21.2.16所示:

图21.2.16 原始的编辑界面

21.2.5 事件的数据结构

在计算机的世界中,一个事件需要一个模型来对应,本例中就创建了一个LYKXEventModel类来映射事件。它是一个Swift的类,并不用继承自NSObject,通过定义属性去存储事件的数据,代码如下:

动手写21.2.11 Practise-> LYKXEventModel.swift

这里需要存储一个事件的名称、一个事件的唯一标示ID、一个事件的时间和一个事件的备注,这些属性的类型都可以直接被SQLite数据库存储。如果存储了不能被SQLite数据库接受的类型,读者可能需要在中间进行转换。

接着为其增加初始化方法,代码如下:

event_id通过创建实例时的一个13位时间戳作为值被存储,一旦被写入数据库就不可更改了。time通过当前时间的时间戳作为值被存储。

接着创建两个类常量yearMonthDF和timeDF,它们都是DateFormatter的实例,之后提供展示信息的函数需要用到。定义canSave、yearMonthDesc和timeDesc三个函数分别用于判断这个事件是否可以被存储、提供年月字符串以及时间字符串,代码如下:

动手写21.2.12 Practise-> LYKXEventModel.swift

这样,事件的数据结构就定义好了。

21.2.6 事件的数据库读写

有了事件的模型之后,就可以将其写入数据库以及从数据库中读取。为数据库读写创建管理类LYKXEventManager,代码如下:

动手写21.2.13 Practise-> LYKXEventManager.swift

database属性用于存储创建好的数据库的连接指针,createResult用于存储创建数据库的结果。创建数据库的代码如下:

动手写21.2.14 Practise-> LYKXEventManager.swift

这个函数从创建数据库到创建表一气呵成。如果创建失败会返回false,如果创建成功则会返回true,并且database会赋上数据库连接的指针,用于之后的插入和查询操作。创建好数据库之后就可以实现插入数据库的函数,代码如下:

动手写21.2.15 Practise-> LYKXEventManager.swift

为了方便其他类调用插入函数,这里接受LYKXEventModel的实例作为传入参数,接着执行SQL语句进行插入,最后返回插入结果。这里需要注意:要使用“replace into”作为插入语句,这样可以保证在unique的字段已经存在的情况下,通过替换的方式将一条记录插入数据库。在创建数据库时,已经使用event_id作为unique的键,这样可以保证全表所有记录的event_id不会重复,那么通过insertEvent方法就可以进行替换操作,比如不是新建一个事件而是替换。

最后是查询函数的实现,代码如下:

动手写21.2.16 Practise-> LYKXEventManager.swift

这里主要通过sqlite3_step()函数来执行statement,并且将结果转换为一个LYKXEventModel的实例。通过sqlite3_column_*()的一系列方法将结果中的值转换为Swift中的类型,然后赋值到实例的属性上,再添加到数组的末尾。最终将整个数组返回,如果查询失败则返回一个空数组。到此为止,事件的数据库读写就完成了。下面我们要将添加事件的功能开发实现。

21.2.7 开发添加事件的功能

回到LYKXEventEditViewController中,因为实际上这个页面主要是编辑一个事件的数据,所以需要重写初始化的方法,代码如下:

动手写21.2.17 Practise-> LYKXEventEditViewController.swift

需要提供一个init()方法,这个方法传入的参数是一个LYKXEventModel的实例,如果外部没有使用这个初始化方法,则在init()中会默认创建一个,这样就需要一个属性去存储这个事件,实例如下:

接着为UITableView的Cell添加上eventModel中的数据,在UITableView的两个协议方法中修改,代码如下:

动手写21.2.18 Practise-> LYKXEventEditViewController.swift

为事件输入Cell的textField增加placeHolder,即没有输入任何字符时的提示,以及设置delegate属性为LYKXEventEditViewController的实例。为时间展示的Cell增加日期的赋值操作,展示事件提醒的时间。为事件备注输入的textView也设置placeHolder。其中还有一个本例中未被用到的时间选择的Cell,这里先为其添加值变化之后的回调函数timeValueChange以及附上eventModel的time属性。最后为备注输入的UITextView设置了placeHolder。下面将对这些内容进行剖析。

LYKXTextInputTableViewCell中,首先声明了一个协议,代码如下:

动手写21.2.19 Practise-> LYKXTextInputTableViewCell.swift

这个协议实现了NSObjectProtocol并且声明了新的实现方法,调用时会传入一个Cell和一个textField参数,在之后的实现中会将Cell本身和自身的textField传入。接着在Cell的awakeFromNib的方法中通过NotificationCenter去监听textField的文字变化的通知。

然后在通知回调的textFieldDidChangeText()方法中调用delegate实现的协议方法,这样就可以获取到textField已经变化的情况。

在LYKXTextViewTableViewCell中,增加一个UITextView的placeHolder属性用于展示无字符输入时的展示情况,并且在设置时,增加对当前textView文字变化的监听。如果已经有文字输入则不展示placeHolder,因为在扩展中不允许添加新的存储属性,所以添加一个只读属性PlaceHolderTag用于返回一个视图的tag值,另一个只读属性placeHolder用于返回一个UILabel的实例作为placeHolder。对于每个UIView以及UIView子类的实例,读者都可以为其设置tag属性的值,再通过视图的父视图调用viewWithTag方法便能获取到指定的视图,代码如下:

动手写21.2.20 Practise-> LYKXTextViewTableViewCell.swift

为了美观,在awakeFromNib中将textView的textContainer属性的lineFragmentPadding设置为0,此时输入框的文字紧贴输入框的左侧,代码如下:

动手写21.2.21 Practise-> LYKXTextViewTableViewCell.swift

两个Cell的自定义功能实现之后,需要实现点击Cell的方法。在本例中,点击时间展示Cell会展开时间选择Cell。切换到LYKXEventEditViewController类中,实现UITableViewDelegate的选择Cell的方法如下:

动手写21.2.22 Practise-> LYKXEventEditViewController.swift

当点击时,如果没有展示时间选择Cell则通过insert的方式展示,如果已经展示则使用remove的方式移除。在时间发生变化时,会调用timeValueChange()方法,实现如下:

这里重载了时间展示的Cell,这样更新的时间就会被展示在Cell上。此外还要为LYKXEventEdit ViewController实现LYKXTextInputTableViewCellDelegate的协议,首先类声明增加满足的协议,代码如下:

动手写21.2.23 Practise-> LYKXEventEditViewController.swift

接着实现协议中声明的函数如下:

通过Cell找到对应修改的内容,如果是事件名称,则将textField中的text赋值上去。通过textView的text修改备注信息。本例使用updateDoneButtonStatus()方法,实现如下:

动手写21.2.24 Practise-> LYKXEventEditViewController.swift

self.navigationItem.rightBarButtonItem是“添加”或者“修改”按钮,在viewDidLoad时添加到导航栏上,代码如下:

动手写21.2.25 Practise-> LYKXEventEditViewController.swift

这里需要实现两个方法,点击导航栏左侧按钮或者右侧按钮,代码如下:

动手写21.2.26 Practise-> LYKXEventEditViewController.swift

点击左侧取消按钮时,判断自己的视图是被present还是被push进来的,然后直接退出;点击右侧添加或者修改按钮时,通过delegate方式去保存eventModel,然后也退出。在LYKXEventEdit ViewController类中声明协议如下:

动手写21.2.27 Practise-> LYKXEventEditViewController.swift

并且添加delegate属性:

至此,整个LYKXEventEditViewController的开发就完成了。

21.2.8 开发事件展示的界面

切换到View Controller中,现在要展示添加的事件,并且处理修改页面的回调。先看类声明的变动,代码如下:

动手写21.2.28 Practise-> ViewController.swift

这里增加支持的协议,包括UITableViewDelegate、UITableViewDataSource和LYKXEventEditView ControllerDelegate三个。然后增加数据库操作管理类的实例,以及存储所有事件的数组属性,代码如下:

动手写21.2.29 Practise-> ViewController.swift

重写初始化方法,在初始化时创建数据库操作管理类,代码如下:

动手写21.2.30 Practise-> ViewController.swift

在viewDidLoad中为UITableView注册Cell以及设置delegate和dataSource属性,同时查询数据库中的事件,并且重载UITableView,代码如下:

动手写21.2.31 Practise-> ViewController.swift

实现UITableView的协议方法,代码如下:

动手写21.2.32 Practise-> ViewController.swift

这里使用了系统默认的UITableViewCell,借助其自带的属性进行展示,点击之后跳转到编辑页面。在展示时,通过判断事件的时间来决定展示信息,包括过期、当天和未来三种情况,如果当天没有过期则会显示还剩多少时间。此外,在Cell的detailLabel上展示了备注信息,这里用到了几个常量可在类声明之外定义,代码如下:

动手写21.2.33 Practise-> ViewController.swift

在完成展示事件的开发之后,回到添加事件的方法中,增加一行代码,如下:

动手写21.2.34 Practise-> ViewController.swift

将添加事件页面的delegate属性设置为自己,然后实现其保存事件的回调方法,如下:

在编辑事件的页面中点击添加或修改按钮,则会保存事件到数据库,并且重载tableView。至此整个倒计时应用就开发完成了。

21.3 小结

本章向读者介绍了iOS开发的流程,并且通过一个实战例子讲述了视图控制器与控件之间如何组织起一个iOS应用。经过本书的系统学习,相信读者在Swift语言以及iOS开发方面的能力都得到了提高。在日后的iOS开发中,读者还需要不断阅读苹果公司提供的文档资料进行更广泛和深入的学习。iOS系统每年都会有升级,会不断地带来新生事物,我们要抱着 “Stay hungry, Stay foolish”的态度继续前行!