前言


在前面的文章中谈了谈对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. /// A view that arranges its children in a vertical line.
  2. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  3. @frozen public struct VStack<Content> : View where Content : View {
  4.  
  5. /// Creates an instance with the given spacing and horizontal alignment.
  6. ///
  7. /// - Parameters:
  8. /// - alignment: The guide for aligning the subviews in this stack. It has
  9. /// the same horizontal screen coordinate for all children.
  10. /// - spacing: The distance between adjacent subviews, or `nil` if you
  11. /// want the stack to choose a default distance for each pair of
  12. /// subviews.
  13. /// - content: A view builder that creates the content of this stack.
  14. @inlinable public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content)
  15.  
  16. /// The type of view representing the body of this view.
  17. ///
  18. /// When you create a custom view, Swift infers this type from your
  19. /// implementation of the required `body` property.
  20. public typealias Body = Never
  21. }

我们解释一下它初始化的方法参数:

1、首先我们要认识到VStack是一个结构体

2、alignment: HorizontalAlignment 我们可以看到它有一个默认的居中对齐值,它控制的就是容器里面的子视图的对齐方式,这个可以自己体验下。

3、spacing: CGFloat? = nil 这是个可选类型的参数,它控制的是容器里面子视图之间的间距。

4、@ViewBuilder content: () -> Content  这是一个很有意思的东西,很值得我们仔细的说说,因为我们在后面会经常使用到这个@ViewBuilder,要暂时不管它那这个参数就只剩下content: () -> Content部分,这个闭包相信都能理解,一个比较简单的闭包,对Content 的约束都在声明VStack的时候说的比较清楚。那他和普通的闭包区别也就在@ViewBuilder上,我们就把重点转移到对@ViewBuilder的理解上了。

下面是关于ViewBuilder的定义:

  1. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  2. @_functionBuilder public struct ViewBuilder {
  3.  
  4. /// Builds an empty view from a block containing no statements.
  5. public static func buildBlock() -> EmptyView
  6.  
  7. /// Passes a single view written as a child view through unmodified.
  8. ///
  9. /// An example of a single view written as a child view is
  10. /// `{ Text("Hello") }`.
  11. public static func buildBlock<Content>(_ content: Content) -> Content where Content : View
  12. }

这里面最值得注意点就是这个 @_functionBuilder 修饰符,_functionBuilder实质上能对函数进行一次处理,具体的我们可以看看下面的例子:

  1. /// 用_functionBuilder修饰TestBuilder
  2. /// 就像用_functionBuilder修饰了ViewBuilder一样
  3. /// 我们就用TestBuilder看看它的实际效果
  4. @_functionBuilder struct TestBuilder {
  5.  
  6. /// String... 参数 数量可变,你可以传入任意数量的参数
  7. /// - Parameter items: items description
  8. /// - Returns: description
  9. static func buildBlock(_ items: String...) -> [String] {
  10.  
  11. return items
  12. }
  13. }
  14.  
  15. /// 然后我们有这样一个方法
  16. /// @TestBuilder模拟@ViewBuilder
  17. /// - Parameter content: content description
  18. func testBuilder(@TestBuilder _ content:() -> [String]){
  19.  
  20. print(content())
  21. }
  22.  
  23. /// 然后我们调用的时候
  24. self.testBuilder {
  25. "1"
  26. "2"
  27. "3"
  28. "4"
  29. }

