第7节:详解Animation-1基础

[TOC]

详解Animation-1基础

参考文章:

一、动画入门

请点击进入阅读:【Flutter】Animation 动画 ( Flutter 动画基本流程 | 创建动画控制器 | 创建动画 | 设置值监听器 | 设置状态监听器 | 布局中使用动画值 | 动画运行 )

二、Animation、AnimationController、Tween

以下内容摘自:Flutter动画 2 - Animation、Tween 详解

Tween的唯一职责就是定义从输入范围到输出范围的映射。输入范围通常为0.0到1.0

1
final Tween doubleTween = new Tween<double>(begin: -200.0, end: 0.0);

AnimationController 首先是继承于 Animation 的, 相对Animation而言,AnimationController作用则是控制动画.它可以设置动画的时长,并且包含动画的执行方法,具体如下表格所示.

AnimationController 的常用操作说明:

动画执行方法 说明
forward() 正向开始执行动画
reverse() 反向开始执行动画
reset() 重置动画到初始状态
dispose() 取消/停止动画

flutter AnimationStatus 动画状态说明:

AnimationStatus 动画状态值 说明
AnimationStatus.forward 执行 controller.forward() 会回调此状态
AnimationStatus.reverse 执行 controller.reverse() 会回调此状态
AnimationStatus.dismissed 动画从 controller.reverse() 反向执行 结束时会回调此方法
AnimationStatus.completed 动画从 controller.forward() 正向执行 结束时会回调此方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
_animationController = AnimationController(duration: Duration(milliseconds: 300), 

// 位移动画
_positionAnimation = Tween<double>(begin: 0, end: 50).animate(_animationController)
..addListener(() {
setState(() {});
});

// 颜色动画
_colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate(_animationController)
..addListener(() {
setState(() {});
});

_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
print("动画完成");
}
});

三、旋转动画 RotationTransition

End

第7节:详解Animation-2动画组

[TOC]

详解Animation-2动画组

参考文章:

一、Animation动画组-串行

参考文档:

串行动画的实现方案总共有三种,分别是 监听状态法, Interval时间间隔法, TweenSequence动画序列法.

1、监听状态法

状态监听法主要通过AnimationController监听动画的completed状态,然后再去执行下一个动画,如此往复,直到所有动画完成.

例如现在我们需要实现先执行组件在0.3秒钟往下偏移50个单位,然后再执行在0.6s中组件的颜色由 橘色 变为 红色.

整体Demo代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class FlutterAnimationWidget extends StatefulWidget {
TurnTableLotteryPage({Key key}) : super(key: key);

@override
_FlutterAnimationWidgetState createState() => _FlutterAnimationWidgetState();
}

class _FlutterAnimationWidgetState extends State<FlutterAnimationWidget> with TickerProviderStateMixin {
// ①声明位移动画控制器和颜色动画控制器以及位移动画和颜色动画
AnimationController _animationController;
Animation<double> _animation;
AnimationController _colorAnimationController;
Animation<Color> _colorAnimation;

@override
void initState() {
super.initState();

// ②分别创建位移、颜色的动画控制器和动画
_animationController = AnimationController(duration: Duration(milliseconds: 300), vsync: this);
// Tween对象调用`animate()`函数直接传入上面的AnimationController
_animation = Tween<double>(begin: 0, end: 50).animate(_animationController)
..addListener(() {
setState(() {});
});
_colorAnimationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);
_colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate(_colorAnimationController)
..addListener(() {
setState(() {});
});

// ③最后,我们只需要监听位移动画完成状态之后执行颜色动画即可,
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_colorAnimationController.forward();
};
});
}

void startEasyAnimation() {
_animationController.forward();
}

@override
void dispose() {
_animationController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 200,
height: 50,
color: _colorAnimation.value,
margin: EdgeInsets.only(top: _animation.value),
),
FlatButton(
onPressed: startEasyAnimation,
child: Text(
"点击执行最简单动画",
style: TextStyle(color: Colors.black38),
),
),
],
),
),
);
}
}

2、Interval时间间隔法

上面的状态监听需要一个动画过程就写一个Controller,而且基本上还要每一个Controller都监听执行完成然后再去启动下一个Controller.如果一个动画过程有十几个,自己想想都是脑瓜子嗡嗡的.所以接下来我们就来介绍第二种方案 - Interval时间间隔法 .

Interval时间间隔法 的整体思路是一个动画Controller控制所有动画的执行.然后每一个动画只需要确认自己在整个动画的时间比重即可.

