iOS 开发整理

文章不涉及语言部分,代码部分Objective-CSwift都有可能使用。

文章内容以APP开发为主,以及开发相关的设计模式、设计规范和相关原理。

UI开发#

UIView (视图)#

UIView在iOS中则是在屏幕上用来管理一块矩形区域的对象,也是iOS开发中最为基础的UI控件。

开发中UIView的作用常用的有两个:

  1. 布局
  2. 管理子View
  3. 管理区域内的事件
  4. 渲染图像和动画

布局主要指的是 View 的大小和位置,依靠的是UIView的 frame 属性。

1
2
UIView *view = [[UIView alloc] init];  // 创建一个UIView
view.frame = CGRectMake(10,20,30,40); // view坐标 (10,20),view的宽高(40,50)

管理子View代表UIView具有容器属性,可以通过 addSubview方法 在一个UIView中添加子UIView。
而对于拥有多个子View的复杂View,UIView通常采用的是栈管理来处理所有的同级的子View。当子View出现重叠现象的时候,优先显示的则是后入栈的View。

1
2
3
4
UIView *superView = [[UIView alloc] init];  
UIView *subView = [[UIView alloc] init];

[superView addSubview: subView];

管理区域内的事件是因为 UIView 继承自 UIResponder 。
UIResponder 是所有事件响应的基石,为整个事件查找过程提供了处理能力, UIView继承自UIResponder, 也就拥有相关的事件处理能力。因此 UIView 具有响应触摸事件的能力。

渲染图像和动画的特性主要在于 UIView 下面的 CALayer 。CALayer直接继承自NSObject,所以它不具备事件能力,只用于绘制内容,而UIView则是Layer的代理,Layer依靠UIView提供的容器显示绘制内容。
CALayer 在iOS中等同一个纹理。CALayer中的content属性就指向了一个缓存区,用于放置bitmap。

UIView也拥有他的生命周期。

在 子View 添加到 父View 的过程中,会经历下面几个函数。

1
2
3
4
- (void)willMoveToSuperview:(nullable UIView *)newSuperview;
- (void)didMoveToSuperview;
- (void)willMoveToWindow:(nullable UIWindow *)newWindow;
- (void)didMoveToWindow;

通过实际的测试我们会知道顺序为:
willMoveToSuperview -> didMoveToSuperview -> willMoveToWindow -> didMoveToWindow。
先添加到父View上面,最后在Window上面显示出来。

UIViewController (视图控制器)#

UIViewController,本身包含一个自身的UIView,通常作为一个控制器和容器,管理其下的所有View和子控制器。

开发中,常见与每个界面都有一个属于自己的 UIViewController ,界面内的相关内容都在这个 UIViewController 中处理。

UIViewController的生命周期执行顺序为

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
+ (void)initialize {
NSLog(@"类初始化方法:initialize ");
}

- (instancetype)init {
self = [super init];
NSLog(@"实例初始化方法:init");
return self;
}

- (void)loadView {
[super loadView];
NSLog(@"加载视图:loadView");
}

#pragma mark- life cycle
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"将要加载视图:viewDidLoad");
}

- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"视图将要出现:viewWillAppear:(BOOL)animated");
}

- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
NSLog(@"将要布局子视图:viewWillLayoutSubviews");
}

- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSLog(@"已经布局子视图:viewDidLayoutSubviews");
}

- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"视图已经出现:viewDidAppear:(BOOL)animated");
}

- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
NSLog(@"视图将要消失:viewWillDisappear:(BOOL)animated);
}

- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
NSLog(@"视图已经消失:viewDidDisappear:(BOOL)animated");
}

- (void)dealloc {
NSLog(@"释放:dealloc");
}

相关的业务可以在对应的函数中处理。

TabBar样式页面构建#

TabBar是最常见的iOS视图构建方式,常见于App的底部,由多个按钮组成,点击不同的按钮会切换不同的页面。

我们通常使用UITabBarController来管理TabBar以及用于切换页面的各个UIViewController。

1
2
3
4
5
6
7
8
9
10
11
12
// 创建不同的UIViewControll
let firstNavi = UINavigationController(rootViewController: UIViewController())
firstNavi.viewControllers.first?.view.backgroundColor = .red
firstNavi.tabBarItem.title = "First"

let secondNavi = UINavigationController(rootViewController: UIViewController())
secondNavi.viewControllers.first?.view.backgroundColor = .blue
secondNavi.tabBarItem.title = "Second"

// 创建TabBarController,并添加需要控制的UIViewController
let tabbarViewController = TabBarController()
tabbarViewController.viewControllers = [firstNavi, secondNavi]

Navigation管理页面跳转#

Navigation使用 栈管理页面 ,常用两种操作:

  1. push
  2. pop
1
2
3
4
// 后面的布尔值判断是否需要动画效果
navigationController?.pushViewController(NewViewController(), animated: true)

navigationController?.popViewController(animated: true)

UITableView 的使用#

delegate 简介#

delegate是方便iOS开发使用代理模式。

在iOS中,设计者先设计好一套delegate,然后在具体的实际直接调用delegate即可。
使用者则按需求实现对应的方法,并让delegate = self即可。

iOS中的delegate是基于协议protocol完成的

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
// A类中设计一个协议,协议中定义相关接口
// protocol如果继承了AnyObject表示这个协议只用于Class使用。

protocol CheckClickProtocol: AnyObject {

func navigationFinish();
}

Class A {
// 创建一个delegate变量方便外部调用,使用weak是避免循环引用(如果protocol没有继承AnyObject则不需要weak)
weak var delegate: CheckClickProtocol?

func show() {
// 在需要的时机调用delegate
delegate?.navigationFinish()
}
}

// Class B 继承协议CheckClickProtocol
Class B: CheckClickProtocol {
// Class B 实现了协议内的方法
func navigationFinish() {
print("跳转完成")
}

// 创建Class A的实例,并将delegate赋予self。
A().delegate = self
}

这样一个代理也就完成了,当调用delegate?.navigationFinish(),就调用Class B中的实现方法。

UITableView#

UITableView 是iOS开发中最常用的组建,用于表示竖列表。

官网图
通常有两种Style,左边是Plain模式,右边是Group模式

1
2
3
4
5
6
7
8
// 创建一个UITableView
var tableView: UITableView?
tableView = UITableView(frame: UIScreen.main.bounds, style: .plain)
tableView?.delegate = self
tableView?.dataSource = self
tableView?.register(UITableViewCell.classForCoder(), forCellReuseIdentifier: "cell")
// 添加到页面上
view.addSubview(tableView!)

UITableView的主要功能需要使用 UITableViewDelegate 和 UITableViewDataSource 两个协议来实现的。

  1. UITableViewDataSource 用作操作数据内容
  2. UITableViewDelegate 用于处理滚动和展示的相关逻辑

UITableViewDataSource常用协议方法:

1
2
3
4
5
6
7
8
9
10
// 设置每个组有多少行
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
// 设置每行的Cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
// 设置有多少组
func numberOfSections(in tableView: UITableView) -> Int
// 设置每组的组头内容
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
// 设置每组的组尾内容
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String?

UITableViewDelegate常用协议方法:

1
2
3
4
5
6
7
8
9
10
11
12
// 设置每行高度
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
// 设置组头的高度
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
// 设置组尾的高度
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat
// 设置自定义组头
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
// 设置自定义组尾
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView?
// 点击事件处理
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)

按照相关协议可以轻松完成相关内容。

UICollectionView 的使用#

UICollectionView是一种流式布局的表格控件。

UICollectionView的使用逻辑大部分都和UITableView是一样的,但是也有不同点:

  1. UICollectionViewCell 必须先regist注册,然后在函数 cellForItemAt 中使用
  2. UICollectionView不是用来代表每个内容的,而是用的item
1
2
3
4
5
6
7
8
9
10
11
// 创建布局模式
let collectionViewLayout = UICollectionViewFlowLayout()
// 创建UICollectionView
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: collectionViewLayout)
// 设置代理
collectionView?.delegate = self
collectionView?.dataSource = self
// 注册Cell
collectionView?.register(UICollectionViewCell.classForCoder(), forCellWithReuseIdentifier: "cell")
// 添加到页面上
view.addSubview(collectionView!)

而对应的基本协议:

1
2
3
4
5
6
7
8
9
10
11
12
// 设置需要展示的个数
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 500
}

// 设置每个Cell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = .orange
return cell
}

UICollectionViewLayout#

UICollectionViewLayout 是用于生成UICollectionView的所有布局信息的抽象类,所有自定义的用于UICollectionView的Layout都需要继承UICollectionViewLayout。

而UICollectionViewFlowLayout则是由系统提供的一个布局信息。

在Layout中有个三个基本属性:

1
2
3
4
5
6
// 设置每个item的大小
collectionViewLayout.itemSize = CGSize(width: 100,height: 100)
// 设置每行的最小间距
collectionViewLayout.minimumLineSpacing = 20
// 设置每个item左右之间的最小间距
collectionViewLayout.minimumInteritemSpacing = 20

这里值得注意的是,minimumLineSpacing 和 minimumInteritemSpacing 只是代表着最小间距,而不是实际间距,因为itemSize过小时,这个间距会增大,如果itemSize过大时,这个间距会变小,等间距小到minimumLineSpacing或minimumInteritemSpacing时,间距将不会小于这个最小值。

当然,也有更加细致的处理方式,那就是UICollectionViewDelegateFlowLayout,在使用了UICollectionViewDelegateFlowLayout委托后,可以根据里面定义的协议来处理相关内容。

比如:

1
2
3
4
5
// 处理Item的大小
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

return CGSize(width: 100, height: 100)
}

UICollectionView 实现瀑布流#

todo

UIScrollView的使用#

UIScrollView 是开发中最常见的控件之一,用于视图滚动上面。
UIScrollView 的原理:

  1. UIScrollView等于只显示视图的一部分,其它部分遮罩起来
  2. 通过变化UIScrollView的bounds属性来起到移动的效果

class UIScrollView : UIView

UIScrollView中有三个基本属性:

  • contentSize contentSize是整个scrollview的可滚动区域
  • contentOffset contentOffset表示了scrollview的移动
  • contentInset contentInset用于简单的改变滚动规则(更改滚动区域等)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func createScrollView() {
// 创建一个ScrollView
let scrollView = UIScrollView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height))

// 设置滑动模式
scrollView.isPagingEnabled = true
// 设置滑动内容总面积
scrollView.contentSize = CGSize(width: view.bounds.width * 2, height: view.bounds.height)
// 重置滑动区域位置
scrollView.contentOffset = CGPoint.zero

// 添加内容
for _ in 0...1 {
let view = UIView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height))
scrollView.addSubview(view)
}

// scrollView 添加到显示页面
view.addSubview(scrollView)
}

UIScrollViewDelegate#

当然,UIScrollView也有着一套协议,只要遵循 UIScrollViewDelegate 即可

其中常见的协议内容是:

scrollViewDidScroll:

滑动监听

scrollViewWillBeginDragging:

开始滚动时调用一次

scrollViewWillEndDragging:withVelocity:targetContentOffset:

didEndDragging前调用,滑动scrollView,并且手指离开时执行。一次有效滑动,只执行一次。

scrollViewDidEndDragging:willDecelerate:

滑动视图,当手指离开屏幕那一霎那,调用该方法。一次有效滑动,只执行一次。

scrollViewShouldScrollToTop:

指示当用户点击状态栏后,滚动视图是否能够滚动到顶部。需要设置滚动视图的属性

scrollViewDidScrollToTop:

当滚动视图滚动到最顶端后,执行该方法

scrollViewWillBeginDecelerating:

滑动减速时调用该方法。

scrollViewDidEndDecelerating:

滚动视图减速完成,滚动将停止时,调用该方法。一次有效滑动,只执行一次

UIScrollView的属性#

  • scrollEnabled : UIScrollView能否滑动 , 默认为true
1
open var isScrollEnabled: Bool
  • directionalLockEnabled : UIScrollView是否限制滑动方向,true则为只能单一方向滑动,false则是不限制,默认为false
1
open var isDirectionalLockEnabled: Bool
  • scrollsToTop : 点击状态栏后回到scrollView顶部,此属性默认为true,但是同期有多个scrollview时会失效,要将不需要此功能的scrollview及其子类的scrollsToTop设为false
1
open var scrollsToTop: Bool
  • scrollRectToVisible:animated: : 在可滚动范围内,滚动到一个未显示区域。
1
open func scrollRectToVisible(_ rect: CGRect, animated: Bool)
  • pagingEnabled : 是否支持分页 ,默认为false
1
open var isPagingEnabled: Bool
  • bounces : 是否支持弹簧 ,默认支持
1
open var bounces: Bool
  • alwaysBounceVertical | alwaysBounceHorizontal : 在bounces为YES时,设置垂直或者竖直反弹是否有效 ,默认均为false
