解析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进行具体的分析,先看看它的源码:
/// 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<Content> : 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的定义:
@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实质上能对函数进行一次处理,具体的我们可以看看下面的例子:
/// 用_functionBuilder修饰TestBuilder
/// 就像用_functionBuilder修饰了ViewBuilder一样
/// 我们就用TestBuilder看看它的实际效果
@_functionBuilder struct TestBuilder { /// String... 参数 数量可变,你可以传入任意数量的参数
/// - Parameter items: items description
/// - Returns: description
static func buildBlock(_ items: String...) -> [String] { return items
}
} /// 然后我们有这样一个方法
/// @TestBuilder模拟@ViewBuilder
/// - Parameter content: content description
func testBuilder(@TestBuilder _ content:() -> [String]){ print(content())
} /// 然后我们调用的时候
self.testBuilder {
"1"
"2"
"3"
"4"
}
随后的打印结果就是 ["1", "2", "3", "4"]
那下面我们理解一下这个例子,在整个显式的调用中,我们似乎是没有用到buildBlock函数的,那要是我们在定义TestBuilder的时候要是不定义buildBlock是不是也可以,当然是不行的,这个在具体的例子中可以试试,在调用的时候就会报错,告诉你没有buildBlock函数,这个函数的具体的作用,我们在对它的注释中能找到答案。
Builds an empty view from a block containing no statements.
可以简单翻译成-从不包含任何语句的块中生成空视图。那我们就明白了,它的作用感觉类似初始化的样子,要没有它就显然是不行的。
还有上面我们调用的时候为什么要写成列的形式,能不能写成"1" "2" "3" "4" 这种形式呢?肯定是不行的,这个你也可以自己尝试一下。
我们要再往深入挖掘一下,因为后面还有个问题需要我们注意,在ViewBuilder的最后一个Extension中的buildBlock的代码是这样的
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder { public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)> where C0 : View, C1 : View, C2 : View, C3 : View, C4 : View, C5 : View, C6 : View, C7 : View, C8 : View, C9 : View
}
由于它里面最多能接收10个View,所以在我们常见的Stack中也就最多能接收到是个子视图,这点需要我们注意,不要到时候写的超过十个了然后一头雾水不知道是啥错误。接着我们肯定会疑惑,那就没有办法写是个以上的子视图了吗?答案当然是不是,肯定可以,具体的可以通过Group或者ForEach来实现,我们就不在往下深究了,这个问题可以自己看看!
不知道看到这大家对ViewBuilder应该有了一些认识了吧,我会在后面的参考文章中具体的在给几个例子地址,大家可以再仔细的看看,我们就看我们Demo中的一个使用,他具体的一个场景是这样的,在登录页面,我想加一个点击除了输入框之外收起键盘的操作,我们具体的实现方法其实就是在最底层添加了一个View,然后在它上面添加了点击的手势,具体得我们看看代码:
/// 定义一个常见的背景View
struct Background<Content: View>: View { private var content: Content init(@ViewBuilder content: @escaping () -> Content) { self.content = content()
} var body: some View { Color.white
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.overlay(content)
}
} /// UIApplication 的扩展
extension UIApplication { func endEditing() { sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
} /// 具体的使用就是下面这样,这样就达到了我们的目的,中间的代码我隐藏起来了,代码在BaseLoginView中可以查看到
///
var body: some View { Background {
/// 里面具体的视图内容
}.onTapGesture { self.endEditing()
}
}
这样我相信就基本把这个比较重要的@ViewBuilder给说清楚了,这个VStack或者HStack也就应该慢慢的再理解了。
理解了之后我们也就能总结一下我们用SwiftUI写UI时候的一个简单逻辑
1、创建好你需要的SwiftUI文件
2、规划好你的视图层级,比如说是不是嵌套的NavigationView里面,然后开始规划Stack,看具体的是需要规划成几个你需要的Stack
3、再往下就是里面具体的各种控件View了,我打算把他们放到下一篇再做一个具体的总结
下一篇我们就说说SwiftUI关于View跳转的方式,以及传值注意点、View位置设置、大小缩放等等的属性的使用。
参考文章:
解析SwiftUI布局细节(一)的更多相关文章
- 解析SwiftUI布局细节(二)循环轮播+复杂布局
前言 上一篇我们总结的主要是VStack里面的东西,由他延伸到 @ViewBuilder, 接着我们上一篇总结的我们这篇内容主要说的是下面的几点,在这些东西说完后我准备解析一下苹果在SiwftUI文档 ...
- 解析SwiftUI布局细节(三)地图的基本操作
前言 前面的几篇文章总结了怎样用 SwiftUI 搭建基本框架时候的一些注意点(和这篇文章在相同的分类里面,有需要了可以点进去看看),这篇文章要总结的东西是用地图数据处理结合来说的,通过这篇文章我们能 ...
- SpringMVC解析5-DispatcherServlet逻辑细节
MultipartContent类型的request处理 对于请求的处理,spring首先考虑的是对于Multipart的处理,如果是MultipartContent类型的request,则转换req ...
- FrameWork内核解析之布局加载与资源系统(三)
阿里P7Android高级架构进阶视频免费学习请点击:https://space.bilibili.com/474380680本篇文章将继续从以下两个内容来介绍布局加载与资源系统: [ LayoutM ...
- SpringRMI解析3-RmiServiceExporter逻辑细节
在发布RMI服务的流程中,有几个步骤可能是我们比较关心的. 获取registry 由于底层的封装,获取Registry实例是非常简单的,只需要使用一个函数LocateRegistry.createRe ...
- div+css布局细节问题
cursor: pointer;在chrome里支持,hand不支持
- Android中measure过程、WRAP_CONTENT详解以及 xml布局文件解析流程浅析
转自:http://www.uml.org.cn/mobiledev/201211221.asp 今天,我着重讲解下如下三个内容: measure过程 WRAP_CONTENT.MATCH_PAREN ...
- html css 布局小细节
学了两个月的html和css每天都重复一样的生活,敲着大同小异的代码,这样的生活枯燥无味.我腻了,我也累了!小米首页算是我写的第三个静态页面,写了好久,很多细节都把握不好,下面的这个简单的布局细节是我 ...
- IOS Widget(3):SwiftUI开发小组件布局入门
引言 经过上一篇文章,我们已经可以在桌面上展示出一个小组件出来了,你肯定想小试牛刀,动手改一改,那我们就从改小组件的布局做起吧.本文不会讲解Swift语法,如果是熟悉Flutter,Kotlin这 ...
随机推荐
- 你了解ABBYY FineReader 14么?
有没有一款是能够同时处理纸质文档和个类型PDF的一站式解决方案?答案是肯定的,ABBYY FineReader 14集合了强大的光学字符识别(OCR)以及 PDF 查看和编辑功能.不仅能够高效识别图片 ...
- 使用思维导图MindManager能否增强记忆?
学生时代,每当面对冗杂的需要背诵的课业时,有很多人都会发出"这么多内容怎么背啊"."我讨厌死记硬背"."昨天背完今天就忘了"的呐喊.那么,如 ...
- 思维导图软件iMindMap幻灯片设置功能介绍
我们运用iMindMap演示来播放幻灯片时,有没想过,我怎么改动幻灯片的播放时长,怎么设置它的播放速度这些基本设置呢.下面,本文就告诉你,我们该去哪里修改这些iMindMap幻灯片设置: 我们打开iM ...
- 借助Mac自带的强大的搜索功能,如何快速搜索打开Tuxera Disk Manager
现在很多小伙伴们在遇到Mac读写NTFS格式硬盘问题的时候,都会选择使用Tuxera NTFS这个磁盘读写工具.因为这款读写工具不仅可以帮助我们进行读写工作,还具有一个磁盘管理工具Disk Manag ...
- ceph 集群快速部署
1.三台Centos7的主机 [root@ceph-1 ~]# cat /etc/redhat-release CentOS Linux release 7.2.1511 (Core) 2.主机 ...
- 【ACwing 100】InDec序列——差分
(题面来自AcWing) 给定一个长度为 n 的数列 a1,a2,-,an,每次可以选择一个区间 [l,r],使下标在这个区间内的数都加一或者都减一. 求至少需要多少次操作才能使数列中的所有数都一样, ...
- CountDownLatch、CyclicBarrier、Semaphore、Exchanger 的详细解析
本文主要介绍和对比我们常用的几种并发工具类,主要涉及 CountDownLatch . CyclicBarrier . Semaphore . Exchanger 相关的内容,如果对多线程相关内容不熟 ...
- 【证书】curl 和 java 请求报证书错误
1. 说明: 以下:例子的域名因为工作环境的问题,被我拿自己的博客域名替代了,所以无法进行模拟测试,请珍重,哈哈! 2. 环境: centos:7.5 java jdk:1.8.0_74 3. cur ...
- Centos7配置阿里epel源|yum源
这一步非常重要.重要.重要.在这解释一下源的概念,打个比方如果手机想获取一个软件,可以选择很多途径,如华为的华为商店,小米的应用商店,苹果的App store,源就相当于各种手机获取软件的商店.因为国 ...
- drf的权限扩充
drf框架为我们提供了基本的权限验证.主要包括三种验证 1.AllowAny 所有用户 2.IsAuthenticated 验证过的用户 3.IsAdminUser 超级管理员 这些权限人员不一定满足 ...