整体Demo代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class FlutterAnimationWidget extends StatefulWidget {
TurnTableLotteryPage({Key key}) : super(key: key);

@override
_FlutterAnimationWidgetState createState() => _FlutterAnimationWidgetState();
}

class _FlutterAnimationWidgetState extends State<FlutterAnimationWidget> with TickerProviderStateMixin {
// ①声明一个动画Controller和多个动画.
AnimationController _animationController;
Animation<double> _animation;
Animation<Color> _colorAnimation;

@override
void initState() {
super.initState();

// 然初始化AnimationController,AnimationController的动画时间(**duration**)要设置成所有动画的总时长,例如这里我设定为600毫秒(_animation时长:300毫秒,_colorAnimation时长:300毫秒).
_animationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);
// Tween对象调用`animate()`函数不再是直接传入上面的AnimationController,而是传入一个 CurvedAnimation 对象。
// 其中CurvedAnimation构建过程中需要传入两个参数一个是 parent ,用于指定AnimationController. 另外一个是 curve,用于指定动画曲线函数.我们可以使用常用的动画曲线函数,也可以自己生成,这里我们就自己生成.指定动画执行的时间区间.
_animation = Tween<double>(begin: 0, end: 50).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval(0.0, 0.5), // 由于两个动画时间长度是对分的,每一个都是300毫秒,所以 curve 参数中的值就分别是 **Interval(0.0, 0.5)**、**Interval(0.5, 1.0)**
),
)..addListener(() {
setState(() {});
});
_colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval(0.5, 1.0),// 由于两个动画时间长度是对分的,每一个都是300毫秒,所以 curve 参数中的值就分别是 **Interval(0.0, 0.5)**、**Interval(0.5, 1.0)**
),
)..addListener(() {
setState(() {});
});
}

void startEasyAnimation() {
_animationController.forward();
}

@override
void dispose() {
_animationController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 200,
height: 50,
color: _colorAnimation.value,
margin: EdgeInsets.only(top: _animation.value),
),
FlatButton(
onPressed: startEasyAnimation,
child: Text(
"点击执行最简单动画",
style: TextStyle(color: Colors.black38),
),
),
],
),
),
);
}
}

3、TweenSequence动画序列法

上面的两种方案虽然能解决动画组的问题,但是都太过于繁琐,那么有没有一种比较优雅的方案呢?这就需要使用到 TweenSequenceTweenSequenceItem 这两个类了. 其中 TweenSequence 是动画组类,TweenSequenceItem 则是用来定义每一个动画的具体实现的类.但是TweenSequenceTweenSequenceItem也不是尽善尽美的,它最大的问题就是前后变化的属性值类型必须是一致的.

下面,我们仍然以改变Margin为例, 先让视图组件往上移动50,再让视图 来看看如何使用 TweenSequenceTweenSequenceItem 来实现这个动画.

整体代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
class FlutterAnimationWidget extends StatefulWidget {
TurnTableLotteryPage({Key key}) : super(key: key);

@override
_FlutterAnimationWidgetState createState() => _FlutterAnimationWidgetState();
}

class _FlutterAnimationWidgetState extends State<FlutterAnimationWidget> with TickerProviderStateMixin {
// ①声明一个 动画控制器AnimationController 和 动画Animation.
AnimationController _animationController;
Animation<double> _animation;

@override
void initState() {
super.initState();
// 仍然以两者的动画总时长为600毫秒为例
_animationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);

// 然后,我们通过 **TweenSequence** 和 **TweenSequenceItem** 这两个类对 动画Animation 进行实现.
// 实现两个 **TweenSequenceItem**, **TweenSequenceItem**中的 <font color=red>weight</font> 属性是来设定动画执行的时间权重.也就是在整个动画过程,当前动画执行时长占总时长的比例.例如下面 第一个动画插值占的时间比例为 `50/(50 + 100)`. 第二个动画插值占的时间比例为 `100/(50 + 100)` .
TweenSequenceItem downMarginItem = TweenSequenceItem<double>(
tween: Tween(begin: 1.0, end: 50.0),
weight: 50,
);
TweenSequenceItem upMarginItem = TweenSequenceItem<double>(
tween: Tween(begin: 50.0, end: 100.0),
weight: 100,
);
// 然后创建一个动画插值组,把上面两个动画插值放入组中.
TweenSequence tweenSequence = TweenSequence<double>([
downMarginItem,
upMarginItem,
]);
// 最后,生成动画就OK了.
_animation = tweenSequence.animate(_animationController);
_animation.addListener(() {
setState(() {});
});
}

void startEasyAnimation() {
_animationController.forward();
}

@override
void dispose() {
_animationController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 200,
height: 50,
color: Colors.orangeAccent,
margin: EdgeInsets.only(top: _animation.value),
),
FlatButton(
onPressed: startEasyAnimation,
child: Text(
"点击执行最简单动画",
style: TextStyle(color: Colors.black38),
),
),
],
),
),
);
}
}
动画组实现总结

上面三种实现动画组基本上已经说完了,接下来我们就来对比其不同点.

特性 监听状态法 Interval时间间隔法 TweenSequence动画序列法
代码简洁度 🔅🔅 🔅🔅🔅 🔅🔅🔅🔅🔅
动画是否可交织
动画属性是否可以多变

动画是否可交织 : 动画可否交织主要是说两个动画之间是否需要上一个动画完全执行完成之后,下一个动画才能执行.

动画属性是否可以多变 : 动画属性多变是指当前动画过程中可变化的属性是否可以有多个,例如同时变化尺寸和颜色等等.

第2节:详解TextField

[TOC]

参考文章:

一、TextField class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
const TextField({
Key key,
this.controller,// 文本控制器,详见下文
this.focusNode, // 焦点,详见下文
this.decoration = const InputDecoration(), // 输入框装饰器,详见下文
TextInputType keyboardType,
this.textInputAction, //更改键盘本身的操作按钮,详见下文
this.textCapitalization = TextCapitalization.none, //提供了一些有关如何使用户输入中的字母大写的选项
this.style,
this.textAlign = TextAlign.start,
this.textDirection,
this.autofocus = false, // 是否自动获取焦点
this.obscureText = false, // 是否隐藏正在编辑的文本,如用于输入密码的场景等,文本内容会用“•”替换。
this.autocorrect = true,
this.maxLines = 1, // 能写入的最大行数
this.maxLength, // 能写入的最大字符数
this.maxLengthEnforced = true,
this.onChanged, // 文本框事件:文字改变触发
this.onEditingComplete, // 文本框事件:当用户提交可编辑内容时调用(常用于“焦点(FocusNode)”变更)
this.onSubmitted, // 文本框事件:文字提交触发(键盘按键)
this.inputFormatters,
this.enabled,
this.cursorWidth = 2.0, // 光标宽度 Colors.red
this.cursorRadius, // 光标半径 Radius.circular(16.0)
this.cursorColor, // 光标颜色 16.0
this.keyboardAppearance,
this.scrollPadding = const EdgeInsets.all(20.0),
this.enableInteractiveSelection = true,
this.onTap, // 文本框事件:
}) : assert(textAlign != null),
assert(autofocus != null),
assert(obscureText != null),
assert(autocorrect != null),
assert(maxLengthEnforced != null),
assert(scrollPadding != null),
assert(maxLines == null || maxLines > 0),
assert(maxLength == null || maxLength > 0),
keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
assert(enableInteractiveSelection != null),
super(key: key);

二、文本框的输入器装饰 InputDecoration decoration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const InputDecoration({
this.icon,
this.labelText,
this.labelStyle,
this.helperText,
this.helperStyle,
this.hintText,
this.hintStyle,
this.errorText,
this.errorStyle,
this.errorMaxLines,
this.hasFloatingPlaceholder = true,
this.isDense,
this.contentPadding,
this.prefixIcon,
this.prefix,
this.prefixText,
this.prefixStyle,
this.suffixIcon,
this.suffix,
this.suffixText,
this.suffixStyle,
this.counterText,
this.counterStyle,
this.filled,
this.fillColor,
this.errorBorder,
this.focusedBorder,
this.focusedErrorBorder,
this.disabledBorder,
this.enabledBorder,
this.border,
this.enabled = true,
this.semanticCounterText,
}) : assert(enabled != null),
assert(!(prefix != null && prefixText != null), 'Declaring both prefix and prefixText is not allowed'),
assert(!(suffix != null && suffixText != null), 'Declaring both suffix and suffixText is not allowed'),
isCollapsed = false;

图解如下:

Flutter TextField

输入器装饰 InputDecoration decoration的其他参数

参数 作用 备注
contentPadding 内容的边距,默认是有一个边距的 contentPadding: new EdgeInsets.all(0.0)

2、文本控制器 TextEditingController controller

controller.clear() 清空了用户名输入框中的内容

3、键盘输入类型 TextInputType keyboardType

键盘输入类型(数字,文本等各种类型):设置TextField获得焦点的时候弹出的键盘

类型 作用
TextInputType.number 数字键盘
TextInputType.text 普通完整键盘
TextInputType.emailAddress 带有“@”的普通键盘
TextInputType.datetime 带有“/”和“:”的数字键盘
TextInputType.multiline 带有选项以启用有符号和十进制模式的数字键盘

4、键盘本身的操作按钮 TextInputAction

1
2
3
TextField(
textInputAction: TextInputAction.search,
),

5、TextCapitalization

类型 作用
TextCapitalization.characters 大写句子中的所有字符
TextCapitalization.words 将每个单词的首字母大写

6、文本框的其他参数

见类结构

二、键盘

Flutter | 滚动 PageView 自动关闭键盘

1
2
3
4
5
PageView(
onPageChanged: (index) {
WidgetsBinding.instance?.focusManager.primaryFocus?.unfocus();
}
)

第1节:详解Button

参考文章:

一、基础知识

1、继承关系

Object > Diagnosticable > DiagnosticableTree > Widget > StatelessWidget > MaterialButton > RaisedButton、FlatButton、OutlineButton

  • 2021-12-18

TextButton 是 1.20.0 推出的一个新的按钮

Flutter TextButton 详细使用配置、Flutter ButtonStyle概述实践

flutter 2.0版本新增了三个按钮

TextButton、OutlinedButton、ElevatedButton

2、类

RaisedButton Material Design中的button, 一个凸起的材质矩形按钮。
FlatButton Material Design中的button,一个没有阴影的材质设计按钮。
OutlineButton Material Design中的button,RaisedButton和FlatButton之间的交叉:一个带边框的背景透明的按钮,当按下按钮时,其高度增加,背景变得不透明。
IconButton 用于创建仅包含图标的按钮,参数就不再讲解
DropdownButton 一个显示可供选择的选项的按钮
FloatingActionButton 材质应用程序中的圆形按钮
InkWell 实现平面按钮的墨水飞溅部分

RaisedButton、FlatButton、OutlineButton三个控件都继承于MaterialButton,查看源码会发现MaterialButton由RawMaterialButton(无主题Button)构建的。而RawMaterialButton与CupertinoButton是一对button组合,都继承于StatefulWidget,前者是google风格,后者iOS风格!

附:使用CupertionBUtton要注意导入库:import ‘package:flutter/cupertino.dart’;

二、 MaterialButton参数详解

这里我们仅对个别不好理解的做解释。

属性 作用 备注
VoidCallback onPressed 点击激活按钮时调用的方法
ValueChanged onHighlightChanged 按下和抬起时都会调用的方法,详看后面示例
ButtonTextTheme textTheme 定义按钮的基色,以及按钮的最小尺寸,内部填充和形状的默认值。
Color disabledTextColor 未设置按钮点击回调时使用的文本颜色
Color splashColor 按钮被按下的水波纹颜色,默认是有值的,不要要水波纹效果设置透明颜色即可!
colorBrightness 按钮的主题亮度,当设置了textColor、color颜色,此值无效!

更多内容参考:Flutter RaisedButton、FlatButton、OutlineButton 参数详解

[TOC]

入门

  • 一、修饰符
  • 二、通知、代理、block

一、修饰符

Swift基础部门:http://www.swift51.com/swift4.0/chapter2/01_The_Basics.html

修饰符:

修饰符 所修饰的属性或者方法的作用范围
private 只能在当前类里访问
fileprivate 在当前的Swift源文件里可以访问
internal
(默认访问级别)
此修饰符可写可不写
在源代码所在的整个模块都可以访问。
①如果是框架或者库代码,则在整个框架内部都可以访问,框架由外部代码所引用时,则不可以访问。
②如果是App代码,也是在整个App代码,也是在整个App内部可以访问。
public 可以被任何人访问。
①其他module中不可以被override和继承,
②而在module内可以被override和继承
open 可以被任何人使用,包括override和继承

访问顺序:
现在的访问权限则依次为:open > public > internal > fileprivate > private。

swift – 静态变量static

1
2
3
4
5
6
7
8
9
10
11
12
class ViewController: UIViewController {
//静态变量 swift中的static静态变量,只能在这里声明,不能在方法中声明,会报错
static var i : Int = 1

override func viewDidLoad() {
super.viewDidLoad()

//调用静态变量
//print(self.i) 错
print(ViewController.i) //这样调用
}
}

懒加载 & Readonly

1、lazy关键字修饰一个变量就可以实现懒加载
2、懒加载的本质是,在第一次使用的时候使用调用,只会赋值一次

示例:

1
2
3
4
5
//lazy var btn : UIButton = UIButton();
lazy var btn : UIButton = {
let btn = UIButton()
return btn
}()

ReadOnly

Swift如何优雅的的设置只读(readOnly)属性

优雅地使用 Selector - Swift

截取字符串

参考文章:

修改函数参数的值/让方法参数可修改方法

在swift中,我们常常对数据进行一些处理。因为swift的计算属性,所以如果不是大量重复性处理,基本可以在set及didSet中改变原数据的一些状态。但需要用到同样的算法处理大量数据的时候,仍然需要写方法来解决。在如C一类的传统语言中,指针变量能轻易帮我们处理并直接修改掉原数据,而apple本身期望siwft中尽量减少指针的出现,因此,swift常规方法中经常用到的是值传递。值传递最明显的后果便是无法对原数据进行直接修改。如果我们需要处理后的数据结果,那么就需要重新定义一个变量来接收值。在原数据被废弃的情况下,这样既增多了代码量,也产生了空间大量浪费。因此 siwft提供了关键字修饰inout来申明数据地址传递,也称之为引用传递。在siwft3.0中 inout的位置发生了改变,处于标签位置,不过其作用相同。具体作用如下图代码:

其他参考:swift之inout

举例:

1
2
3
4
5
6
func show(with fixHeight: inout CGFloat, blankBGColor: UIColor) {
let maxHeight: CGFloat = UIScreen.main.bounds.height-60
if fixHeight > maxHeight {
fixHeight = maxHeight
}
}
1
2
3
4
5
6
func addTitle(text: String? = "") {
// 直接在方法中参数里赋默认值就好
//if text == nil {
// text = ""
//}
}

二、通知、代理、block

什么时候用通知,什么时候用代理,什么时候用block
通知 : 两者关系层次太深,八竿子打不着的那种最适合用通知.因为层级结构深了,用代理要一层一层往下传递,代码结构就复杂了

代理 : 父子关系,监听的方法较多的时候使用

block : 父子关系,监听的方法较少的时候使用

1、代理

  • 定义swift中代理的协议
1
2
3
4
5
6
7
// swift 中的代理必须继承自NSObjectProtocol
protocol VisitorViewDelegate : NSObjectProtocol
{
// 代理中的方法默认必须实现,有可以不实现的情况,以后整理
func visitorViewDidClickRegisterBtn(visitorView : VisitorView)
func visitorViewDidClickLoginBtn(visitorView : VisitorView)
}
  • 声明代理
1
2
// 代理属性,与OC一样,用weak修饰
weak var delegate : VisitorViewDelegate? // 可选类型,代理可以有也可以没有
  • 在按钮点击的事件中执行代理方法
1
2
3
4
5
6
7
8
9
10
11
12
// 注册按钮的点击
@IBAction func registerBtnClick(sender: UIButton) {
// 监听到点击,通知代理做事情
// 代理中的方法默认是必须实现的(也有可选的,后面再说),所以这里没有判断代理有没有实现相应的方法
delegate?.visitorViewDidClickRegisterBtn(self)
}

// 登录按钮的点击
@IBAction func loginBtnClick(sender: UIButton) {
// 监听到点击,通知代理做事情
delegate?.visitorViewDidClickLoginBtn(self)
}
  • 代理的具体实现

    swift中为了让方法分类更清晰,实现代理或者数据源的方法单独写到分类中

1
2
3
4
5
6
7
8
9
10
11
12
13
// MARK: - VisitorViewDelegate代理方法
// swift 中为了区分不同类型的代理方法或者数据源方法,通过extension实现了更好的区分
extension BaseTableViewController: VisitorViewDelegate
{
func visitorViewDidClickRegisterBtn(visitorView : VisitorView) {
ChaosLog("")
}

func visitorViewDidClickLoginBtn(visitorView : VisitorView) {

ChaosLog("")
}
}

2、block

作为类属性:

1
var clickHandle: ((_ sheetModel: CJDemoActionSheetModel, _ selectIndex: NSInteger)->())?

作为函数变量:

1
2
3
init(sheetModels:[CJDemoActionSheetModel], clickHandle: @escaping (_ sheetModel: CJDemoActionSheetModel, _ selectIndex: NSInteger)->()) {

}

其他参考文章:

其他

获取类名

1
2
CJDemoActionSheetTableViewCell.self
UIViewController.self

如何在int和float之间的算术在Swift?

1
2
3
4
var myInt = 5;
var myDouble = 3.4;
var doubleResult = Double(myInt) + myDouble;
var intResult = myInt + Int(myDouble)

内存管理

CFRelease

苹果的一些底层框架返回的对象有的是自动管理内存的(annotated APIs),有的是不自动管理内存。

如果一个函数名中包含CreateCopy,则调用者获得这个对象的同时也获得对象所有权,返回值Unmanaged需要调用takeRetainedValue()方法获得对象。调用者不再使用对象时候,Swift代码中不需要调用CFRelease函数放弃对象所有权,这是因为Swift仅支持ARC内存管理,这一点和OC略有不同。

上文摘自:https://www.jianshu.com/p/103591adc3d1 中 CFRoopLoop使用 的使用

Swift GCD延迟加载

详细参考:

swift 自定义view的初始方法

swift中的@objc的作用

Swift枚举的全用法

iOS 国际化全解-swift与OC

第1节:Swift基础知识

[TOC]

入门

  • 一、基础部分
  • 二、基本运算符

OC与Swift混编

原本在 swift 3 中除了手动添加 @objc 声明函数支持 OC 调用还有另外一种方式:继承 NSObject。

即class 继承了 NSObject 后,编译器就会默认给这个类中的所有函数都标记为 @objc ,支持 OC 调用。

然而在实际项目中,一个 swift 类虽然继承了 NSObject,但是其中还是有很多函数不会在 OC 中被调用,这里有很大的优化空间。

于是根据 SE160 的建议,苹果修改了自动添加 @objc 的逻辑:现在一个继承 NSObject 的 swift 类不再默认给所有函数添加 @objc。只在实现 OC 接口和重写 OC 方法时才自动给函数添加 @objc 标识。

XCode 9会在运行过程中自行检测类中函数是被 OC 调用,然后提示添加 @objc。下图中的 vc 是 swift 中的类,showStatus 也是 swift 函数,现在编译器会提示需要手动添加 @objc:

Swift概念新增/区别

swift的guard语句的时候,我当时不太理解以后会有什么理由能用到它。这个语句的介绍如下:

与if语句相同的是,guard也是基于一个表达式的布尔值去判断一段代码是否该被执行。与if语句不同的是,guard只有在条件不满足的时候才会执行这段代码。你可以把guard近似的看做是Assert,但是你可以优雅的退出而非崩溃。

一、基础部分

Swift基础部门:http://www.swift51.com/swift4.0/chapter2/01_The_Basics.html

1、声明常量和变量

声明变量时在类型后添加?或者!,就是告诉编译器这是一个Optional的变量,如果没有初始化,你就将其初始化为nil

常量和变量必须在使用前声明,用 let 来声明常量,用 var 来声明变量。

1
2
3
4
5
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0

// 可以在一行中声明多个常量或者多个变量,用逗号隔开:
var x = 0.0, y = 0.0, z = 0.0

与变量不同,常量的值一旦被确定就不能更改了。尝试这样做会导致编译时报错:

1
2
3
let languageName = "Swift"
languageName = "Swift++"
// 这会报编译时错误 - languageName 不可改变

附:类型标注

Swift可以根据赋值推断出类型。

1
2
let PI = 3.1415
var Str = "string"

你也可以使用类型标注(type annotation)来指定类型。在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。声明中的冒号代表着“是…类型”

1
2
3
4
var welcomeMessage: String = "string"

// 可以在一行中定义多个同样类型的变量,用逗号分割,并在最后一个变量名之后添加类型标注:
var red, green, blue: Double

2、输出print

Swift使用字符串插值(string interpolation)的方式将常量或变量当作占位符加入到长字符串中,我们可以借此拼接长字符。

1
2
3
4
let value1 = "123"
var value2 = 345
print("value1 = \(value1) , value2 = \(value2)")
// 输出: value1 = 123 , value2 = 345

3、数据类型(布尔值、数组、字典、元组、可选类型)

3.1、布尔值

和OC不同,Swift中的布尔值使用truefalse表示真、假

1
2
3
4
let boolValue = true
if boolValue {
print("value is true")
}

这里需要说明的是,Swift不能像OC中那样,数值为0即表示布尔值NO,非0即为YES,因此下面OC代码可以通过

1
2
3
4
5
float value = 0.001;
if (value) {
NSLog(@"value:%f",value);
}
// 输出: value:0.001000

而在Swift中会编译报错

1
2
3
4
let boolValue = 0.001
if boolValue {
print("value is true")
}

3.2、可选类型

3.2.1、可选类型

使用可选类型(optionals)来处理值可能缺失的情况。

1
2
3
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int"

因为该构造器可能会失败,所以它返回一个可选类型(optional)Int,而不是一个 Int。一个可选的 Int 被写作 Int? 而不是 Int。问号暗示包含的值是可选类型,也就是说可能包含 Int 值也可能不包含值。(不能包含其他任何值比如 Bool 值或者 String 值。只能是 Int 或者什么都没有。)

3.2.2、隐式解析可选类型

可选类型 String 和隐式解析可选类型 String 之间的区别:

1
2
3
4
5
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要感叹号来获取值

let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 不需要感叹号

你可以把隐式解析可选类型当做一个可以自动解析的可选类型。你要做的只是声明的时候把感叹号放到类型的结尾,而不是每次取值的可选名字的结尾。

nil

Swift 的 nil 和 Objective-C 中的 nil 并不一样。

Objective-C Swift
nil 在 Objective-C 中,nil 是一个指向不存在对象的指针。
只有对象类型能被设置为nil
在 Swift 中,nil 不是指针——它是一个确定的值,用来表示值缺失。
任何类型的可选状态都可以被设置为 nil,不只是对象类型。

你可以给可选变量赋值为nil来表示它没有值:

1
2
3
4
var serverResponseCode: Int? = 404
// serverResponseCode 包含一个可选的 Int 值 404
serverResponseCode = nil
// serverResponseCode 现在不包含值

强制解析

1
2
3
4
5
6
7
//let possibleNumber = "123"
//let convertedNumber = Int(possibleNumber)
var convertedNumber: Int? = 123
if convertedNumber != nil {
print("convertedNumber has an integer value of \(convertedNumber!).") //当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的强制解析(forced unwrapping):
}
// 输出 "convertedNumber has an integer value of 123."

可选绑定

if 语句中写一个可选绑定:

1
2
3
if let constantName = someOptional {
statements
}

举例:

1
2
3
4
5
6
if let actualNumber = Int(possibleNumber) {
print("\'\(possibleNumber)\' has an integer value of \(actualNumber)") //转换成功,因为它已经被可选类型 包含的 值初始化过,所以不需要再使用 ! 后缀来获取它的值。
} else {
print("\'\(possibleNumber)\' could not be converted to an integer")
}
// 输出 "'123' has an integer value of 123"

这段代码可以被理解为:“如果 Int(possibleNumber) 返回的可选 Int 包含一个值,创建一个叫做 actualNumber 的新常量并将可选包含的值赋给它。”

二、基本运算符

1、赋值运算符

与 C 语言和 Objective-C 不同,Swift 的赋值操作并不返回任何值。所以以下代码是错误的:

1
2
3
if x = y {
// 此句错误, 因为 x = y 并不返回任何值
}

这个特性使你无法把(==)错写成(=),由于 if x = y 是错误代码,Swift 能帮你避免此类错误发生。

如果赋值的右边是一个多元组,它的元素可以马上被分解成多个常量或变量:

1
2
let (x, y) = (1, 2)
// 现在 x 等于 1,y 等于 2

2、算术运算符

加法运算符也可用于 String 的拼接:

1
"hello, " + "world"  // 等于 "hello, world"

在新版Swift中,++ 和 – 运算符被取消,因此 i++ 这种形式的累加需要换成 i += 1 这种形式。

3、一元正号/负号运算符

1
2
3
4
let tempValue = -6
let plusSix = -tempValue // plusSix 等于 6
let minusSix = +tempValue // minusSix 等于 -6
//当你在使用一元负号来表达负数时,你可以使用一元正号来表达正数,如此你的代码会具有对称美。

4、比较运算符(Comparison Operators)

比较运算多用于条件语句,如if条件:

1
2
3
4
5
6
7
let name = "world"
if name == "world" {
print("hello, world")
} else {
print("I'm sorry \(name), but I don't recognize you")
}
// 输出 "hello, world", 因为 `name` 就是等于 "world"

5、空合运算符(Nil Coalescing Operator)

空合运算符a ?? b)将对可选类型 a 进行空判断,如果 a 包含一个值就进行解封,否则就返回一个默认值 b。表达式 a 必须是 Optional 类型。默认值 b 的类型必须要和 a 存储值的类型保持一致。

1
2
3
a ?? b	// 空合运算符
// 等价于
a != nil ? a! : b // 三目运算符

注意: 如果 a 为非空值(non-nil),那么值 b 将不会被计算。这也就是所谓的短路求值

6、区间运算符(Range Operators)

写法 示例
闭区间运算符 a…b for index in 1…5 {}
闭区间运算符的单侧形式 [a…]或[…b] for name in names[2…] { print(name) }
半开区间运算符 a..<b for i in 0..<count {}
半开区间运算符的单侧形式 [..<b]

7、逻辑运算符(Logical Operators)

注意: Swift 逻辑操作符 &&|| 是左结合的,这意味着拥有多元逻辑操作符的复合表达式优先计算最左边的子表达式。为使得代码更加可读,建议使用括号来明确优先级

1
2
3
4
5
6
7
8
9
10
11
if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// 等价于
if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}

8、溢出运算符

1
2
3
4
5
6
7
8
9
10
11
var maxInt = Int8.max	// Int8 型整数能容纳的最大值是 +127,以二进制表示即 01111111。
var minInt = Int8.min // Int8 型整数能容纳的最小值是 -128,以二进制表示即 10000000。
print("Int8.max:\(maxInt), Int8.min:\(minInt)")
// 输出 Int.max:127, Int.min:-128

// 运算的错误写法:
maxInt += 1 // 编译错误
minInt -= 1 // 编译错误

// 运算的正确写法:
minInt = minInt &- 1 // 当使用溢出减法运算符对其(二进制10000000)进行减 1 运算时,得到二进制数值 01111111,也就是十进制数值的 127(符号位被翻转了),这个值也是 Int8 型整数所能容纳的最大值。

三、函数

Swift的函数使用 func 声明一个函数,形式为:

1
2
3
func name(parameters) -> return type {
function body
}

常用的函数形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 无参数函数
func sayHello() -> String {
return "hello, world"
}
// 多参数函数,无返回值( -> Void 可以省略 )
func personInfo(name: String, age: Int) -> Void {
// function body
}
// 注意:严格上来说,虽然没有返回值被定义,greet(person:) 函数依然返回了值。没有定义返回类型的函数会返回一个特殊的Void值。它其实是一个空的元组(tuple),没有任何元素,可以写成()。

// 多重返回值,这里返回一个元组
func minMax(array: [Int]) -> (min: Int, max: Int) {
return (array.min()!, array.max()!)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 可选返回类型,‘_’ 表示忽略参数标签
func max(_ array: [Int]) -> Int? {
return array.max()
}
// 指定参数标签(参数说明,和参数名以空格分隔),区别于参数名,默认参数名就是外部的标签名
func someFunction(parameterDescription parameter: String) {
print(parameter)
}
someFunction(parameterDescription: "parameter")
// 可以看到参数标签只供外部调用显示使用,而参数名parameter可以供内部函数使用
// 默认参数
func sumFunc(a: Int, b: Int = 12) -> Int {
return a &+ b
// 如果你在调用时候不传第二个参数,parameterWithDefault 会值为 12 传入到函数体中。
}
print(sumFunc(a: 3)) // 输出 15
// 可变参数,使用(...)的方式接收可变参数,并且必须作为左后一个参数
func sumFunc2(_ numbers: Int ...) -> Int {
var total: Int = 0
// 和OC实现不定参数不同,Swift已将不定参转换为数组
for number in numbers {
total += number
}
return total
}
print("\(sumFunc2(1,2,3,4,5))") // 输出 15
/*
输入输出参数
如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就可以将这个参数定义为输入输出参数
这有点类似OC中常见的NSError的使用,他们的本质是接受一个地址,直接操作内存地址进行修改和访问
可是使用 inout 关键字来定义输入输出参数,需要注意的是,在新版本中,已经将 inout 放在了 '参数名:' 后面,而不是前面
*/
func swapTwoObj(_ a: inout String, _ b : inout String) {
(a, b) = (b, a)
}
var str1 = "123"
var str2 = "456"
swapTwoObj(&str1, &str2)
print("str1:\(str1),str2:\(str2)") // 输出 str1:456,str2:123