iOS开发入门第4节:纯代码写UI

上一节中,我们用简简单单的几行代码实现了一个iOS动画,本节将对iOS动画代码做详细解释,并开始用纯代码方式来进行UI开发。

什么是UI开发:UI即User Interface(用户界面)的简称,泛指用户的操作界面。通俗点说,就是一个APP上用户能看到的、能触摸、拖拉的东西,比如背景、文字、按钮、图片等都是构成UI的元素,选用什么颜色、什么形状、摆放到什么位置都是UI设计的工作。UI设计可以让软件变得美观有个性有品味,还可让软件的操作变得舒适、简单、自由,充分体现软件的定位和特点。因此UI设计是很重要的,但是UI设计主要是设计师和美工的工作。程序员的主要工作是在App上实现这些UI设计,我们称为UI开发。UI设计与开发直接影响用户的体验,在移动App开发中占有很重的比例,每一个iOS程序员都必须熟练掌握。本节分享一种UI开发方法:用纯代码来写UI。

一、创建工程

打开Xcode创建一个iOS的"Single View App"工程,工程名为:MyCode。不懂创建的人请参看

二、工程目录简介

上图界面中左侧有四个大文件夹,下面大致了解一下这些文件夹的作用:

1. Products: 主要用于mac电脑软件开发,iOS开发用不到。

2. MyCodeTests: 用于单元测试。

3. MyCodeUITests: 用于UI测试。

4. MyCode: iOS开发的内容主要都是存放在这个文件夹中。

MyCode这个文件夹又包含:

4.1 AppDelegate.swift:代表应用程序,App初始化需要的内容都在这里做,App是从这里开始启动的,这个文件暂时不做深入。

4.2 ViewController.swift: 这是iOS存放视图控制器类的文件(如果不懂得什么是“类”,应该先看看“面向对象”编程思想相关的文章),里面定义一个页面 (ViewController),我们编写的UI代码都要写在这个页面里,比如文字、按钮、图片等元素都要摆放在这个页面里,才能展示给用户看。本节将重点关注这个文件以及里面定义的类。

4.3 Main.storyboard: 这个文件里面定义一个主storyboard(即故事板),storyboard里面可以放置多个页面(ViewController),页面之间的跳转关系也可以在storyboard里面定义。故事板可以帮助我们用比较直观的方式来快速的开发UI,通过storyboard我们可以看到我们设计的页面长什么样子。比如,我们要在页面上添加一张图片,我们只要将一个图片控件直接拉到storyboard上,就可以看到这个图片在页面上到底是大是小,位置在哪里等等。这是iOS推荐的UI开发模式。有人要问了,那我们还要用代码写UI,不是很麻烦吗?其实这两种方式写UI各有优缺点,我们可以取长补短,这在后面讲到storyboard的时候再讨论。Main.storyboard顾名思义就是主页面所在的storyboard,也就是点开App后,默认的用户看到的第一个页面。我们可以在一个项目中定义多个storyboard,一个storyboard里面又可以定义多个页面(ViewController)。所以storyboard可以认为是一群相关页面的集合。

5. Assets.xcassets: 这个文件夹主要用于存放资源文件,比如图片

6. LauchScreen.storyboard: 顾名思义就是启动页面所在的storyboard文件。在打开一个App的时候,一般不会直接跳到主页面,经常会先来个倒计时,先显示某某公司Logo或者广告图片视频什么的,承担这个功能的页面就叫启动页。

7. info.plist: 这个文件是项目的配置文件,比如主页面是哪个页面,Main.storyborad只是创建项目时系统默认的主页面所在的storyborad,在info.plist里可以随时更换到别的storyborad。

三、认识视图控制器

下面我们重点关注ViewController.swift这个文件,单击这个文件,得到如下界面(红框和箭头是我做的标记),下面逐一解释代码的作用

import UIKit:UIKit是iOS的SDK提供给我们专门用于编写UI的库或者包,import是导入这个库的意思,只有import之后,在这个文件中才能使用这个库提供的相关的类来编写UI。以后还会用到更多SDK中的库,也要这么导入。上面说的这些库都是iOS的SDK内部提供的库,使用时可以直接这样导入。站在巨人的肩膀上,明显可以省很多事,因此有时我们要经常在项目中用别人造过的轮子或者说工具,这些叫第三方库。使用第三方库时略微麻烦一点,怎么操作请关注后续我的文章。

UIViewController:这是UIKit库中一个重要的类,顾名思义“视图控制器”。项目创建时,系统自动为我们创建了一个ViewController类来继承UIViewController类。一个ViewController类的实例化对象对应App中的一个页面。所以很明显,我们的UI代码应该写在ViewController类里面。我们暂时不用关注这个类是怎么被实例化的,只要知道当我们编译运行时,系统会自动为我们实例化这个类。

viewDidLoad(): 这是UIViewController中的一个“方法”(也叫“函数”),代表页面容器已经初始化完毕,这时页面什么都没有还是空白的,我们可以在这时往页面中添加我们设计的UI元素,比如图片、文字。每个页面都有一个完整的生命周期:从它开始被创建一直到它被销毁回收。在这整个生命周期的不同阶段,UIViewController都会提供对应的函数(viewDidLoad()就是其中一个),让我们有机会进行修改界面、回收资源等操作。我们要添加的UI代码都是写在图中红色箭头所指的地方。其他生命周期函数还有很多,有兴趣可以自己查找学习,在这里不做介绍。

四、UIView

UIView是UIKit库中视图的基类,可以理解为页面中的一个色块,如下图大红框框中的部分就是一个页面(ViewController),而其中的红色的块就是一个UIView。

1. 属性和布局

我们对视图最关心的有两点:

a) 它长什么样:这称为视图的属性,比如是什么颜色、是否有边框、边框的颜色、边框的大小等

b) 它应该放在页面的哪个位置:这就是布局,布局有两种方法。一种使用frame,另一种是用AutoLayout。

(1)属性

对于以后要用到的其他更高级的视图控件,比如UILabel(专门显示文字)、UIImageView(专门显示图片)、UIButton(按钮)等都是类似的。只是他们有更多的属性而已。我们学习这些视图,无非就是熟悉他们的属性和布局,因此可以举一反三。

下面是一段最简单的例子:

let purpleView = UIView()

purpleView.backgroundColor=UIColor.purple

purpleView.frame=CGRect(x: 0, y: 100, width: 150, height: 150)

view.addSubview(purpleView)

可以将上面这段黑色加粗的代码拷贝到如下图所示的位置:

编译运行效果如下图:

下面对代码逐行做说明:

let purpleView = UIView() //这句是创建一个视图,注意要定义在函数外面

purpleView.backgroundColor = UIColor.purple //这句是将视图的背景色设置为紫色

purpleView.frame = CGRect(x: 0, y: 100, width: 150, height: 150) /*这句是设置视图的大小和位置:x:0表示视图与页面的左边距离为0,y:100表示视图与页面的上边距离为100,width:150代表视图宽度为150,height:150代表视图的高度为150。*/

view.addSubview(purpleView) //这句是表示将purpleView这个视图对象添加到页面里

注意:iOS用“//”来注释单行代码,可以用这个方法将临时不用的代码注释起来,也可以用来对代码进行说明。被“//”注释后的代码在程序运行时,将不会执行。

(2)布局

还可以用自动布局AutoLayout的约束方式来限制视图的位置:

//创建一个视图,与上面一样要放置的函数外面

let redView = UIView()

//禁止将AutoresizingMask转化为Constraints

redView.translatesAutoresizingMaskIntoConstraints = false

// 背景色为红色

redView.backgroundColor = UIColor.red

// 将视图添加到页面中

view.addSubview(redView)

//创建约束,注意:要在视图添加到其父容器后,才能进行约束设置,否则App会奔溃

//宽度约束

let widthConstraint = NSLayoutConstraint(item: redView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0, constant: 150)

//高度约束

let heightConstraint = NSLayoutConstraint(item: redView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0, constant: 150)

//顶部约束

let topConstraint = NSLayoutConstraint(item: redView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 100)

//左侧约束

let leftConstraint = NSLayoutConstraint(item: redView, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1.0, constant: 150)

//在页面中添加多个约束

view.addConstraints([widthConstraint,heightConstraint,leftConstraint,topConstraint])

上面这段黑色加粗的代码可以直接拷贝到项目中:

编译运行结果如下:

用自动布局AutoLayout的约束来设置视图的位置是比较灵活的,但是iOS系统提供的这种写法,明显太繁琐。所以一般使用第三方提供的SnapKit库来简化代码的写法,有兴趣的人可以查阅相关资料。

2. 动画

上面谈到的属性和布局都是设置静止不动的内容,有时我们想让这些图片或文字能够动起来,这样看起来比较生动有趣,这就要用到动画。

在 那节,我们分享了一个简单的iOS动画,没有对代码做任何解释,下面将对动画代码做详细说明。主要代码如下:

animView.frame=CGRect(x:0,y:0,width:100,height:100) //设置动画视图的尺寸

animView.center=view.center //动画视图放在父视图的正中央

animView.backgroundColor=UIColor.green//动画视图的背景色设置为绿色

view.addSubview(animView)//将动画视图添加到父视图(即页面)

UIView.animate(withDuration: 2,delay:1,usingSpringWithDamping:0.2,initialSpringVelocity:0,options:[.repeat,.autoreverse], animations:{

self.animView.transform=CGAffineTransform(scaleX: 0.5, y: 0.5)//将动画视图大小缩小为原来的一半

},completion:nil)

前面四行,是设置属性和布局可以参看上面的,不做说明。我们重点关注最后一个方法:

UIView.animate()这个方法是UIView类提供的一个静态方法,专门用于播放和控制视图动画。里面的参数有:

withDuration: 2表示动画总的时长为2秒

delay:1表示动画延时1秒才开始播放,就是这段动画代码被执行后不马上播放,而要等1秒钟后才开始播放。

usingSpringWithDamping:0.2弹簧动画的阻尼值,也就是相当于摩擦力的大小,该属性的值从0.0到1.0之间,越靠近0,阻尼越小,弹动的幅度越大,反之阻尼越大,弹动的幅度越小,如果大道一定程度,会出现弹不动的情况。

initialSpringVelocity:0弹簧动画的速率,或者说是动力。值越小弹簧的动力越小,弹簧拉伸的幅度越小,反之动力越大,弹簧拉伸的幅度越大。这里需要注意的是,如果设置为0,表示忽略该属性,由动画持续时间和阻尼计算动画的效果。

options后面可以设置很多可选项,.repeat这个选项表示动画是重复的,.autoreverse这个选项表示动画播放完毕后会自动倒播,注意这些选项前面要加一个点。

animations:{}这个大括号里面我们要指定视图最终属性值。比如我们要让一个原来透明度为1的视图慢慢变为透明度为0.5,这个过程我们不需要关心,我们只要在这个大括号中将视图最终0.5这个值设置好就行了。系统会根据视图最初的透明度以及我们设置的这个0.5自动生成中间值并用这些值来控制完成动画过程。也就是说,我们代码只要告诉系统,视图最终的属性值是多少系统就会为我们自动生成这些动画过程。

scanX:0.5,y:0.5表示这是一个缩放动画,并且视图在X方向缩小为原来的一半,Y方向也缩小为原来的一半,总体看就是方块整体慢慢缩小一半。

2.1 动画类型

按照动作,动画可分为以下几个类型:

(1)平移:

self.animView.transform=CGAffineTransform(translationX: -200, y: 20)

translationX: -200, y: 0表示视图向左移动200距离,同时向下移动20距离。x正值表示向右移动,负值表示向左移动;y正值表示向下移动,负值表示向上移动。

(2)缩放:

self.animView.transform=CGAffineTransform(scaleX: 0.5, y: 0.5)

scanX:0.5,y:0.5表示视图在x方向缩小为原来的一半,y方向也缩小为原来的一半,总体看就是方块整体慢慢缩小一半。x和y的值一般要大于等于0

(3)旋转:

self.animView.transform=CGAffineTransform(rotationAngle:CGFloat.pi/4)

rotaionAngle:CGFloat.pi/4表示顺时针旋转45度,CGFloat.pi/4代表旋转的角度。

2.1 组合动画

有的人说了:我想同时变可以吗,比如平移的同时缩放?当然可以啦。可以这样写:

let scale=CGAffineTransform(scaleX:0.6,y:0.6)//缩小到原来的0.6倍

let rotation=CGAffineTransform(rotationAngle:CGFloat.pi/4)//顺时针旋转45度

self.animView.transform=rotation.concatenating(scale)

用concatenating()这个方法,你想套几个动画都可以,比如还有一个平移动画可以这样写:

let transY=CGAffineTransform(translationX: 0, y: 150)//向下移动150的距离

let scale=CGAffineTransform(scaleX:0.6,y:0.6)//缩小到原来的0.6倍

let rotation=CGAffineTransform(rotationAngle:CGFloat.pi/4)//顺时针旋转45度

self.animView.transform=transY.concatenating(rotation.concatenating(scale))

这些动画就不在这里演示了,因为这里没办法加视频。用相关代码替换掉对应的行就可以实现。

本系列会不断更新,有兴趣的同学,请关注我。