随后的打印结果就是 ["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的代码是这样的

  1. @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
  2. extension ViewBuilder {
  3.  
  4. 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
  5. }

由于它里面最多能接收10个View,所以在我们常见的Stack中也就最多能接收到是个子视图,这点需要我们注意,不要到时候写的超过十个了然后一头雾水不知道是啥错误。接着我们肯定会疑惑,那就没有办法写是个以上的子视图了吗?答案当然是不是,肯定可以,具体的可以通过Group或者ForEach来实现,我们就不在往下深究了,这个问题可以自己看看!

不知道看到这大家对ViewBuilder应该有了一些认识了吧,我会在后面的参考文章中具体的在给几个例子地址,大家可以再仔细的看看,我们就看我们Demo中的一个使用,他具体的一个场景是这样的,在登录页面,我想加一个点击除了输入框之外收起键盘的操作,我们具体的实现方法其实就是在最底层添加了一个View,然后在它上面添加了点击的手势,具体得我们看看代码:

  1. /// 定义一个常见的背景View
  2. struct Background<Content: View>: View {
  3.  
  4. private var content: Content
  5.  
  6. init(@ViewBuilder content: @escaping () -> Content) {
  7.  
  8. self.content = content()
  9. }
  10.  
  11. var body: some View {
  12.  
  13. Color.white
  14. .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
  15. .overlay(content)
  16. }
  17. }
  18.  
  19. /// UIApplication 的扩展
  20. extension UIApplication {
  21.  
  22. func endEditing() {
  23.  
  24. sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
  25. }
  26. }
  27.  
  28. /// 具体的使用就是下面这样,这样就达到了我们的目的,中间的代码我隐藏起来了,代码在BaseLoginView中可以查看到
  29. ///
  30. var body: some View {
  31.  
  32. Background {
  33. /// 里面具体的视图内容
  34. }.onTapGesture {
  35.  
  36. self.endEditing()
  37. }
  38. }

这样我相信就基本把这个比较重要的@ViewBuilder给说清楚了,这个VStack或者HStack也就应该慢慢的再理解了。

理解了之后我们也就能总结一下我们用SwiftUI写UI时候的一个简单逻辑

1、创建好你需要的SwiftUI文件

2、规划好你的视图层级,比如说是不是嵌套的NavigationView里面,然后开始规划Stack,看具体的是需要规划成几个你需要的Stack

3、再往下就是里面具体的各种控件View了,我打算把他们放到下一篇再做一个具体的总结

下一篇我们就说说SwiftUI关于View跳转的方式,以及传值注意点、View位置设置、大小缩放等等的属性的使用。

参考文章:

SwiftUI之ViewModifier详解

SwiftUI中的@ViewBuilder

项目地址

解析SwiftUI布局细节(一)的更多相关文章

  1. 解析SwiftUI布局细节(二)循环轮播+复杂布局

    前言 上一篇我们总结的主要是VStack里面的东西,由他延伸到 @ViewBuilder, 接着我们上一篇总结的我们这篇内容主要说的是下面的几点,在这些东西说完后我准备解析一下苹果在SiwftUI文档 ...

  2. 解析SwiftUI布局细节(三)地图的基本操作

    前言 前面的几篇文章总结了怎样用 SwiftUI 搭建基本框架时候的一些注意点(和这篇文章在相同的分类里面,有需要了可以点进去看看),这篇文章要总结的东西是用地图数据处理结合来说的,通过这篇文章我们能 ...

  3. SpringMVC解析5-DispatcherServlet逻辑细节

    MultipartContent类型的request处理 对于请求的处理,spring首先考虑的是对于Multipart的处理,如果是MultipartContent类型的request,则转换req ...

  4. FrameWork内核解析之布局加载与资源系统(三)

    阿里P7Android高级架构进阶视频免费学习请点击:https://space.bilibili.com/474380680本篇文章将继续从以下两个内容来介绍布局加载与资源系统: [ LayoutM ...

  5. SpringRMI解析3-RmiServiceExporter逻辑细节

    在发布RMI服务的流程中,有几个步骤可能是我们比较关心的. 获取registry 由于底层的封装,获取Registry实例是非常简单的,只需要使用一个函数LocateRegistry.createRe ...

  6. div+css布局细节问题

    cursor: pointer;在chrome里支持,hand不支持

  7. Android中measure过程、WRAP_CONTENT详解以及 xml布局文件解析流程浅析

    转自:http://www.uml.org.cn/mobiledev/201211221.asp 今天,我着重讲解下如下三个内容: measure过程 WRAP_CONTENT.MATCH_PAREN ...

  8. html css 布局小细节

    学了两个月的html和css每天都重复一样的生活,敲着大同小异的代码,这样的生活枯燥无味.我腻了,我也累了!小米首页算是我写的第三个静态页面,写了好久,很多细节都把握不好,下面的这个简单的布局细节是我 ...

  9. IOS Widget(3):SwiftUI开发小组件布局入门

    引言   经过上一篇文章,我们已经可以在桌面上展示出一个小组件出来了,你肯定想小试牛刀,动手改一改,那我们就从改小组件的布局做起吧.本文不会讲解Swift语法,如果是熟悉Flutter,Kotlin这 ...

随机推荐

  1. 如何修改IDM下载器的临时文件夹位置

    所有的应用程序在下载时,都会有一些默认的选项.比如产生的临时文件存放在C盘目录下,或者定期自动更新等设置.那么当我们的计算机上安装了很多程序之后,C盘的空间就会渐渐地变小了,从而有了空间不足等等情况, ...

  2. 怎么用MindManager制作议论文思维导图

    大家都写过作文吧,做小学到高考到大学,这是谁也摆脱不了的,但是大家写作文会提前把自己的思路整理出来吗?让自己行文更为顺畅,作文更为流利吗?特别是关于议论文,一直是高考写作的一个重点篇目,写好议论文,就 ...

  3. MindManager中主题间距/线条粗细的灵活调整

    在MindManager中,主题和线条是思维导图的基本元素,只有通过它们才能将要表达的思想呈现.并联系起来.因此,关于它们的属性设置就会多一点,如颜色.宽度.位置等.而调整主题之间的距离及线条的粗细, ...

  4. C语言讲义——常量(constant)

    变量可以反复赋值:常量只能在定义时赋值,此后不得更改. 常量的定义需要加关键字const.如: #include <stdio.h> main() { const double PI=3. ...

  5. sentinel降级误解

    public void initDegradeRule(){ List<DegradeRule> rules=new ArrayList<>(); DegradeRule ru ...

  6. Vue.js 桌面端自定义滚动条组件|vue美化滚动条VScroll

    基于vue.js开发的小巧PC端自定义滚动条组件VScroll. 前段时间有给大家分享一个vue桌面端弹框组件,今天再分享最近开发的一个vue pc端自定义滚动条组件. vscroll 一款基于vue ...

  7. 浅谈代理模式与java中的动态代理

    代理模式的定义: 代理模式是一个使用律非常高的模式,定义如下: 为其他对象提供一种代理,以控制对这个对象的访问. 类图: 简单的静态代理: public interface IRunner{ //这是 ...

  8. LeetCode 030 Substring with Concatenation of All Words

    题目要求:Substring with Concatenation of All Words You are given a string, S, and a list of words, L, th ...

  9. Alpha冲刺-第八次冲刺笔记

    Alpha冲刺-冲刺笔记 这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzzcxy/2018SE2 这个作业要求在哪里 https://edu.cnblogs. ...

  10. bootstrap 按钮颜色属性

    bootstrap 按钮颜色属性有几种