Swift是苹果公司于年发布的编程语言,它强大而直观,适用于macOS、iOS、watchOS和AppletvOS等平台软件的开发。Swift语法简洁、表现力强,由于它非常的年轻,因此融合了很多现代语言的优点。
Swift的进化堪称恐怖,每年都以肉眼可见的速度衍生出新功能、新特性。没办法,爹有钱啊。
本文将以入门者的角度,开发一款简单的待办清单App,你一定可以从中感受到Swift开发iOSApp的乐趣和强大。
写给完全没接触过、又对iOSApp开发感兴趣的你的。没有Mac电脑也没关系,我们不纠结具体语法,走马观花了解Swift写App的魔力就足够了,然后再决定你要不要学这门新鲜的语言。
准备工作Xcode是进行iOSApp开发的集成开发工具。在AppStore下载完成后,点击打开就进入了它的欢迎界面:
点击CreateanewXcodeproject,创建新的项目。
进入到新的界面:
选择要开发的软件类型,这里我们肯定选择iOSAPP开发了。
点击下一步后进入项目详情界面:
ProductName:项目名称。Team:开发团队。如果没有的话可能这里会提示你申请一个,通常用苹果账号就可以了。(就是你iPhone手机的那个账号)OrganizationIdentifier:机构识别码,通常是你的网站的反写。如果没有,随便写一个也Ok。Interface和Language规定了开发所使用的语言和框架。选择SwiftUI和Swift。其他条目按照图片中填写就行了,然后点击下一步,进入下一个界面:
在此界面中选择项目需要保存的位置,点击创建按钮,项目就生成了。
Xcode的界面这里就不赘述了,读者可以自己摸索摸索。
提醒一下,记得将图片中序号1的渲染机型修改为iPhone13Pro或者你熟悉的手机机型。
HelloWorld让我们仔细观察ContentView.swift文件,此文件大体上分为三块:
importSwiftUI导入了Swift的核心框架,暂时先不管它。尾部的结构体ContentView_Previews定义了模拟器中需要渲染的内容,也不管它。中间的结构体ContentView,定义了手机页面的外观,它就是核心了。观察ContentView,现在我们不用去关心什么是varbody、什么是someView,只需要知道varbody后面花括号里的东西,直接代表了手机上界面的样子,就足够了。
所以那花括号里有什么?来看看:
Text("Hello,world!").padding()
就算你完全没学过编程,看单词也能猜到个大概的意思:
Text("Hello,world!"):页面中有一行文字,内容是Hello,world!。.padding():文字周围用空白进行了某个宽度的填充。是这样的吗?点击上图中模拟器面板的Resume按钮,渲染效果试试看:
其实这里就能看出SwiftUI框架有意思的地方了:它描述了“UI应该是什么样子”,而不是用一句句代码指导“应该怎样去构建UI”。
举个例子,如果用传统的命令式开发模式,我们在代码中需要创建文本类、设置它的文字内容、将其添加到UI上、并且进行布局:
//页面启动完成的生命周期函数funcviewDidLoad(){super.viewDidLoad()//创建文本对象letlabel=UILabel()//给文本赋值label.text="Hello,World!"//将文本对象添加到主UI中view.addSubview(label)//下面省略了大一串布局代码//...}
相对的,用本文中聊到的声明式开发模式,你只要告诉程序“我需要一行文本”就可以了:
varbody:someView{Text("Hello,world!")}
是不是清爽多了?
状态和列表欣赏完项目自动生成的代码后,接下来就要改动ContentView里的内容,让其变成一个可以展示待办清单列表的迷你项目了:
//struct表示结构体,如果以前没接触过,你暂时把它理解成class类就好了//(实际上结构体和类有本质区别,这里不展开讲)structContentView:View{//
State修饰的变量表示本地状态//todoList的任何改变,都会自动触发UI的重新渲染StateprivatevartodoList=["Apple","Pear","Tomato"]varbody:someView{//列表控件List{//循环渲染元素ForEach(todoList,id:\.self){iteminText(item)}}}}被
State修饰的变量,代表了它是被管理起来的本地状态,用于UI中数据的双向绑定。再看看varbody中的改动:
List表示这里有一个列表。ForEach表示列表的内容将依靠本地状态todoList动态生成。里面的id:\.self暂时不用去深究它,你只需要知道它用于识别列表中每个元素的身份就可以了。item是列表的每个元素,用Text(item)展示了其文本内容。如果你学习过Vue,那么理解起来就更加容易了:
State就类似于Vue中的data;ForEach(...,id:...)和Vue中的v-for语法类似,需要一个id值作为列表元素的识别凭证。再一次提醒,作为新手了解向的文章,你真的不需要逐句去斟酌每个对象、关键字、变量的具体含义,只需要知道它们大概的作用就足够了。
代码就这么多了,来看看渲染效果:
用有限的几行代码,就创造出了一个简单而美观的列表,并且具有了良好的布局效果。
试试在模拟器中,将手机横向放置的布局效果,同样做到了很好的自适应,尽管你没写哪怕一行跟布局有关的代码。
自定义状态上面的代码中,我们用了一个字符串数组todoList,定义了一个本地状态。但是如此简单的数据结构,对一个待办清单来说显然差了点意思。最起码的,如何表现此条项目完成与否?
这时候就需要复杂一点的数据结构了,比如将自定义对象作为本地状态的列表元素。
首先定义一个结构体ToDoItem:
structToDoItem:Identifiable{letid=UUID()varisOn=trueletname:String}它满足Identifiable协议,因此后面代码的ForEach中就不用写难看的id:\.self了,程序会根据协议自动将ToDoItem里的letid=UUID()作为身份识别符。isOn是一个可更改(var)的布尔值,表征了此对象是否已完成。name是一个不可更改(let)的字符串,表征了对象的具体文本内容。
很好,接下来将ContentView修改成下面这样:
structContentView:View{//装饰器
State表示todoList是一个受监控的本地状态StateprivatevartodoList=[ToDoItem(name:"Apple"),ToDoItem(name:"Pear"),ToDoItem(name:"Tomato")]varbody:someView{//列表元素List{//动态遍历渲染ForEach(todoList){iteminText(item.name)}}}}将本地状态的列表元素替换为了ToDoItem结构体。注意观察ForEach中的变化。渲染界面如下:
表面上和前一小节没有变化,但是内部的数据结构变为了自定义结构体,后续就可以愉快地持续扩展结构体里的属性了。
输入框和按钮对待办清单App来说,光有列表还不够,最起码得能输入新条目。所以得添加以下两个控件:
文本输入框TextField,用于输入新条目的文本。按钮Button,用于提交输入的文本。新增以下代码:
structContentView:View{
StateprivatevartodoList=[//省略已有代码...]//新增本地状态newName//用于接收用户输入的文本StateprivatevarnewName:String=""varbody:someView{//新增代码//VStack:垂直方向布局VStack{//HStack:水平方向布局HStack{//文本控件TextField("输入新事项",text:newName)//按钮控件Button("确认"){//点击按钮后执行的操作letnewItem=ToDoItem(name:newName)todoList.append(newItem)newName=""}}.padding()//省略已有代码List{//...}}}}有点轻车熟路了对吧,在Swift的世界里,尽量不让你指挥它“这个控件要如何执行初始化、那个界面要如何添加对象”,你只要告诉它“这里有什么”就行。
稍微研究下新写的代码:
新增了一个状态newName,用来放置用户在输入框里键入的文本。VStack和HStack分别代表垂直方向布局和水平方向布局。所以后面你可以看到,输入控件整体和列表是垂直布局的,而输入框和按钮是水平布局的。TextField是输入框控件,注意它第二个参数text:newName里的美元符号,这种特殊写法表示传入的newName是双向绑定的状态,而不是简单的传入了newName变量里面的值。双向绑定的意思是不管你在代码里改变newName的值,还是在输入框控件里修改,它两是完全同步变化的,并且这个变化会立刻反应到UI中。Button是按钮控件,点击后会创建一个新的列表元素,把它添加到todoList中,并且将输入框控制的文本清除。重新渲染模拟器,看看效果:
由于输入效果是动态的,所以你需要点一下上图箭头指的那个按钮,让渲染从静态切换为动态。
然后随便输入点东西,并点击确认按钮:
顺利的话,新条目就添加到列表里了:
怎么样,是不是有点意思?
让我们更进一步经过上面的折腾,虽然我们已经把列表数据转化为自定义结构体ToDoItem()了,但数据和界面还可以进一步解耦。随着项目逐渐扩展,程序架构需要更明确的分层和细化。
如果把UI界面描述为视图(View),数据描述为模型(Model),那么我们还需要一个桥梁,帮助视图和模型进行多对多的通信和数据流的双向绑定。
这个桥梁在上面的代码中是没有的,因此先来写它。
实际写项目时应该把视图、模型和“桥梁”都作为单独的文件。本文为了方便就没这么做了。
新增一个ToDoViewModel类如下:
classToDoViewModel:ObservableObject{//
Published装饰需要进行绑定的数据Publishedprivate(set)vartodoList:[ToDoItem]//初始化init(){self.todoList=[ToDoItem(name:"Apple"),ToDoItem(name:"Pear"),ToDoItem(name:"Tomato")]}//新增数据条目funcappend(_item:ToDoItem){todoList.append(item)}//改变条目是否完成的状态functoggle(_item:ToDoItem){//这一行代码将item的id赋值给index变量//语法原理暂时不要去深究ifletindex=todoList.firstIndex(where:{0.id==item.id}){//toggle()函数将布尔值反转todoList[index].isOn.toggle()}}}这个类的关键就在于用
Published装饰的todoList,它就是需要和视图进行绑定的数据模型。视图不应该直接修改模型,所以你看到有关键字private(set)限制了类外部的指令只能读取不能修改。对模型的修改要通过ToDoViewModel内置的方法。“桥梁类”里可以有多个Published装饰的模型,也可以提供给多个视图使用。接下来的模型没有变化,还是之前的那个结构体:
structToDoItem:Identifiable{letid=UUID()varisOn=trueletname:String}
最后是视图,稍微有点长:
structContentView:View{//
StateObject用于自定义的“桥梁类”,作用和前面的State差不多StateObjectprivatevarviewModel=ToDoViewModel()//newName状态无改动StateprivatevarnewName:String=""varbody:someView{VStack{//之前的TextField和Button//无改动HStack{//...}.padding()List{ForEach(viewModel.todoList){iteminHStack{//.foregroundColor根据isOn的状态改变文本的颜色//(a?b:c)被称为三元操作符Text(item.name).foregroundColor(item.isOn?.primary:.gray)//在两个元素间填充占位的空白Spacer()//Group里模拟了单选框//Group是和VStack类似的布局对象//注意它里面是可以写if这种控制流语句的//根据isOn的值,改变单选框的外观Group{ifitem.isOn{//Image调用了内置的图片//"circle"是个中空的圈Image(systemName:"circle")}else{//中间带勾的圈Image(systemName:"checkmark.circle.fill")}}//将单选框渲染为蓝色.foregroundColor(.blue)//定义了单选框的点击事件//点击后触发了“桥梁”类的toggle()方法.onTapGesture{viewModel.toggle(item)}}}}}}}看起来改了很多,但其实真的挺简单的:
自定义的“桥梁类”要用StateObject装饰。UI里Group那一坨模拟了单选框,单选框的外观会根据item.isOn布尔值来调整。来看看效果。刷新模拟器:
列表的每个元素多了个单选框。
点击单选框,就变成下面这样了:
文字变灰色,单选框外观也变成了带勾的完成状态。大功告成了。
没错,折腾完上面这个简单的例子,你已经摸到现在愈发主流和火热的架构模式:MVVM了!(Model-View-ViewModel)
在MVVM架构中View和Model不能直接通信,必须通过ViewModel。ViewModel是MVVM的核心,它通常是一个观察者,当Model数据发生变化时ViewModel能够监听并通知到对应的View做UI更新,反之当用户操作View时ViewModel也能获取到数据的变化并通知Model数据做出对应的更新操作。这就是MVVM中数据的双向绑定。而上面一直在说的“桥梁”,其实就是这个ViewModel(视图模型)了。
还挺酷的吧?
什么,暗黑模式已经搞定了?主流的App都具有两种色彩模式,白天是明亮模式;夜间自动切换到暗黑模式,保护大半夜葛优瘫的你,和你那迷人的小眼睛。
那该如何实现两种色彩模式呢?小节标题已经剧透了:你在不知不觉中已经搞定暗黑模式了。
不信将模拟器切换到夜间看看:
Swift尽可能的帮助你脱离命令式的代码,让你将注意力集中在业务真正需要的地方。
总结你能看到文章的这个位置,相信也不需要我做什么总结了。Swift本身是一门功能强大、类型安全、吸收了各种现代语言优点的非常年轻的语言。用Swift写App能感受到它简洁又强大的能力。
下一步,你可以从这些资料开始Swift之旅:
Swift编程语言[1]-可能是最用心的基础语法翻译。斯坦福大学SwiftUI开发CSp-每年都是那个熟悉的爷爷PaulHegarty讲课,尽量看新的,因为Swift进化速度非常之快。HackingWithSwift[2]-Swift的超级宝藏网站,从小知识到教程,你能想到的它都有。走你,让我们在AppStore相见!
如果本文对你有帮助,欢迎各种渠道和我交流,我决定要不要继续写安利向的续作,哈哈。
作者杜赛,喜欢写辣鸡App,博客有很多Python搭建Web程序[3]的教程,欢迎来围观。
前往留言板参考资料[1]Swift编程语言: