SwiftUI之深入解析Alignment Guides的超实用实战教程

一、Alignment Guide 简介

  • Alignment guides 是一个强大的布局工具,但通常未被充分利用。在很多情况下,它们可以帮助我们避免更复杂的选项,比如锚点偏好。如下所示,对对齐的更改也可以自动(并且容易地)动画化:

请添加图片描述

  • 如果您曾经尝试过使用 alignment guides,那么可能会对结果感到困惑。它们倾向于做期望它们做的事,直到它们不做。在花了一些时间测试对齐指南的限制后,可以得出结论,它们确实有效。然而,我们对它们的期望是困惑的。这种混淆来自于没有意识到有一整套隐式 alignment guides 在起作用,当忽视它们时,事情就不会如我们所愿,容器中的每个 View 都有 alignment guides。
  • alignment guides 基本上是一个数值,它在视图中设置一个点,该点决定了该视图相对于其他视图的位置。注意,对齐可以是垂直的,也可以是水平的。假设有三个视图(A, B 和 C),它们的水平导线分别为0(零)、20 和 10,视图将被定位,使视图 A 的起始点(从起始点算零)与视图 B 的第 20 个水平点和视图 C 的第 10 个水平点对齐:

在这里插入图片描述

  • 同样的概念,适用于垂直对齐:

在这里插入图片描述

  • 从这些例子中,可以看到:垂直容器(VStack)需要水平对齐,而水平容器(HStack)需要垂直对齐。乍一看可能很奇怪,但如果仔细看这些图片,就会意识到这完全有道理。
  • 首先要说明的是,可以指定像 .leading 开头,然而,在每种情况下,它都有完全不同的含义:

在这里插入图片描述

  • 分析说明:
    • Container Alignment:规定哪些 alignmentGuides() 可以忽略,哪些可以不忽略,但是它还为所有包含的视图定义了隐式 alignment guides,这些视图没有 explicit guide;
    • Alignment Guide:除非此值与 Container Alignment 参数匹配,否则在布局期间将忽略此指南;
    • Implicit Alignment Value:它是一个数值,指示它所修改的视图的指南的位置,有一些方便的预设值,如 d.width、d[.leading]、d[.center] 等,但最终返回的是一个数值,这是与给定指南关联的默认(隐式)值;
    • Explicit Alignment Value:它是一个数值,指示它所修改的视图的指南的位置,这是一个显式值(即由以编程方式定义的值);
    • Frame Alignment:指示容器内所有包含的视图如何对齐(作为一个唯一的组);
    • Text Alignment:对于多行文本视图,它指定文本行如何在文本视图内对齐。

二、Implicit Alignments 和 Explicit Alignments

  • 容器中的每个视图都有对齐方式,之所以强调它,是因为它是需要记住的最重要的概念之一。当通过调用 .alignmentGuide() 定义对齐方式时,对齐方式是显式的。当不指定它时,它是隐式的,隐式对齐的值将由容器视图中的对齐参数提供(例如 VStack(alignment: .leading))。
  • 你可能想知道,如果不为 VStack、HStack 或 ZStack 指定对齐参数会发生什么?它们确实有违约,所有情况下都是 .center。

三、ViewDimensions

  • 到目前为止,已经看到,在. alignmentguide() 修饰符的 computeValue 闭包中将对齐指南指定为 CGFloat,它是一个必须返回的任意数。如果没有数据的话,计算这样的数字是很有挑战性的。来看看 .alignmentGuide() 方法的声明:
func alignmentGuide(_ g: HorizontalAlignment, computeValue: @escaping (ViewDimensions) -> CGFloat) -> some View
func alignmentGuide(_ g: VerticalAlignment, computeValue: @escaping (ViewDimensions) -> CGFloat) -> some View
  • 这是一个重载方法,有两个版本,一个用于水平参考线,一个用于垂直参考线。但是这个方法最有趣的地方是 computeValue 闭包为我们提供了一个 ViewDimensions 类型的参数。这个类型是一个结构体,包含一些关于创建对齐指南的视图的有用信息:
public struct ViewDimensions {
    public var width: CGFloat { get } // The view's width
    public var height: CGFloat { get } // The view's height

    public subscript(guide: HorizontalAlignment) -> CGFloat { get }
    public subscript(guide: VerticalAlignment) -> CGFloat { get }
    public subscript(explicit guide: HorizontalAlignment) -> CGFloat? { get }
    public subscript(explicit guide: VerticalAlignment) -> CGFloat? { get }
}
  • 最容易算出的是宽度和高度,它们控制着正在处理的视图的宽度和高度。但是有那些令人费解的下标方法,它们接收一个 HorizontalAlignment 或 VerticalAlignment 值作为它们的索引,来看看如何访问它们:
Text("Hello")
    .alignmentGuide(HorizontalAlignment.leading, computeValue: { d in                        
        return d[HorizontalAlignment.leading] + d.width / 3.0 - d[explicit: VerticalAlignment.top]
    })
  • 当探索 HorizontalAlignment, VerticalAlignment 和 align 类型时,将会看到这些值是什么。

四、Alignment 的歧义使用

  • 大多数情况下,不需要像这样指定对齐方式的全名:
d[HorizontalAlignment.leading] + d.width / 3.0 - d[explicit: VerticalAlignment.top]
  • 编译器可以推断出是在谈论水平对齐还是垂直对齐,因此可以简单地使用:
d[.leading] + d.width / 3.0 - d[explicit: .top]
  • 然而,在某些情况下,编译器可能会抱怨对对齐的模糊使用,特别是在使用 .center 值时,这是因为 .center 值有两种类型:HorizontalAlignment.center 和 VerticalAlignment.center。当编译器无法推断正在使用的类型时,可能需要指定全名。

五、HorizontalAlignment 和 VerticalAlignment 的类型

  • 当一个 ViewDimension 值被一个类型为 HorizontalAlignment 的索引访问时,可以获得视图的前缘,视图的中心或视图的后缘:
extension HorizontalAlignment {
    public static let leading: HorizontalAlignment
    public static let center: HorizontalAlignment
    public static let trailing: HorizontalAlignment
}
  • 索引可以通过两种方式指定:
d[.trailing]
d[explicit: .trailing]
  • 第一个是隐式值,这意味着给定对齐类型的默认值。对于 .center 的 .leading, width/2.0 通常为 0,对于 .trailing,它是宽度。
  • VerticalAlignment 就像 HorizontalAlignment,但它有更多的兴趣点,除了 .top、.center 和 .bottom,也可以得到 firstTextBaseline(最上面的文本基线)和 lastTextBaseline(最下面的文本基线)。
extension VerticalAlignment {
    public static let top: VerticalAlignment
    public static let center: VerticalAlignment
    public static let bottom: VerticalAlignment
    public static let firstTextBaseline: VerticalAlignment
    public static let lastTextBaseline: VerticalAlignment
}
  • 如前所述,ZStack 容器需要一种方法来指定两种对齐方式(一个 horizontal 对齐,一个 vertical 对齐)。为此,有了 Alignment 类型,它结合了这两者。例如,如果想要顶部垂直对齐和顶部水平对齐,有两个选择:
ZStack(alignment: Alignment(horizontal: .leading, vertical: .top)) { ... }
ZStack(alignment: .topLeading) { ... }
  • 当开始使用自定义对齐指南时,第一个选项将是有意义的。

六、Container Alignment 和 Frame Alignment

① Container Alignment

  • 容器视图(VStack、HStack 或 ZStack) 中的对齐参数有两个效果:
    • 确定哪些 .alignmentguides() 与布局相关,所有与容器参数中的对齐方式不同的对齐指南将在布局期间被忽略;
    • 它为那些没有显式对齐集的视图提供隐式对齐指南。
  • 在下面的动画中,可以看到如何更改容器中的对齐参数,将在布局期间改变有效的对齐参考线:

请添加图片描述

  • 如果更改 withAnimation 块中的对齐方式,视图将移动到它们的新位置。

② Frame Alignment

  • 到目前为止,看到的所有对齐,都是关于如何定位视图,一旦确定了这一点,布局系统需要将整个组定位在容器内。通过提供frame(alignment:),正在告诉系统如何这样做。如果不指定,则该组将在其容器内居中。

请添加图片描述

  • 通常情况下,改变框架对齐不会产生任何影响,这不是一个 bug。在大多数情况下,容器是紧的,也就是说,容器足够大,可以容纳所有视图,但不能再多一个像素。因此,在 frame() 修饰符中使用,.leading, .center 或 .trailing 将不起作用,因为视图组已经使用了所有的空间。它不能移动到任何一边,因为没有空间。

七、Multiline Text Alignment()

  • 这个很简单,也很直接,当有一个包含多条行的 Text 视图时,它将决定行之间如何对齐:

在这里插入图片描述

八、与 Alignment Guides 交互

  • 为了本文,创建一个有用的学习工具,它将探索到目前为止学到的东西,最好在 iPad上 以横向模式运行代码,如果没有 iPad,可以使用模拟器:

请添加图片描述

  • 使用“Show in Two Phases”选项来了解对齐是如何工作的,可以看到对齐指南如何移动到它的新位置,然后如何移动视图以使所有指南实际对齐。需要注意的是,通常情况下,所有这些都是同时发生的。但是,为了更好地理解这个过程,这个选项将其分为两个部分。尝试方法如下:
    • 试试 frame(alignment:) 参数:看看它是如何在窄容器和宽容器中工作的,应该注意到,当我们的容器是紧的时,这个参数将不起作用;
    • 更改容器的对齐方式,注意,只要对齐指南的类型相同,更改就不会产生任何影响,唯一会移动的视图是具有隐式对齐的视图,这是因为它是唯一具有不同对齐值的视图;
    • 通过与视图交互,测试不同的预设对齐值,可以将对齐参考线设置为 .leading(按 L 键),.center(按 C 键),.trailing(按 T 键);
    • 通过与视图交互,测试不同的任意对齐值,点击黄色条,选择对齐点;
    • 测试小于零或大于视图宽度的对齐值,要做到这一点,请延长黄色条,当测试不同的值时,试着在点击屏幕之前预测会发生什么。

九、自定义 Alignments

  • 现在我们知道标准对齐是如何工作的,创建一个自定义对齐,来看看第一个例子的代码,并分析它的作用:
extension HorizontalAlignment {
    private enum WeirdAlignment: AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return d.height
        }
    }
    
    static let weirdAlignment = HorizontalAlignment(WeirdAlignment.self)
}
  • 当定义自定义对齐时,正在做两件事:
    • 确定它是水平的还是垂直的;
    • 为隐式对齐(即没有显式调用. alignmentguide() 的视图)提供默认值。
  • 通过使用高度作为默认对齐方式,它创造了一种有趣的效果:

在这里插入图片描述

struct CustomView: View {
    var body: some View {
        VStack(alignment: .weirdAlignment, spacing: 10) {
            
            Rectangle()
                .fill(Color.primary)
                .frame(width: 1)
                .alignmentGuide(.weirdAlignment, computeValue: { d in d[.leading] })
            
            ColorLabel(label: "Monday", color: .red, height: 50)
            ColorLabel(label: "Tuesday", color: .orange, height: 70)
            ColorLabel(label: "Wednesday", color: .yellow, height: 90)
            ColorLabel(label: "Thursday", color: .green, height: 40)
            ColorLabel(label: "Friday", color: .blue, height: 70)
            ColorLabel(label: "Saturday", color: .purple, height: 40)
            ColorLabel(label: "Sunday", color: .pink, height: 40)
            
            Rectangle()
                .fill(Color.primary)
                .frame(width: 1)
                .alignmentGuide(.weirdAlignment, computeValue: { d in d[.leading] })
        }
    }
}

struct ColorLabel: View {
    let label: String
    let color: Color
    let height: CGFloat
    
    var body: some View {
        Text(label).font(.title).foregroundColor(.primary).frame(height: height).padding(.horizontal, 20)
            .background(RoundedRectangle(cornerRadius: 8).fill(color))
    }
}

十、Aligning Non-Siblings

  • 在前面的示例中,已经看到了如何创建自定义对齐,但这有什么意义呢?没有自定义对齐也可以实现相同的结果,使用自定义对齐的真正好处是,使用它们来对齐位于视图层次结构不同分支上的视图。
  • 来看下一个例子:

在这里插入图片描述

  • 如果分析这个视图的组件,将意识到我们需要将图像与文本视图对齐,但它们不属于同一个容器:

在这里插入图片描述

  • 图像和文本视图都有一个共同的容器(HStack),因此将创建一个自定义对齐,以匹配它们的中心点。重要的是要记住适当地设置公共容器的对齐参数。
extension VerticalAlignment {
    private enum MyAlignment : AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return d[.bottom]
        }
    }
    static let myAlignment = VerticalAlignment(MyAlignment.self)
}

struct CustomView: View {
    @State private var selectedIdx = 1
    
    let days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
    
    var body: some View {
            HStack(alignment: .myAlignment) {
                Image(systemName: "arrow.right.circle.fill")
                    .alignmentGuide(.myAlignment, computeValue: { d in d[VerticalAlignment.center] })
                    .foregroundColor(.green)

                VStack(alignment: .leading) {
                    ForEach(days.indices, id: \.self) { idx in
                        Group {
                            if idx == self.selectedIdx {
                                Text(self.days[idx])
                                    .transition(AnyTransition.identity)
                                    .alignmentGuide(.myAlignment, computeValue: { d in d[VerticalAlignment.center] })
                            } else {
                                Text(self.days[idx])
                                    .transition(AnyTransition.identity)
                                    .onTapGesture {
                                        withAnimation {
                                            self.selectedIdx = idx
                                        }
                                }
                            }
                        }
                    }
                }
            }
            .padding(20)
            .font(.largeTitle)
    }
}
  • 您可能想知道,所有没有明确指定垂直对齐的文本视图怎么办?它们不打算用隐式值吗?如果是这样,它们不都是一个叠在另一个上面吗?都是有效的问题。这是对齐指南的另一个令人困惑的事实。然而,在这种情况下,我们处理的是 VStack,而不是 ZStack,这意味着它里面的所有视图都必须垂直堆叠。Alignment guides 不会破坏这一点,布局系统将使用所选视图中的显式对齐来对齐箭头图像,其他没有明确指南的文本视图将相对于有明确指南的文本视图进行定位。

十一、ZStack 自定义对齐 Alignment

  • 如果需要为 ZStack 创建自定义对齐,这里有一个模板:
extension VerticalAlignment {
    private enum MyVerticalAlignment : AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return d[.bottom]
        }
    }
    
    static let myVerticalAlignment = VerticalAlignment(MyVerticalAlignment.self)
}

extension HorizontalAlignment {
    private enum MyHorizontalAlignment : AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return d[.leading]
        }
    }
    
    static let myHorizontalAlignment = HorizontalAlignment(MyHorizontalAlignment.self)
}

extension Alignment {
    static let myAlignment = Alignment(horizontal: .myHorizontalAlignment, vertical: .myVerticalAlignment)
}

struct CustomView: View {
    var body: some View {
        ZStack(alignment: .myAlignment) {
            ...
        }
    }
}

十二、总结

  • 至此为止,已经看到了 alignment guides 是多么强大,一旦了解了它们能提供什么,它们就会变得更有意义。为了获得更好的体验,应该记住以下几点:
    • 容器中的每个视图都有 alignment guides,如果未显式指定,则由容器的对齐参数确定;
    • 在布局期间,与容器对齐参数中指定的类型不同的 alignment guides 将被忽略。
    • VStack 使用水平对齐,而 HStacks 使用垂直对齐;
    • 如果容器是紧密的,则 frame 方法中的对齐参数将没有视觉效果;
    • 当来自视图层次结构不同分支的两个视图需要彼此对齐时,需要自定义 alignment guides。
  • 本文中的大多数示例使用水平对齐,但是相同的概念也适用于垂直对齐。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/306145.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL语法及IDEA使用MySQL大全

在项目中我们时常需要写SQL语句,或简单的使用注解直接开发,或使用XML进行动态SQL之类的相对困难的SQL,并在IDEA中操控我们的SQL,但网上大都图方便或者觉得太简单了,完全没一个涵盖两个方面的讲解。 单表: …

GO语言笔记3-指针

指针的概念 先看一段代码的输出 package main import "fmt" func main(){ var age int 18fmt.Println("age的内存地址值是:",&age)//age的内存地址值是: 0xc000012090// 定义一个指针变量// *int 是一个指针类型,可以理解为指向int类型的…

TEMU 新手小白必看!2024入驻流程/入驻类目/入驻资料等详细流程讲解

2023 TEMU 可谓是赚足眼球,流量持续上涨,2024年相信不少卖家们已经跃跃欲试,但大陆卖家如何入驻TEMU?哪些品类适合入驻?又有哪些入驻要求和资料?别急,今天东哥就一一给大家详细讲解,…

Python操作excel-读取、表格填充颜色区分

1.场景分析 遇到一个需要读取本地excel数据,处理后打入到数据库的场景,使用java比较重,python很好的解决了这类问题 2.重难点 本场景遇到的重难点在于: 需要根据表格内的背景颜色对数据进行筛选 读取非默认Sheet 总是出现Value…

UE5 使用动画模板创建多个动画蓝图

我们制作游戏的时候,角色会根据不同的武器表现出来不同的攻击动画,待机动画以及移动动画。如果我们在UE里面实现这个需求,是通过复制粘贴的方式修改,还是有更好的方式。 这里就需要介绍一下动画模板,我们可以将动画蓝图…

在黑马程序员大学的2023年终总结

起笔 时间真快,转眼又是年末。是时候给2023做个年终总结了,为这一年的学习、生活以及成长画上一个圆满的句号。 这一年相比去年经历了很多事情,接下来我会一一说起 全文大概4000字,可能会占用你15分钟左右的时间 经历 先来给大…

外包干了3个多月,技术退步明显

先说一下自己的情况,本科生,19年通过校招进入广州某软件公司,干了接近4年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

【STM32】WDG看门狗

1 WDG简介 WDG(Watchdog)看门狗 看门狗可以监控程序的运行状态,当程序因为设计漏洞、硬件故障、电磁干扰等原因,出现卡死或跑飞现象时,看门狗能及时复位程序,避免程序陷入长时间的罢工状态,保…

解决不同请求需要的同一实体类参数不同(分组校验validation)

问题概述 新增目录是自动生成id&#xff0c;不需要id参数&#xff1b;更新目录需要id&#xff0c;不能为空 pom.xml中已有spring-boot-starter-validation依赖 <!--validation(完成属性限制&#xff0c;参数校验)--><dependency><groupId>org.springframew…

设计模式的艺术P1基础—2.4-2.11 面向对象设计原则

设计模式的艺术P1基础—2.4-2.11 面向对象设计原则 2.4 面向对象设计原则概述 向对象设计的目标之一在于支持可维护性复用&#xff0c;一方面需要实现设计方案或者源代码的重用&#xff0c;另一方面要确保系统能够易于扩展和修改&#xff0c;具有较好的灵活性。 面向对象设计…

NSSCTF EasyP

开启环境&#xff1a; 这一题我们通过分析需要知道一些知识&#xff1a; 1.$_SERVER[‘PHP_SELF’] &#xff1a;正在执行脚本的文件名 例子&#xff1a;127.0.0.1/pikachu/index.php 显示&#xff1a;/pikachu/index.php 2.S​ERVER[′REQUESTU​RI′]&#xff1a;与 _SERV…

小巧且兼具高性能的小模型 TinyLlama 等

TinyLlama-1.1B 小模型在边缘设备上有着广泛的应用&#xff0c;如智能手机、物联网设备和嵌入式系统&#xff0c;这些边缘设备通常具有有限的计算能力和存储空间&#xff0c;它们无法有效地运行大型语言模型。因此&#xff0c;深入探究小型模型显得尤为重要。 来自新加坡科技…

【C语言】操作符

操作符分类 算术操作符移位操作符位操作符赋值操作符单目操作符关系操作符逻辑操作符条件操作符逗号操作符下标引用、函数调用和结构成员操作符 算术操作符 除了 % 操作符之外&#xff0c;其他的几个操作符可以作用于整数和浮点数。 对于 / 操作符如果两个操作数都为整数&am…

AMEYA360 | 热敏电阻的工作原理及作用 热敏电阻厂商有哪些

摘要&#xff1a;热敏电阻是一种传感器电阻&#xff0c;其电阻值随着温度的变化而改变。热敏电阻的工作原理是使用传感器来帮助调节温度高低&#xff0c;作用包括电压调节&#xff0c;音量控制&#xff0c;时间延迟和电路保护。热敏电阻具有测温、温度补偿、过热保护、液面测量…

基于springboot+vue的家政服务系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目背景…

jmeter连接数据库

1.准备工作 连接数据库需要第三方包 mysql-connector-java-5.1.35-bin 放入路径下&#xff1a;"C:apache-jmeter-5.0\lib\ext\mysql-connector-java-5.1.35-bin.jar" 2.重启jmeter 3.运用场景&#xff1a;可以用于造数据&#xff0c;恢复数据&#xff0c;方便案例…

Java设计模式详解超详细(含示例代码)

1. 什么是设计模式 设计模式&#xff0c;是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。 2. 设计模式分类 创建型模式&#xff0c;共五种&#xff1a;工厂方法…

Java后端开发——Mybatis实验

文章目录 Java后端开发——Mybatis实验一、MyBatis入门程序1.创建工程2.引入相关依赖3.数据库准备4.编写数据库连接信息配置文件5.创建POJO实体6.编写核心配置文件和映射文件 二、MyBatis案例&#xff1a;员工管理系统1.在mybatis数据库中创建employee表2.创建持久化类Employee…

使用pyinstaller打包生成exe(解决gradio程序的打包问题)

解决 [Errno 2] No such file or directory: gradio_client\types.json 问题&#xff0c;不需要手动创建hook文件 解决 FileNotFoundError: [Errno 2] No such file or directory: gradio\blocks_events.pyc 问题&#xff0c;不需要将pyi文件重命名为pyc文件 最终实现gradio程…

基于uniapp封装的table组件

数据格式 tableData: [{elcInfo: [{tableData:[1,293021.1,293021.1,293021.1,293021.1,]}]},{elcInfo: [{tableData:[1,293021.1,293021.1,293021.1,293021.1,]}]},{elcInfo: [{tableData:[1,293021.1,293021.1,293021.1,293021.1,]}]},/* {title: "2",elcInfo: [{…