解析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这 ...
随机推荐
- Maximum execution time of 30 seconds exceeded in
在执行一次php脚本的时候,遇到了这样的报错,经过c Maximum execution time of 30 seconds exceeded in 翻译过来就是:执行时间超过了30秒最长执行时间: ...
- 如何在MathType输入手写体a
作为强大的数学公式编辑器,MathType中还能设置各种样式,还支持自定义设置,给大家编辑公式提供了更多的便利.那么有用户问:要如何将输入的字母a变为手写体呢?下面就来一起学习. 输入手写体a的步骤如 ...
- 一文搞懂所有Java集合面试题
Java集合 刚刚经历过秋招,看了大量的面经,顺便将常见的Java集合常考知识点总结了一下,并根据被问到的频率大致做了一个标注.一颗星表示知识点需要了解,被问到的频率不高,面试时起码能说个差不多.两颗 ...
- CSS3 学习笔记(上)
一.CSS简介 CSS(Cascading Style Sheets)层叠样式表.其中,样式定义为如何显示HTML元素,它通常储存在样式表,将样式添加到HTML中,能够解决内容与表现分离的问题.由于网 ...
- Matlab 补充知识
1:disp函数和fprintf函数类似 disp(a) 自动输出a变量的值 disp('dadad') 输出一个字符串 Disp(['a','b','ccc']) 三个字符串连在一起 2:matla ...
- 牛客练习赛68 牛牛的无向图 题解(krusal思想)
题目链接 题目大意 要你查询q 次询问,每次询问给出一个 L ,询问\(\sum_{i=1}^n\sum_{j=i+1}^n[d(i,j)<=L]\).其中 [C] 表示当命题 C 为真的时候为 ...
- 【震惊】手把手教你用python做绘图工具(一)
在这篇博客里将为你介绍如何通过numpy和cv2进行结和去创建画布,包括空白画布.白色画布和彩色画布.创建画布是制作绘图工具的前提,有了画布我们就可以在画布上尽情的挥洒自己的艺术细胞. 还在为如何去绘 ...
- gsap基础一[from,to,fromTo]
学了几天基础了,感觉总算有点入了一个门的感觉啦,gasp不难,想想一年前我看着官网跟天文一样,今年真的进步很大,在外网发现学习的新世界, 自己的获取知识和查看api源码的能力也增强了许多,现在国内的气 ...
- 探究 | 如何捕获一个Activity页面上所有的点击行为
前言 最近逛wanAndroid论坛,发现一个有趣的问题:如何捕获一个Activity页面上所有的点击行为. 一起研究下吧,不想看源码的小伙伴可以直接看文末总结- 准备工作 先得罗列出页面上的一些点击 ...
- 【DKNN】Distilling the Knowledge in a Neural Network 第一次提出神经网络的知识蒸馏概念
原文链接 小样本学习与智能前沿 . 在这个公众号后台回复"DKNN",即可获得课件电子资源. 文章已经表明,对于将知识从整体模型或高度正则化的大型模型转换为较小的蒸馏模型,蒸馏非常 ...