跃迁引擎

空気を読んだ雨降らないでよ

iOS Research & Development


iPhone/iPad AutoLayout 框架 Kirov 架构设计

架构设计

Kirov 的能力非常强大,可以让工程师以最快速度还原设计稿,一次编码同时适配 iPhone/iPad 的不同比例设计,同时它非常的轻量化,支持绝对布局与相对布局以及自动适配子视图。

核心代码

设计基准

整体布局的核心来源于通用抽象出来的设计基准类KirovDesignBasic,其负责承接外部使用者填充进来的设计稿基准值:

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
@objc(KirovDesignBasic)
@objcMembers public class KirovDesignBasic: NSObject {
/// 设计稿基准尺寸,默认 .zero
@objc public var frame: CGRect = .zero
/// 设计稿基准顶边距,默认 CGFloat.leastNonzeroMagnitude
@objc public var top: CGFloat = CGFloat.leastNonzeroMagnitude
/// 设计稿基准底边距,默认 CGFloat.leastNonzeroMagnitude
@objc public var bottom: CGFloat = CGFloat.leastNonzeroMagnitude
/// 设计稿基准左边距,默认 CGFloat.leastNonzeroMagnitude
@objc public var left: CGFloat = CGFloat.leastNonzeroMagnitude
/// 设计稿基准右边距,默认 CGFloat.leastNonzeroMagnitude
@objc public var right: CGFloat = CGFloat.leastNonzeroMagnitude
/// 设计稿基准宽度,默认 CGFloat.leastNonzeroMagnitude
@objc public var width: CGFloat = CGFloat.leastNonzeroMagnitude
/// 设计稿基准高度,默认 CGFloat.leastNonzeroMagnitude
@objc public var height: CGFloat = CGFloat.leastNonzeroMagnitude
/// 设计稿基准字体,默认 UIFont.systemFont(ofSize: 17)
@objc public var font: UIFont = UIFont.systemFont(ofSize: 17)
/// 设计稿基准字体,默认 0
@objc public var lineHeight: CGFloat = 0.0

override init() {
super.init()
}
}

KirovDesignBasic 之上,有分别对应着 iPhone 和 iPad 设计基准的KiroviPhoneDesignBasicKiroviPadDesignBasic,这两个基准类的作用是负责对设计稿的填充值进行分流:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// iPad 设计稿基准
@objc(KiroviPadDesignBasic)
@objcMembers public class KiroviPadDesignBasic: KirovDesignBasic {
override init() {
super.init()
}
}

/// iPhone 设计稿基准
@objc(KiroviPhoneDesignBasic)
@objcMembers public class KiroviPhoneDesignBasic: KirovDesignBasic {
override init() {
super.init()
}
}

布局控制

具体的布局由 Kirov 来负责控制,每一个Kirov 对象的内部都分别持有了一个KiroviPhoneDesignBasicKiroviPadDesignBasic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/// iPhone & iPad 自动适配布局属性,基于 frame
@objc(Kirov)
@objcMembers public class Kirov: NSObject {
/// iPad 的设计稿基准
@objc public var iPadDesign: KiroviPadDesignBasic!
/// iPhone 的设计稿基准
@objc public var iPhoneDesign: KiroviPhoneDesignBasic!
/// 自动适配子视图,默认 fasle
/// - Note: 当 autoAdaptSubView = true,自动适配所有子视图
@objc public var autoAdaptSubView: Bool = false
/// 自动适配富文本,默认 fasle
/// - Note: 当 autoAdaptAttributedString = true时,自动适配富文本大小
@objc public var autoAdaptAttributedString: Bool = false

override init() {
super.init()
self.iPadDesign = KiroviPadDesignBasic()
self.iPhoneDesign = KiroviPhoneDesignBasic()
}
}

布局实现

自动适配

布局的实现依托于针对基础的 UIView 进行扩展,以及一些特性控件(UITextView/UITextField/UILabel/UIButton…)的特性支持。

对每一个潜在的布局对象,都实现了一个扩展方法configureAutoAdaptLayoutWithBlock其允许你对该布局对象进行 iPhone / iPad 的同时适配,并且自动识别采用绝对布局/相对布局、是否需要同时适配子视图等等:

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
/// iPhone & iPad 自动适配布局,基于 frame
@objc(configureAutoAdaptLayoutWithBlock:)
public func configureAutoAdaptLayoutWithBlock(_ block: (_ kirov: Kirov) -> Void) {
block(self.kirov)

if self.kirov.iPadDesign.frame.size == .zero && self.kirov.iPhoneDesign.frame.size == .zero {
/// - Note: 如果没有设置 frame 尝试进行绝对布局
kirov_setAbsoluteLayout()

/// - Note: 自动适配子视图
kirov_autoAdaptSubviews = self.kirov.autoAdaptSubView
guard kirov_autoAdaptSubviews else { return }
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.kirov_adaptSubviews(root: self)
}

if self.frame == .zero {
assertionFailure("缺少 iPad 和 iPhone 基准尺寸,请补充")
} else {
return
}

} else if self.kirov.iPadDesign.frame.size == .zero {
assertionFailure("缺少 iPad 的基准尺寸,请补充")
} else if self.kirov.iPhoneDesign.frame.size == .zero {
assertionFailure("缺少 iPhone 的基准尺寸,请补充")
}

/// - Note: 设置 frame
kirov_setFrames(kirov_ipadFrame: self.kirov.iPadDesign.frame, kirov_iphoneFrame: self.kirov.iPhoneDesign.frame)

/// - Note: 自动适配子视图
kirov_autoAdaptSubviews = self.kirov.autoAdaptSubView
guard kirov_autoAdaptSubviews else { return }
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.kirov_adaptSubviews(root: self)
}
}

子视图的适配

如果开启了子视图的适配开关,将以当前视图为根节点,其节点下的所有子视图进行递归适配

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
private extension UIView {
/// 适配子视图
/// - Parameters root 父视图
private func kirov_adaptSubviews(root: UIView) {
for subview in root.subviews {
// - Note: 如果CALayer的position属性包含了NaN,说明视图没有准备好,结束循环
if subview.frame == .zero {
break
}
let originFrame = subview.frame;
// - Note: 将所有子视图都标记为自动适配
subview.kirov.autoAdaptSubView = true
/// - Note: setFrames (kirov_ipadFrame:kirov_iphoneFrame: ) 内部将会根据当前设备类型自动计算出合适的适配比例
subview.kirov_setFrames(kirov_ipadFrame: originFrame, kirov_iphoneFrame: originFrame)
/// - Note: 自动适配子视图相关属性
if let label = subview as? UILabel {
label.kirov_autoAdapt()
}
if let textFiled = subview as? UITextField {
textFiled.kirov_autoAdapt()
}
if let textView = subview as? UITextView {
textView.kirov_autoAdapt()
}
if let button = subview as? UIButton {
button.kirov_autoAdapt()
}
subview.kirov_adaptSubviews(root: subview)
}
}
}

绝对布局的相邻坐标检测

针对绝对布局,需要考虑碰撞检测,判断相邻点是否可以进行布局

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
77
78
/// 当前 view 判定周边路径的方向
private enum KirovDirection {
/// 左方
case left
/// 上方
case top
/// 右方
case right
/// 下方
case bottom
}

/// 获取指定方向上相邻最近视图点的坐标
/// - Parameters direction 方向
private func kirov_nearestViewDistanceInDirection(_ direction: KirovDirection) -> CGFloat {
guard let superview = superview else {
return -1
}
var nearestViewDistance: CGFloat = CGFloat.leastNonzeroMagnitude
let point = convert(CGPoint.zero, to: superview)

switch direction {
case .left:
for subview in superview.subviews {
if subview != self {
let subviewRight = subview.frame.origin.x + subview.frame.width
if subviewRight <= point.x {
let distance = point.x - subviewRight
if distance < nearestViewDistance {
nearestViewDistance = distance
}
}
}
}
case .top:
for subview in superview.subviews {
if subview != self {
let subviewBottom = subview.frame.origin.y + subview.frame.height
if subviewBottom <= point.y {
let distance = point.y - subviewBottom
if distance < nearestViewDistance {
nearestViewDistance = distance
}
}
}
}
case .right:
for subview in superview.subviews {
if subview != self {
let subviewLeft = subview.frame.origin.x
if subviewLeft >= point.x {
let distance = subviewLeft - point.x
if distance < nearestViewDistance {
nearestViewDistance = distance
}
}
}
}
case .bottom:
for subview in superview.subviews {
if subview != self {
let subviewTop = subview.frame.origin.y
if subviewTop >= point.y {
let distance = subviewTop - point.y
if distance < nearestViewDistance {
nearestViewDistance = distance
}
}
}
}
}

if nearestViewDistance == CGFloat.leastNonzeroMagnitude {
nearestViewDistance = -1
}

return nearestViewDistance
}
最近的文章

Rust - 值的所有权

值的生杀大权究竟在谁手上?进入内存管理以后,我们就会一起研究 Rust 学习过程中最难啃的硬骨头:所有权和生命周期。为什么要从这个知识点开始呢?因为,所有权和生命周期是 Rust 和其它编程语言的主要区别,也是 Rust 其它知识点的基础。 其实,所有权和生命周期之所以这么难学明白,除了其与众不同的 …

, , 开始阅读
更早的文章

从零开始的 Rust 修炼生活

前言在当今这个快速发展的软件开发领域,掌握一门高效且安全的编程语言显得尤为重要。Rust,作为一门集性能与安全性于一身的现代系统编程语言,自2010年首次公开以来,便以其独特的所有权系统和零成本抽象理念吸引了大量开发者的关注。本系列文章旨在为初学者提供一条系统学习Rust的道路,从基础语法到高级特 …

, 开始阅读
comments powered by Disqus