一、前言

  • SwiftUI 的各种堆栈是许多框架中最基本的布局工具,能够定义组视图,这些组视图可以按照水平、垂直或覆盖视图对齐。
  • 当涉及到水平和垂直的变体时( HStack 和 VStack),需要在这两者之间动态的切换。例如,假如正在构建一个 App 其中包含 LoginActionsView,一个让用户登录时在列表中选择操作的类:
struct LoginActionsView: View {...var body: some View {VStack {Button("Login") { ... }Button("Reset password") { ... }Button("Create account") { ... }}.buttonStyle(ActionButtonStyle())}
}struct ActionButtonStyle: ButtonStyle {func makeBody(configuration: Configuration) -> some View {configuration.label.fixedSize().frame(maxWidth: .infinity).padding().foregroundColor(.white).background(Color.blue).cornerRadius(10)}
}
  • 以上代码中,使用了 fixedSize 防止按钮文本被截断,这仅是在确信给定的内容视图不会比视图本身更大的情况。目前,按钮是垂直排列的,并且填满了水平线上的可用空间(可以用以上示例代码预览按钮的样子),虽然这在竖向的 iPhone 上看起来很好,但假设现在想要在横向模式下让 UI 横向排列。

二、GeometryReader

  • GeometryReader 能实现吗?一种方式是用 GeometryReader 测量当前可用空间,并根据宽度是否大于其高度,可以选择使用 HStack 或 VStack 来渲染内容。
  • 虽然可以在 LoginActionsView 中放入该逻辑,但希望以后能复用代码,因此需要重新创建一个专门的视图,作为一个独立的组件来实现动态堆栈的切换逻辑。
  • 为了使代码可用性更高,不会硬编码让两个堆栈变体使用对齐或间距什么的。相反,让我们像 SwiftUI 一样,对这些属性参数化,同时设定框架所使用的默认值,如下所示:
struct DynamicStack<Content: View>: View {var horizontalAlignment = HorizontalAlignment.center
var verticalAlignment = VerticalAlignment.center
var spacing: CGFloat?@ViewBuilder var content: () -> Contentvar body: some View {GeometryReader { proxy inGroup {if proxy.size.width > proxy.size.height {HStack(alignment: verticalAlignment,spacing: spacing,content: content)} else {VStack(alignment: horizontalAlignment,spacing: spacing,content: content)}}}}
}
  • 由于使新的 DynamicStack 使用与 HStack 和 VStack 相同的 API ,现在可以在 LoginActionsView 中直接将以前的 VStack 换成新的自定义的实例:
struct LoginActionsView: View {...var body: some View {DynamicStack {Button("Login") { ... }Button("Reset password") { ... }Button("Create account") { ... }}.buttonStyle(ActionButtonStyle())}
}
  • 就像上面的代码展示的那样,使用 GeometeryReader 来展示动态切换有一个相当明显的缺点,在几何图形阅读器中总是会填充水平和垂直方向的所有可用空间(以便测量实际空间)。在例子中,LoginActionsView 不再只是水平方向的排列,它现在也能移动到屏幕的顶部。

三、使用尺寸类的示例

  • 相反,使用 Apple 的尺寸类系统来决定 DynamicStack 应该在底层使用 HStack 还是 VStack 。这样做的好处不仅仅是在引入 GeometeryReader 之前保留同样紧凑的布局,并且会使 DynamicStack 在开始的时候以一种和系统组件类似的方式在所有设备和方向上构建。
  • 为了观察当前水平方向的尺寸,需要用到 SwiftUI 环境系统, 通过在 DynamicStack 中声明 @Environment - 标记属性(带有 horizontalSizeClass 关键路径),将会使我们在视图内容中切换到当前 sizeClass 的值:
struct DynamicStack<Content: View>: View {...@Environment(\.horizontalSizeClass) private var sizeClassvar body: some View {switch sizeClass {case .regular:hStackcase .compact, .none:vStack@unknown default:vStack}}
}private extension DynamicStack {var hStack: some View {HStack(alignment: verticalAlignment,spacing: spacing,content: content)}var vStack: some View {VStack(alignment: horizontalAlignment,spacing: spacing,content: content)}
}
  • 经过以上操作,LoginActionsView 将可以在常规的尺寸渲染时动态切换成水平布局(例如在大尺寸的 iPhone 使用横屏,或者全屏 iPad 上的任一方向),而其它所有尺寸的配置使用垂直布局,所有这些仍然使用紧凑垂直布局,它使用的空间不超过渲染其内容所需的空间。

四、使用布局协议

  • 虽然最后已经用了非常棒的解决方案,可以在所有支持 SwiftUI 的 iOS 版本中使用,但也让我们来探索一下在 iOS 16 中引入的一些新的布局工具,其中一个工具是新的 Layout 协议,它既能创建完整的自定义布局,直接集成到 SwiftUI 的布局系统中,同时也提供一种更丝滑更动画的方式在各种布局之间动态切换。这都是因为事实证明 Layout 不仅仅是第三方开发者的 API ,Apple 也让 SwiftUI 自己的布局容器使用这个新协议 。
  • 因此,与其直接使用 HStack 和 VStack 作为容器视图,不如将它们作为符合 Layout 的实例,使用 AnyLayout 类型进行包装 — 就像这样:
private extension DynamicStack {var currentLayout: AnyLayout {switch sizeClass {case .regular, .none:return horizontalLayoutcase .compact:return verticalLayout@unknown default:return verticalLayout}}var horizontalLayout: AnyLayout {AnyLayout(HStack(alignment: verticalAlignment,spacing: spacing))}var verticalLayout: AnyLayout {AnyLayout(VStack(alignment: horizontalAlignment,spacing: spacing))}
}
  • 以上的操作是可行的,因为当 HStack 和 VStack 的内容类型是 EmptyView 时,它们都符合新的 Layout 协议(当内容为空时就是这种情况),来看一下SwiftUI 的公共接口:
struct DynamicStack<Content: View>: View {...var body: some View {currentLayout(content)}
}
  • 现在能通过使用新的 currentLayout 解决使用什么布局,再来更新 body 的实现,简单调用从该属性返回的 AnyLayout ,就像函数一样,如下:
struct DynamicStack<Content: View>: View {...var body: some View {currentLayout(content)}
}
  • 那么之前的方案和上面基于布局的方案有什么区别呢?关键的区别在于(除了后者需要 iOS 16)切换布局可以保留正在渲染的底层视图的标识,而在 HStack 和 VStack 之间切换就不会这样。这样做会令动画更流畅,例如在切换设备方向时,也有可能在执行此类更改时获得小幅的性能提升(因为 SwiftUI 总是在其视图层次结构为静态时尽可能表现最佳)。

五、选择合适的视图

  • 但还没有结束,因为 iOS 16 也给了其他有趣的新的布局工具,它有可能也能用于实现 DynamicStack,这是一种全新的视图类型,名字叫做 ViewThatFits。就像字面意思一样,这种新的容器将会在我们初始化时传递的候选列表中,基于当前上下文挑选出最优视图。
  • 在示例中,这意味着能同时把 HStack 和 VStack 传递给它,并且代表在它们中间自动切换:
struct DynamicStack<Content: View>: View {...var body: some View {ViewThatFits {HStack(alignment: verticalAlignment,spacing: spacing,content: content)VStack(alignment: horizontalAlignment,spacing: spacing,content: content)}}
}
  • 在这种情况下,首先放置 HStack 是很重要的,因为 VStack 可能总是合适的,即使在希望布局是横向的情况下(例如 iPad 的全屏模式)。同样重要的是要指出,上述基于 ViewThatFits 的技术将会始终尝试 HStack ,即使在用紧凑尺寸渲染布局时也是如此,只有在 HStack 不适合时才会选择基于VStack 的布局。

SwiftUI之HStack和VStack的切换相关推荐

  1. SwiftUI iOS 开源组件之银行卡切换效果 (教程含源码)

    实战需求 SwiftUI iOS 开源组件之银行卡切换效果 本文价值与收获 看完本文后,您将能够作出下面的界面 看完本文您将掌握的技能 计算组件大小 GeometryReader { geometry ...

  2. Numpy中stack(),hstack(),vstack()函数的使用方法

    stack()与hstack(),vstack()不同,前者堆叠数组是联结(join),而后两者是串联(concatenation),可以体会一下. 1. stack()函数 按照指定的轴对数组序列进 ...

  3. numpy的concatenate()、hstack()、vstack()、stack()函数分析

    numpy中几个常用的相似函数concatenate().hstack().vstack().stack()用法相似,不容易区分.现在从形状的角度,对这4个函数进行总结. 在总结之前,首先介绍一下维度 ...

  4. Numpy中stack(),hstack(),vstack()函数详解

    这三个函数有些相似性,都是堆叠数组,里面最难理解的应该就是stack()函数了,我查阅了numpy的官方文档,在网上又看了几个大牛的博客,发现他们也只是把numpy文档的内容照搬,看完后还是不能理解, ...

  5. python numpy中stack(),hstack(),vstack()函数解释

    引用 stack()函数axes参数的含义总结起来就是: axes=0:不变 axes=1:次外层矩阵转置 axes=2:次外层矩阵转置,同时次次外层矩阵转置 以此类推. hstack()是次外层水平 ...

  6. numpy函数hstack,vstack,dstack简介

    vstack.hstack和dstack都用于把几个小数组合并成一个大数组.它们的差别是小数组的元素在大数组中的排列顺序有所不同.把两部手机摆到一起有几种方式?水平的左右排列,垂直的上下排列,还可以把 ...

  7. [Numpy]stack(), hstack(), vstack(), concatenate()

    部分资料来源于网络,仅做个人学习之用 目录 0. axis取值 1. stack() 2. hstack() 3. vstack() 4. concatenate() 0. axis取值 在numpy ...

  8. SwiftUI 小专栏20200817汇总

    SwiftUI 布局篇汇总 9 分钟前共695字 SwiftUI 如何编程打开Office Word PPT Excel PDF PNG iWor... 14 小时前共4389字 SwiftUI 小技 ...

  9. SwiftUI学习记录

    快捷键 资源库:⌘+⇧+L 布局和堆栈 默认情况下,图像不可调整大小 需要添加一个名为resizable的修饰符(在Modifiers里查找) 增大间距 添加间隙: Views里的spacer 框架默 ...

最新文章

  1. 某集团公司信息化项目经验总结
  2. Makefile的伪目标
  3. 真正开始记录自己学习技术过程的点滴
  4. auto-sklearn详解
  5. docker nacos mysql nginx 集群多台
  6. fopen如何保存西里尔文文件名_如何下载微信视频号的视频?
  7. 深度学习需要掌握的 13 个概率分布
  8. 安卓app开发-02-安卓app快速开发
  9. IT人的地摊不就是开源么 | 凌云时刻
  10. java中图片转base64
  11. crontab、cron、at、atq、batch、ps命令练习题
  12. Unity3D高级编程之进阶主程-陆泽西 (Jesse Lu)
  13. 进击高手【第五期】思维题
  14. (论文解读)High-frequency Component Helps Explain the Generalization of Convolutional Neural Networks
  15. 穆易天气app代码(一)
  16. 《2022中国RPA采购指南》报告正式发布
  17. MySQL忘记密码如何重置
  18. php自定义函数数学计算,PHP基于自定义函数生成笛卡尔积的方法示例
  19. java创建response对象_创建一个HttpResponse对象
  20. day 06 非空约束、唯一约束、主键约束、外键约束

热门文章

  1. Java核心机制(1)
  2. 数据库 之创建新用户
  3. Ubuntu18.04下安装Nvidia驱动和CUDA10.1+CUDNN
  4. Web安全之SQL注入总结
  5. spring的依赖注入 -------基于注解方式
  6. #XMind 8 Update 8下载与补丁激活
  7. VRay Next for SketchUp 室外建筑日景表现教程
  8. Application was not properly initialized at startup, could not find Factory:
  9. 桌面计算机右击选项里没有管理员,为什么没有以管理员身份运行选项
  10. 电脑重装系统以后出现 error: unknown filesystem怎么办?