解析SwiftUI布局细节,循环轮播+复杂布局
在前面的文章中谈了谈对SwiftUI的基本的认识,以及用我们最常见的TB+NA的方式搭建了一个很基本的场景来帮助认识了一下SwiftUI,具体的文章可以在SwiftUI分类部分查找,这篇我准备在写UI的时候从SwiftUI角度我们具体的应该怎样去做,或者说是用SwiftUI我们该从什么角度去解析一个页面。以及对SwiftUI里面的其中一些细节知识做一下分析总结。
以前我们用UIKit写一个列表页的时候我们的步骤可能是下面这样的:
1、创建视图控制器
2、大概解析一下UI,该创建头部的创建头部视图,该写CollectionViewCell或者TableViewCell的我们会做一个基本的分类,规划一下我们需要几个类型的Cell等等
3、把它们进行一个组装,处理相应的各种代理或者事件回调等等
4、处理数据和视图进行数据对接
可能我们大部分都是这样的一个基本的流程,当然还有些涉及到复杂点的业务我们会从单元测试开始等等的会有些许差异,但SwiftUI的重点是对UI的处理,所以我们的重点就单纯说说UI部分,那大家可以这样想,我们用SwiftUI做的时候该怎样去开始呢,用SwiftUI做的时候流程还会和我们使用UIKit处理的时候还一样吗?在实现的细节方面又会有哪些差距呢?带着这样一个小小的思考我们进行下面的总结。
SwiftUI我们怎么做以及细节分析
前面文章我有提过一点就是View,SwiftUI最大的区别除了声明式的UI之外我自己觉得最大的需要我们理解的点就是View,所有的你能看到的基本单位都成了View,没有了控制器这个概念,这点需要我们转过这个弯,不然容易绕进去。
我们从一个具体的实际页面开始梳理一下用SwiftUI实际写UI的时候一些基本的知识,就如我们Demo中的我的页面举例:
我们首先得认识一下它俩:VStack (竖直) HStack (横向)
它们俩我最能接受的方式就是把他们理解成容器(受Cocos影响),一个纵向 (vertical) 容器,一个横向(horizontal)容器,它们前面的V和H也就是这两单词的首字母,提醒一下你要是记不住的话可以记这一点。H(heng) 剩下的V就是纵向的,所有的iOS方向属性几乎都是这样,加深记忆的一个方式而已,但能保证你以后绝不会再搞混淆! 当然这个横向和纵向也是相对你手机屏幕的是竖直还是水平的,不是绝对的,这个理解一下也容易!由于这两里面的东西几乎都是一样的,我们就针对一个VStack进行具体的分析,先看看它的源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// A view that arranges its children in a vertical line.
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@frozen public struct VStack : View where Content : View {
/// Creates an instance with the given spacing and horizontal alignment.
///
/// - Parameters:
/// - alignment: The guide for aligning the subviews in this stack. It has
/// the same horizontal screen coordinate for all children.
/// - spacing: The distance between adjacent subviews, or `nil` if you
/// want the stack to choose a default distance for each pair of
/// subviews.
/// - content: A view builder that creates the content of this stack.
@inlinable public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content)
/// The type of view representing the body of this view.
///
/// When you create a custom view, Swift infers this type from your
/// implementation of the required `body` property.
public typealias Body = Never
}
我们解释一下它初始化的方法参数:
1、首先我们要认识到VStack是一个结构体
2、alignment: HorizontalAlignment 我们可以看到它有一个默认的居中对齐值,它控制的就是容器里面的子视图的对齐方式,这个可以自己体验下。
3、spacing: CGFloat? = nil 这是个可选类型的参数,它控制的是容器里面子视图之间的间距。
4、@ViewBuilder content: () -> Content 这是一个很有意思的东西,很值得我们仔细的说说,因为我们在后面会经常使用到这个@ViewBuilder,要暂时不管它那这个参数就只剩下content: () -> Content部分,这个闭包相信都能理解,一个比较简单的闭包,对Content 的约束都在声明VStack的时候说的比较清楚。那他和普通的闭包区别也就在@ViewBuilder上,我们就把重点转移到对@ViewBuilder的理解上了。
下面是关于ViewBuilder的定义:
1
2
3
4
5
6
7
8
9
10
11
12
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@_functionBuilder public struct ViewBuilder {
/// Builds an empty view from a block containing no statements.
public static func buildBlock() -> EmptyView
/// Passes a single view written as a child view through unmodified.
///
/// An example of a single view written as a child view is
/// `{ Text("Hello") }`.
public static func buildBlock<Content>(_ content: Content) -> Content where Content : View
}
这里面最值得注意点就是这个 @_functionBuilder 修饰符,_functionBuilder实质上能对函数进行一次处理,具体的我们可以看看下面的例子:
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
/// 用_functionBuilder修饰TestBuilder
/// 就像用_functionBuilder修饰了ViewBuilder一样
/// 我们就用TestBuilder看看它的实际效果
@_functionBuilder struct TestBuilder {
/// String... 参数 数量可变,你可以传入任意数量的参数
/// - Parameter items: items description
/// - Returns: descr