1
2
open var alwaysBounceVertical: Bool
open var alwaysBounceHorizontal: Bool
  • tracking : 用户按上屏幕时,返回true
1
open var isTracking: Bool { get }
  • touchesShouldBegin:withEvent:inContentView: : 在子类中重写,点击屏幕时,如果处于一个可交互的视图上,则调用此函数,并且返回为false的话,该子视图的交互无效
1
open func touchesShouldBegin(_ touches: Set<UITouch>, with event: UIEvent?, in view: UIView) -> Bool
  • touchesShouldCancel: 返回值true,发生滚动, touch不在传递给子视图, false则不滚动,touch 传递给子视图
    canCancelContentTouches: 如果是false ,即使touch move,也不滚动的,如果是true, - tracking后,手指移动,会调用 touchesShouldCancelInContentView 方法;
1
2
open var canCancelContentTouches: Bool
open func touchesShouldCancel(in view: UIView) -> Bool
  • delaysContentTouches : 默认值为true;如果设置为false,则无论手指移动的多么快,始终都会将触摸事件传递给内部控件;设置为NO可能会影响到UIScrollView的滚动功能。
1
open var delaysContentTouches: Bool
  • dragging : 在scrollview上面,按着时为true,松开为false
1
open var isDragging: Bool { get }
  • decelerating : 抬起手指后,内容是否滑动
1
open var isDecelerating: Bool { get }
  • decelerationRate : 手指放开后的减速率
1
open var decelerationRate: UIScrollView.DecelerationRate
  • Scroll Indicator 滑动控制器
1
2
3
4
5
- indicatorStyle //滚动控制器风格
- scrollIndicatorInsets //表示滚动指示器从封闭滚动视图中被嵌入的距离。
- showsHorizontalScrollIndicator
- showsVerticalScrollIndicator
- flashScrollIndicators //短暂地显示滚动指示器。

UILable的使用#

用于展示一行或者多行 read-only 的文字。

class UILabel : UIView

常用属性:

  1. text 文字展示
  2. textColor 字体颜色
  3. font 字体型号
  4. textAlignment 对齐方式
  5. backgroundColor 背景色
  6. numberOfLines 最大展示行数
  7. lineBreakMode 文字超出后的,表示省略的小数点出现的位置
  8. sizeToFit() 让UILabel的面积大小去适应文字内容大小,以确保文字能被完全显示

Ex.

1
2
3
4
5
6
7
8
9
10
11
12
let showTxt = UILabel(frame: CGRect(x: 100, y: 100, width: 200, height: 50))

showTxt.text = "文字展示"
showTxt.textColor = .orange
showTxt.font = UIFont.systemFont(ofSize: 16)
showTxt.textAlignment = .left
showTxt.backgroundColor = .darkGray
showTxt.numberOfLines = 1
showTxt.lineBreakMode = .byTruncatingTail
showTxt.sizeToFit()

view.addSubview(showTxt)

sizeToFit()可以让UILabell自适应文字,所以当需要文字自适应的时候很好用

Ex.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let showTxt = UILabel(frame: CGRect(x: 10, y: 100, width: 200, height: 50))
showTxt.text = "文字展示"
view.addSubview(showTxt)

let nextLable = UILabel(frame: CGRect(x: 210, y: 100, width: 0, height: 0))
nextLable.text = "下一段话语"
nextLable.sizeToFit()
view.addSubview(nextLable)

let finalLabel = UILabel(frame: CGRect(x: 210 + nextLable.bounds.width, y: 100, width: 0, height: 0))
finalLabel.text = "结束话题"
finalLabel.sizeToFit()
view.addSubview(finalLabel)
// 最后三段文字会紧密的挨在一起

UIImageView 的使用#

UIImageView是一个专门用于显示图像或者动图的类

class UIImageView : UIView

iOS中,所有的图片都会被系统封装成 UIImage,然后去用于展示。

常见的封装方式:

1
2
3
4
5
6
7
8
// 从 main bundle 中获取资源去封装
let image0 = UIImage(named: "home")

// 使用具体的data资源去封装
let image1 = UIImage(data: Data())

// 按照硬盘文件目录去获取资源去封装,而不是从main bundle中
let image2 = UIImage(contentsOfFile: "urlPath")

创建UIImageView通常有静图和动图两种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 封装两个图片
let image0 = UIImage(named: "home")
let image1 = UIImage(named: "personal")

// 创建一个UIImageView并显示一张图片
let imageView = UIImageView(image: image0)
imageView.frame = CGRect(x: 0, y: 300, width: 100, height: 100)
view.addSubview(imageView)

// 创建一个UIImageView并显示一组动图
let animationImageView = UIImageView()
animationImageView.frame = CGRect(x: 100, y: 300, width: 100, height: 100)
animationImageView.animationImages = [image0!, image1!]
animationImageView.animationDuration = 0.5
animationImageView.startAnimating()
view.addSubview(animationImageView)

因为图片大小有时候和UIImageView 的大小会不一样,使用UIImageView也提供可选择的适应方式。

  • scaleToFill 缩放图片,让ImageView完全显示图片
  • scaleAspectFit 保持图片比例缩放,ImageView会完全显示图片,但是会出现留白
  • scaleAspectFill 保持图片比例缩放,ImageView不完全显示图片,也不会出现留白

手势和点击#

UIButton#

UIButton是系统自带的按钮控件

class UIButton : UIControl

UIButton的使用非常简单:

1
2
3
4
5
6
7
8
9
10
// 创建Button
searchButton = UIButton(frame: CGRect(x: 0, y: view.bounds.height/2, width: 100, height: 50))
// 设置Button在不同状态下的样式
searchButton?.setTitle("点击", for: .normal)
searchButton?.setTitle("按住", for: .highlighted)

view.addSubview(searchButton!)

// 设给button添加点击事件,onClickHandler: 是事件处理函数
searchButton?.addTarget(self, action: #selector(onClickHandler(sender:)), for: .touchUpInside)

UIButton是继承自UIControl的,UIControl中iOS主要用于各种事件处理。UIButton继承了UIControl后也拥有了事件处理能力

Target-Action#

Target-Action 是一种事件处理常用 设计模式。
比如事件处理中,触发了某个事件后,对应的Target就会去调用对应的Action来处理事件。

比较典型的就是button的点击事件处理:

1
2
//其中 Self 就是 Target,对应的处理函数则是 Action,而touchUpInside则是触发的条件
button?.addTarget(self, action: #selector(onClickHandler(sender:)), for: .touchUpInside)

UIGestureRecognizer#

UIGestureRecognizer 是处理手势的基类

class UIGestureRecognizer : NSObject

在任何UIView上面,都是可以添加一个或者多个手势的,然后通过Target-Action来处理事件。

添加手势时需要小心,手势会影响一些系统自带控件的事件响应

常见的手势封装有:

  • UITapGestureRecognizer “点击”手势,可以设置点击的次数
  • UIPinchGestureRecognizer “捏合”手势,比如可以用来改变图片大小
  • UIRotationGestureRecognizer “旋转”手势,两指转动
  • UISwipeGestureRecognizer “轻扫”手势,手指从屏幕上轻划过
  • UIPanGestureRecognizer “滑动”手势,可以识别拖拽或移动动作
  • UIScreenEdgePanGestureRecognizer 边缘滑动
  • UILongPressGestureRecognizer “长按”手势

手势的使用也很简单:

1
2
3
4
5
6
7
8
9
10
11
func creatGestureRecognizer() {
// 创建一个需要使用的UIView
let tapView = UIView(frame: CGRect(x: 0, y: 500, width: 200, height: 100))
tapView.backgroundColor = .cyan
view.addSubview(tapView)

// 创建一个手势,使用Target-Action添加好事件处理
let tapGR = UITapGestureRecognizer(target: self, action: #selector(onClickHandler(sender:)))
// 将事件添加到需要作用于的UIView,即可
tapView.addGestureRecognizer(tapGR)
}

UIGestureRecognizer 也有对应的delegate,可以对其做更加细致的处理

WKWebView#

WKWebView是用来在app内显示和处理web内容的控件。

class WKWebView : UIView

使用WKWebView需要导入WebKit框架

1
2
3
4
5
6
// 创建webview的配置对象
let webConfig = WKWebViewConfiguration()
// 创建webview
webView = WKWebView(frame: view.bounds, configuration: webConfig)
// 添加到显示页面上
view.addSubview(webView)

加载URL

1
2
3
if let urlPath = URL(string: "blog.csdn.net") {
webView.load(URLRequest(url: urlPath))
}

需要注意的是,WKWebView默认是无法加载http,需要加载https。
一定需要加载http的话,可以通过修改或者添加Info.plist中的NSAppTransportSecurity来完成

WKUIDelegate和WKNavigationDelegate#

要更加细节的出现WKWebView的内容,就需要WKUIDelegate和WKNavigationDelegate两个delegate了。

WKNavigationDelegate 处理跳转和请求相关内容

  • func webView(_ webView: WKWebView, decidePolicyFor navigationAction:

    是否加载请求,用于处理 scheme拦截,js和native通信 以及 特殊处理

  • func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!);

    webView完成加载

  • func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error);

    webView加载失败,可在此时显示重新加载按钮

  • func webViewWebContentProcessDidTerminate(_ webView: WKWebView);

    webView crash 回调

WKUIDelegate 处理UI相关内容

  • func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void)

    Alert弹框

  • func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void)

    TextInput弹框

  • func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void)

    confirm弹框

通过KVO来处理进度条之类的问题#

KVO 是iOS自带的处理 监听模式 的API。通过为某个对象添加监听者,来监听对象的属性变化。

KVO 的使用容易造成各种循环使用,而且使用繁杂,所以网上有很多安全并且方便的KVO框架来使用

以WebView中的进度条为例:

1
webview.addObserver(self, forKeyPath: "estimatedProgress", options: NSKeyValueObservingOptions.new, context: nil)

为对象 webview 添加一个监听者 self,监听的内容是 estimatedProgress 属性,监听的要求是 NSKeyValueObservingOptions.new (当出现新内容时)。

1
2
3
4
5
6
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {

if keyPath == "estimatedProgress" {
print("已经加载 \(webView.estimatedProgress * 100)%")
}
}

通过 observeValue 方法,获取监听的内容。

在 Swift 4 之后,iOS有添加了新的添加监听的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建 NSKeyValueObservation 变量,这个变量必须有
var observation: NSKeyValueObservation?

fun observationFunc() {
// 按照方法提示添加监听
observation = webView.observe(\WKWebView.estimatedProgress, options: .new) { [weak self] (wkwebview, change) in
// 必须使用weak self,不然很容易出现循环引用
guard let self = self else {
return
}
print("已经加载 \(change.newValue! * 100)%")
self.progressview.progress = Float(wkwebview.estimatedProgress)
}
}

PS: 在以前的旧版本中,还需要在dealloc中添加 removeObserver 来手动取消监听,但是在xcode7以上,就不需要移除监听者为self的监听了,系统会自动处理。

WebView和Native显示复杂页面#

开发中,我们经常将 WebView 和 Native 混合使用:

  1. 一些多端统一的 富文本 内容,由webview来显示。比如:图文详情
  2. 而复杂的控件则让Native来显示。比如:比如视频播放

很多第三方开源项目可以方便我们作相关的混合处理,比如 HybridPageKit 等

iOS动画#

iOS中的动画核心是 Core Aniamtion。在此基础上,苹果封装了一批简单的、方便使用的动画API。

  1. UIView动画
  2. UIkit组件自带的动画

UIView动画#

UIView动画是iOS动画开发中最常见的,基本上可以包含我们开发中大部分的动画需求。处理基本的Frame,Alpha,Transform等需求,缺点则是不能自定义动画的中间过程。

UIView动画的设置非常简单:

1
2
3
4
5
6
7
UIView.animate(withDuration: 1, delay: 0, options: .autoreverse, animations: {
let newXpoing = animationImageView.frame.origin.x + 200
let newFrame = CGRect(x: newXpoing, y: animationImageView.frame.origin.y, width: animationImageView.bounds.width, height: animationImageView.bounds.height)
animationImageView.frame = newFrame
}) { (finish) in
print("是否动画完成:\(finish)")
}

通过UIView自带的animation方法,duration代表一共需要的时间,delay代表延多少时间开始,options表示动画的类型,后面两个block中,前者animations表示动画结束的状态,后者表示动画结束后的逻辑。

实际上这是最简单的UIView动画,UIView还提供了非常多的动画API方便使用。

CoreAniamtion#

CoreAniamtion常用语处理要求更高,更加细致的动画。比较常用的几个类是:

  1. CABasicAnimation 基础动画
  2. CAKeyframeAnimation 设置关键路径和时间的动画
  3. CAAnimationGroup 简单动画组成复杂动画
  4. CATransition 转场动画

以 CAKeyframeAnimation 为例:

1
2
3
4
5
6
7
8
9
10
// 创建一个 CAKeyframeAnimation 对象,并设置keyPath
let keyframeAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
// 设置动画的关键时间点
keyframeAnimation.keyTimes = [0, 0.2, 1]
// 设置动画的每个关键时间点的关键值大小
keyframeAnimation.values = [1, 2, 1]
// 设置动画的运行时间
keyframeAnimation.duration = 5
// 在需要动画的对象上添加动画
animationImageView.layer.add(keyframeAnimation, forKey: "scaleAnimation")

而CAAnimationGroup则是将复杂动画拆解为多个简单动画:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 设置一段简单动画
let keyframeAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
keyframeAnimation.keyTimes = [0, 0.2, 1]
keyframeAnimation.values = [1, 2, 1]
keyframeAnimation.duration = 5

// 设置另一个简单动画
let keyframeAnimationMove = CAKeyframeAnimation(keyPath: "transform.scale")
keyframeAnimationMove.keyTimes = [0, 0.2, 1]
keyframeAnimationMove.values = [1, 2, 1]
keyframeAnimationMove.beginTime = 5
keyframeAnimationMove.duration = 5

// 创建 CAAnimationGroup 对象
let animationGroup = CAAnimationGroup()
// 设置这组动画的运行时间
animationGroup.duration = 10
// 将各个简单动画组合在一起
animationGroup.animations = [keyframeAnimation, keyframeAnimationMove]
// 在需要动画的对象上添加动画
animationImageView.layer.add(animationGroup, forKey: "animationGoup")

这些CAKeyframeAnimation对象中的keyPath,是指的CALayer中的属性

在复杂动画中,还有一种关于粒子效果的动画,在iOS也有着 CAEmitterLayer 类的API去实现,具体情况具体分析。

UITextView & UITextFeild#

  • UITextFeild 可编辑的文字输入控件,常见于输入框
  • UITextView 可滚动展示的文字输入控件,常见于复杂的富文本显示和编辑
1
2
3
4
5
6
// 创建 UITextField 对象
let inputFeild = UITextField(frame: CGRect(x: 10, y: 150, width: 200, height: 50))
inputFeild.backgroundColor = .orange
// 设置 UITextField 的默认提示语
inputFeild.placeholder = "请输入内容"
view.addSubview(inputFeild)
1
2
3
4
5
6
// 创建 UITextView 对象
let textView = UITextView(frame: CGRect(x: 10, y: 200, width: 200, height: 100))
// 给 UITextView 对象 复制 显示内容
textView.text = "红红火火恍恍惚惚,红红火火恍恍惚惚,红红火火恍恍惚惚,红红火火恍恍惚惚,红红火火恍恍惚惚,红红火火恍恍惚惚,红红火火恍恍惚惚,红红火火恍恍惚惚,红红火火恍恍惚惚,红红火火恍恍惚惚,红红火火恍恍惚惚,红红火火恍恍惚惚"
textView.backgroundColor = .yellow
view.addSubview(textView)

UITextView & UITextFeild 都拥有 becomeFirstResponder() 方法,可以直接将焦点转移到对应的 UITextView & UITextFeild 中,并调起 键盘。
inputFeild.becomeFirstResponder()

UITextView & UITextFeild 也有各自对应的Delegate,和 UIScrollView 一样,更加细节的操作可以根据Delegate提供的方法来实现。

网络开发#

系统网络框架#

APP中的一个基本的网络请求流程是:

  1. url字符串通过URL(string:)方法根据相关的协议封装为URL对象
  2. 然后通过URLRequest(url:),添加URL和一些请求参数和设置,封装为一个URLRequest对象,形成一个请求
  3. 然后通过系统提供的URLsession去将URLRequest对象发送给服务器。(URLsession接受,发送和处理请求,封装Request为Task,然后做处理)
  4. 接受服务器返回的数据

URL格式:

作用:资源定位

[协议类型]://[服务器地址]:[端口号]/[资源层级文件路径]/[文件名]?[查询]#[相关ID]

https://192.163.0.1:8080/files/net.md?name="boy"#nose

URLsession是iOS网络开发中非常重要的一个区域。主要用于负责接受,发送和处理请求
一个URLsession可以创建多个请求,同时一个app也可以创建多个URLsession
URLsession封装Request为Task,来控制其状态。

一个简单的用于理解的例子就是浏览器,一个浏览器可以打开多个窗口请求不同的网站(URLsession可以创建多个请求),而且同时一个浏览器还可以同时拥有正常模式和无痕模式(一个app也可以创建多个URLsession)。

URLsession封装Request为Task,而常见的Task有四种:

  1. dataTask 处理JSON之类的数据流,也是开发中最常见的类型
  2. downloadTask 处理大数据的下载,查看进度和断点下载等
  3. uploadTask 上传数据
  4. streamTask 流数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func requestHandler() {
// 封住URL
let url = URL(string: "http://t.weather.sojson.com/api/weather/city/101030100")
// 如果需要设置request,则需要继续封装request。
var request = URLRequest(url: url!, cachePolicy: .returnCacheDataElseLoad)
request.httpMethod = "Get"
// 系统提供了一个URLSession,除非特殊需要,不用重新创建一个
let session = URLSession.shared
// data就是服务器返回的数据
let task = session.dataTask(with: request) { (data, response, error) in
let dicStr = String(data: data ?? Data(), encoding: .utf8)
print("data: \(dicStr ?? "")")
}
// 请求发起
task.resume()
}

URLRequest 默认为 Get,当为 Get 时,可以设置缓存策略

  1. useProtocolCachePolicy : NSURLRequestCachePolicy枚举定义了一些常量,这些常量被用来指定当系统处理网络请求时与缓存系统的交互类型。这些常量覆盖了很多需要做的交互,在确定是否已经存在缓存数据用于满足加载请求后做。

  2. reloadIgnoringLocalCacheData : URL加载的数据应该从来源加载。不应该使用任何现有的本地缓存数据(不论其是否是新的或是有效的)来满足URL加载请求。

  3. reloadIgnoringLocalAndRemoteCacheData : 不使用本地缓存,且在协议允许的范围内也不使用任何代理以及中介的缓存。

  4. returnCacheDataElseLoad : 已有的缓存不管是否到期都应该被用来满足加载请求。如果依然没有缓存的数据,请求会从原地址加载。

  5. returnCacheDataDontLoad : 已有的缓存不管是否到期都应该被用来满足加载请求。如果依然没有缓存的数据,也不会从原地址尝试加载。这种情况一般用于“离线模式”。

  6. reloadRevalidatingCacheData : 已有的缓存现需要从来源证实有效性,不然需要从原地址重新加载。

所有按照实际情况选择缓存策略。

项目中设计网络层#

很多iOS开发问题,都可以用中间层来解决。iOS中的网络请求设计也是如此。

在业务方面的网络层,通常有三个要点:

  1. 请求接口
  2. 数据回调
  3. 数据转换
  4. 数据缓存

请求接口
即网络层调用接口输入参数,通常有两张设计方式。

  • 集中式

    所有参数通过一个接口输入。

    好处则是参数明确,使用简单。
    缺点也是请求太多后会变得臃肿复杂。

  • 分布式

    设计一个请求基类,然后根据不同的业务设计一个继承自基类的业务请求类,在各自的业务中直接实例化这个业务请求类获取网络数据。

    好处非常明显, 让请求网络的业务中,代码量减少,设计更加的优雅轻盈,可扩展性也强。
    缺点则是当业务非常的多的时候,必然会导致类爆炸,同时维护上百个业务请求类。

数据回调
网络层的传输大多以异步加载为主,即服务器响应后由网络层来负责将数据推给上层业务线程。因此,在回调方案中,block delegate notification 均是不错的选择方案,可以根据不同的实际情况做选择。通常情况下 block 使用频率稍高。

数据转换
这个其实是一个返回数据处理问题。通常我们并不是将服务端返回的数据直接拿出来用的,而是转换成 Dictionary 或者 Model。而这个返回 可用数据 的过程也是网络层需要处理的地方之一。

数据缓存
数据缓存在网络请求中也需要考虑,当请求的数据不是容易变化的数据时,就需要考虑数据缓存问题,将请求成功后服务器给的数据缓存下来,在下次请求时直接使用缓存的数据,这样可以减少流量消耗和请求时间。

数据#

JSON解析#

JSON是一种轻量级的、高可读性的、纯文本形式的数据交换语言,传输由属性值或者序列性的值组成的数据对象。也是app网络开发中最常见的数据交换语言。

iOS中也自带了解析JSON的方法。

在OC或者Swift4之前,常用JSONSerialization来实现,将JSON转成NSDictionary

1
2
3
// data是json对象
let jsonDic = (try! JSONSerialization.jsonObject(with: data!, options: .mutableContainers)) as! NSDictionary
print("data: \(jsonDic)")

Swift4之后则拥有Codable来直接编码或者解码JSON。

public typealias Codable = Decodable & Encodable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 设置CityInfo遵循Decodable解码协议
struct CityInfo: Decodable {
// 变量和JSON里面的key也必须是一致的
var city: String
var citykey: Int
var parent: String
var updateTime: String
}

// 解码JSON对象data, 将其转化成CityInfo对象
guard let jsonModel = try? JSONDecoder().decode(CityInfo.self, from: data!) else {
fatalError("`JSON Decode Failed`")
}

print("data: \(jsonModel)")

实际上,iOS中的数据使用通常都是 JSON 转成 字典或者数组 然后转成 Model 的过程,最后使用 Model 的过程。

文件管理#

iOS APP中,每个APP都有有着自己的沙盒。APP之间除非使用苹果提供的特殊方法外,是无法互相访问的。

每个APP的沙盒内,一般包含两个部分:

  1. APP自身的内容 〖 Bundles 〗文件
    • 应用配置文件
    • 二进制资源和文件
  2. APP的文件系统(文件内的内容按文件名分类) 〖 Datas 〗文件
    • Document 可存放体积较大的用户数据
    • Library 开发者常用,自带缓存文件夹(Cache) 和 用户偏好设置等(Preferences),还可自定义子文件夹
    • SystemData 系统文件
    • temp 临时文件

获取沙盒的方法:

NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)

NSSearchPathForDirectoriesInDomains是方法宏,documentDirectory是Document文件夹,userDomainMask指用户文件。

FileManager#

FileManager是iOS提供的操作文件的管理类。

Ex.

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
func conigFile() {
// 获取系统自带的 FileManager
let fileManage = FileManager.default

// 获取 缓存文件 路径
let cachePath = URL(string: NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first!)
// 设置 新文件夹 路径
let dataPath = cachePath?.appendingPathComponent("WeathAppData")

// 创建文件夹
do {
try fileManage.createDirectory(atPath: dataPath!.path, withIntermediateDirectories: true, attributes: nil)
}
catch {
fatalError("创建失败")
}

// 设置 文件 路径
let itemPath = dataPath!.appendingPathComponent("item")

// 创建文件内容
let itemData = Data()

// 创建文件
do {
try fileManage.createFile(atPath: itemPath.path, contents: itemData, attributes: nil)
}
catch {
fatalError("创建失败")
}

}

其他的文件移动,删除,拷贝等操作也通过 FileManager 提供的API 来完成。

存储#

iOS中系统提供了三种储存方式:

  1. UserDefaults
  2. NSKeyedArchiver 序列化归档
  3. CoreData 数据库
  4. Keychain 密码管理和存储

UserDefaults#

UserDefaults 内容存储在系统的 Library 文件夹下的 Preferences 文件夹中。存储对象以轻量级为主,用户设置之类的内容偏多。

1
2
3
4
5
6
// 系统自带的UserDefaults单例,按照 key-value的方式存放数据
UserDefaults.standard.set("123", forKey: "test")

// 系统自带的UserDefaults单例,调用获取value的方法
let test = UserDefaults.standard.value(forKey: "test")
print("UserDefaults: \(test ?? "nil")")

第三方存储#

第三方存储中常见的是两个类型

  1. key-value 类型 : 比如 Realm 等
  2. 关系数据库 类型:比如 SQLite,FMDB 等

SQLite是比较基础的跨平台数据库,无论是苹果的CoreData还是第三方的FMDB都对其的封装。

线程#

进程#

  • 创建进程:创建所有必要的管理信息,创建完成后才会加入下一步
  • 就绪:获得了一切需要资源和管理信息
  • 运行:运行状态进程数小于等于系统处理器的总数
  • 阻塞:等待,睡眠
  • 终止:进程结束运行

线程#

线程是进程的基本执行单元,进程的任务都是在线程中执行的。其中主线程负责主要的进程任务,在iOS开发中,主线程通常为UI线程。

多线程

多线程常用地方:

  • 网络请求
  • 图片加载

任务执行方式:

  • 串行
  • 并行

多线程执行原理

  • 单核操作系统主要是通过时间片来实现的,两个线程在不同的时间片上交替执行,完成多线程。
  • 多核操作系统是把线程分配给不同的处理器来执行,完成真正的并行。

优缺点

  • 优点:

    1. 简化了编程模型:高效的处理大型任务或者零散任务
    2. 更加轻量级
    3. 提高了效率和资源利用率
  • 缺点:

    1. 加强了程序设计的复杂性
    2. 占用内存空间(不要乱用多线程)
    3. 增加了CPU的调度开销,增加了CPU占有率

iOS中多线程的实现技术方案

  • pThread
  • NSThread 针对线程
  • GCD 针对线程队列
  • NSOperation 对于GCD的面向对象封装

GCD 的使用#

iOS开发中比较常用的多线程技术,苹果官方说法为:异步执行任务的技术之一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 获取全局并发队列,执行异步
DispatchQueue.global().async {

var image:UIImage?
do {
// 网络请求下载图片
let data = try Data(contentsOf: URL(string: "https://i0.hdslb.com/bfs/article/32d57fcafe36df2896d47be0031b33ab8f46eaad.jpg")!)
image = UIImage(data: data)
}
catch {
fatalError()
}

if (image != nil) {
// 获取主线程队列,进行UI操作
DispatchQueue.main.async {
self.topImageView.image = image
}
}
}

自定义队列:

1
2
// label: 自定义队列名;  attributes: 使用 并发或者串行
let concurrentQueue = DispatchQueue(label: "com.concurrentQueue", attributes: .concurrent)

网络图片加载#

缓存异步网络请求的数据#

图片缓存在网络图片加载中相当重要,将请求成功后服务器给的图片数据缓存下来,在下次请求时直接使用缓存的数据,这样可以减少流量消耗和请求时间,常用用于图片下载。

以常用的第三方图片异步网络图片加载框架 SDWebImage例:

  1. 设置一套散列表,将请求的 URL 编码后做为 key 值。
  2. 根据 key 值,查找散列表中是否有对应的值,如果用,就根据对应的值在缓存中获取图片数据。
  3. 如果没有对应的值,则直接进行网络请求,并将获得的数据缓存,然后设置对应URL的value值。

SDWebImage 的使用#

SDWebImage 的设计结构:

1
2
3
4
// sd_setImage: 设置图片
// URL: url地址
// placeholderImage: 占位图
imageView.sd_setImage(with: URL(string: "http://www.domain.com/path/to/image.jpg"), placeholderImage: UIImage(named: "placeholder.png"))

cocospods使用pod init 出现bad interpreter: No such file or directory
通常为mac升级系统时,可能和导致cocospod不能使用,这时升级一下cocospod即可。

1
2
3
>$ sudo gem update --system
>$ sudo gem install cocoapods -n/usr/local/bin
>

音视频#

系统框架 AVKit 和 AVFoundation#

AVKit (iOS8+ ) 是iOS 提供的用于音视频开发的框架。

  1. 提供AVPlayerViewController
  2. 提供层级较高的接口
  3. 提供创建播放相关的UI内容

常见的点击视频后,打开了一个背景黑色的默认播放界面,那个一般就是AVPlayerViewController。

AVFoundation
AVKit 是基于 AVFoundation 封装的框架,如果AVKit无法满足需求,需要对音视频更为底层的控制和自定义,则可以直接使用 AVFoundation。

AVFoundation 有几个关键属性需要认知:

  1. AVAsset 资源属性 (各种媒体数据)
  2. AVPlayer 播放控制属性 (Controller)
  3. AVPlayerLayer 播放画面属性 (View)
  4. AVplayerItem AVAsset封装,播放状态属性 (Model)

使用流程: AVplayerItem封装AVAsset,AVPlayer根据AVplayerItem创建播放器,AVPlayer中获取AVPlayerLayer,将AVPlayerLayer添加到需要播放的View的Layer上面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 获取URL
guard let mp4Url = URL(string: DefineConst.TestMap4Url) else {
fatalError()
}
// 封装URL成AVPlayerItem
let playerItem = AVPlayerItem(url: mp4Url)
// 创建AVPlayer
// 如果不需要处理AVPlayerItem的属性,可以直接调用:
// let playerController = AVPlayer(url: mp4Url)
let playerController = AVPlayer(playerItem: playerItem)
// 创建AVPlayerLayer
let playerLayer = AVPlayerLayer(player: playerController)
// 将AVPlayerLayer添加到需要播放的View的Layer上面
playerLayer.frame = avplayerView.bounds
avplayerView.layer.addSublayer(playerLayer)
// 播放
playerController.play()

通常app内,正在播放的视频只会是一个,所以推荐创建一个管理单例去设置视频播放。

KVO和Notification#

从WebView开始我们就明白用KVO来处理进度条,实际上在视频播放中也是如此,用KVO和Notification来处理部分播放的属性。

Notification的使用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 创建通知中心 NotificationCenter.default
// 设置监听方法 test
// 设置通知的名字 isTest
NotificationCenter.default.addObserver(self, selector: #selector(test), name: NSNotification.Name(rawValue:"isTest"), object: nil)

// 实现通知监听方法
@objc func test(nofi : Notification){
let str = nofi.userInfo!["post"]
print(String(describing: str!) + "this notifi")
}

// 点击发送通知进行
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
NotificationCenter.default.post(name: NSNotification.Name("isTest"), object: self, userInfo: ["post":"NewTest"])
}

// 移除通知
deinit {
/// 移除通知
NotificationCenter.default.removeObserver(self)
}

布局#

frame#

frame 是iOS中最基础的布局。通过计算绝对坐标布局视图位置。

AutoLayout#

AutoLayout 有别于 frame,不再是绝对坐标,还是变成了描述性质的语言,只需要写上约束条件,然后根据约束条件,系统生成frame去布局位置。

  • 好处: 布局变得简单,特别是可变内容或者屏幕适配之类的
  • 坏处:一定程度上性能不如frame,所以性能敏感的地方不要使用

AutoLayout 布局一般使用NSLayoutConstraint,但是NSLayoutConstraint使用非常烦琐,为了解决AutoLayout的编码复杂问题,苹果推出了VFL语言来方便编码,但是实际上VFL仍然有很多的不足,所以我们通常还是使用第三方库来完成约束。

但是在iOS9 之后可以使用一种新的布局API : NSLayoutAnchor

class NSLayoutAnchor<AnchorType> : NSObject where AnchorType : AnyObject

1
2
3
4
5
6
7
8
9
10

avplayerView.translatesAutoresizingMaskIntoConstraints = false
// 给avplayerView添加宽度约束
avplayerView.widthAnchor.constraint(equalToConstant: view.bounds.width).isActive = true
// 给avplayerView添加高度约束
avplayerView.heightAnchor.constraint(equalToConstant: view.bounds.width * 9 / 16).isActive = true
// 给avplayerView的顶部和topImageView的底部添加约束,约束值为0
avplayerView.topAnchor.constraint(equalTo: topImageView.bottomAnchor).isActive = true
// avplayerView的纵向和view的纵向一致
avplayerView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true

iOS适配#

分辨率:

  • 逻辑分辨率:直观反应大小和距离,描述显示的单位,例如UIScreen取的屏幕数据就是逻辑分辨率尺寸
  • 物理分辨率:手机屏幕像素大小,物理分辨率越高,色彩丰富度也越高

逻辑分辨率 是 物理分辨率 按照一定的缩放采样形成的。

通常适配采用的是 逻辑分辨率

适配主要有三点:

  1. 位置,大小,文字是否按比例适配
  2. 图片资源适配 @1x @2x @3x 推荐使用 ImageAsset
  3. iPhonex适配 safeArea等
  4. autolayout

App间的唤起和通信#

URL-Scheme#

Scheme一般用来表示协议,比如 http、https、ftp 等,URL Scheme的字面意思理解就是地址协议。

alipay://open?page=payPage&title=pay

Scheme: alipay

在移动端Safari浏览器输入以上示例网址就会提示你 在”支付宝”中打开链接吗?,然后由你选择”取消”或”打开”。和HTTP协议格式的URL访问流程进行对比,iOS URL Scheme实际上就是启动一个应用的 URL。

设置方法:

选择项目的 TARGETS,打开 Info栏,找到 URL Type,然后根据提示填写URL-Scheme即可

一般情况下,是会调用先安装的app。但是iOS的系统app的URL Scheme肯定是最高的。所以我们定义URL Scheme的时候,尽量避开系统app已经定义过的URL Scheme。

Universal Link#

Universal Link(通用链接) 是Apple在 iOS9 推出的一种能够方便的通过传统HTTPS链接来启动APP的功能,可以使用相同的网址打开网址和APP。当你的应用支持Universal Link(通用链接),当用户点击一个链接是可以跳转到你的网站并获得无缝重定向到对应的APP,且不需要通过Safari浏览器。

相对于URL-Scheme,Universal Link更加的常见,通常会开发一个和 原生APP 内容一致的 H5页面,然后这个页面的 url 即是一个 Universal Link,当你打开这个页面的时候,会查看你的APP是否有安装对应的APP,如果有则直接跳转到 原生APP 的对应页面,如果没有则直接显示一样内容的 H5 页面。

使用Universal Link需要到开发者中心配置:
找到对应的App ID,在Application Services列表里有Associated Domains一条,把它变为Enabled就可以了。

然后在工程配置中相应功能:targets->Signing&Capabilites->Capability->Associated Domains,在其中的Domains中填入你想支持的域名,也必须必须以applinks:为前缀。

官方配置文档

#

iOS 中常见的库 :

  1. 库 Library
  2. 静态库 .a文件
  3. 动态库 .dylib文件 iOS开发中基本不用

OAuth和OpenID#

  • OAuth 授权

    提供第三方登录授权,避免在APP输入第三方的账户密码,增强了安全性。
    比如在APP中使用微信登录的流程。

  • OpenID

    隐藏明文,每个APP独立OpenID
    在第三方登录中,使用OpenID来标记用户,增强用户的信息的安全性
    比如用第三方微信登录时,微信会把代表用户的微信号加密成OpenID,来标记用户

日志#

日志作用#

日志系统的作用:

  1. 定位问题
  2. 记录用户的行为操作
  3. 记错错误的执行
  4. Crash收集
  5. Debug过程数据

通常日志系统会配合上报,上报后,后台会分析和整理这些日志数据,方便开发者等相关人员查看。

日志系统存储相关日志在本地时,注意在 非主线程&线程安全

通常使用 开源项目 来创建日志系统 ,比如 CocoaLumberjack

上报#

上报技术常见两种:

  • 埋点

    在具体的业务代码中添加相应的代码
    特别:代码侵入,无法更改
    比如 友盟 的

  • 无埋点

    基于Runtime等技术Hoot通用方法,不需要在业务代码中添加内容
    特别:动态下发、增加、删除 上报
    比如 Aspect 的

上报 最好还是使用 公司内部平台 或者 第三方数据平台 比较好。

Crash上报和处理#

推荐使用 开源项目 : Bugly / 友盟 等

定位#

使用系统框架:
CoreLocation.framework

可以获取设备的 地理位置,方向 和 海拔 等。

定位授权包含两种:

  • when-in-use authorization: 可以在应用运行期间使用定位服务,可以开启后台运行时扔使用定位,但是不能在获取定位时自动启动应用
  • always authorization: 可以使用所有的定位服务,如果与定位相关的事件发生时应用没有在运行,会自动启动你的应用并发送定位事件

注意:官方文档中建议只获取when-in-use定位授权

When-in-use#

  1. Info.plist中添加Privacy - Location When In Use Usage Description(或者添加NSLocationWhenInUseUsageDescription键
  2. 创建并配置CLLocationManager对象
  3. 调用CLLocationManager对象的requestWhenInUseAuthorization()方法即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 创建 CLLocationManager 对象
let locationManager = CLLocationManager()
func enableBasicLocationServices() {
locationManager.delegate = self

switch CLLocationManager.authorizationStatus() {
case .notDetermined:
// Request when-in-use authorization initially
locationManager.requestWhenInUseAuthorization()
break

case .restricted, .denied:
// Disable location features
disableMyLocationBasedFeatures()
break

case .authorizedWhenInUse, .authorizedAlways:
// Enable location features
enableMyWhenInUseFeatures()
break
}
}
}

Application Extension#

是 系统和其他APP 交互的扩展,Extension是为APP而存在的,一个APP可以有多个Extension,安装好APP后会自动安装好其自带的Extension。

Extension常见的有三类:

  • Share 分享扩展
  • Today 今日扩展
  • Keyboard 键盘扩展

比如:

(今日扩展: 将应用的最新消息展示给用户)

使用:

首先File -> New,找到 Target 选项,然后在 Extension 区域,选择自己想要创建的Extension 类型(比如 Today Extension),然后一个基础的Extension就创建成功了。

Extension创建成功后,会在项目中创建一个和Extension同名的文件夹,里面是UIViewController等文件,可以通过这些修改和添加Extension的内容。

SourceTree 使用记录 计算机原理简介

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×