将某个视图在父视图中居中显现是一个常见的需求,即便关于 SwiftUI 的初学者来说这也并非难事。在 SwiftUI 中,有许多手法能够达成此目的。本文将介绍其中的一些办法,并对每种办法背面的完成原理、适用场景以及注意事项做以阐明。

原文宣布在我的博客wwww.fatbobman.com

欢迎订阅我的公共号:【肘子的Swift记事本】

需求

完成下图中展示的样式:在彩色矩形视图中居中显现单行 Text

在 SwiftUI 中实现视图居中的若干种方法

填充物

Spacer

最常见也是最简单想到的处理方案。

var hello: some View {
    Text("Hello world")
        .foregroundColor(.white)
        .font(.title)
        .lineLimit(1)
}
HStack {
    Spacer()
    hello
    Spacer()
}
.frame(width: 300, height: 60)
.background(.blue)

假如我告诉你上面的代码有两个危险你相信吗?

  • 文本内容超出了矩形的宽度

    Spacer 是有最小厚度设定的,默许的最小垫片厚度为 8px 。即便文本宽度超出了 HStack 给出的主张宽度,但 HStack 在布局时,仍会保留其最小厚度,导致下图上方的文本无法充分运用矩形视图的宽度。

    处理办法为:Spacer(minLength: 0)

    当然,你也能够运用 Spacer 这个特性,操控 Text 在 HStack 中可运用的宽度。

在 SwiftUI 中实现视图居中的若干种方法

  • 将组成后的视图放置在某个可能会充溢屏幕的视图的顶部或底部显现成果或者与你的预期不符
  VStack {
    // Hello world 视图 1
    HStack {
          Spacer(minLength: 0)
          hello
          Spacer(minLength: 0)
      }
      .frame(width: 300, height: 60)
      .background(.blue)
    HStack {
          Spacer(minLength: 0)
          hello
          Spacer(minLength: 0)
      }
      .frame(width: 300, height: 60) // 相同的尺度
      .background(.red)
    Spacer() // 让 VStack 充溢可用空间
}

在 SwiftUI 中实现视图居中的若干种方法

从 SwiftUI 3.0 开始,在运用 background 增加符合 ShapeStyle 协议的元素时,能够经过 ignoresSafeAreaEdges 参数设置是否疏忽安全区域,默许值为 .all ( 疏忽任何的安全区域 )。因而,当咱们将组成后的 hello world 视图放置在 VStack 顶部时( 经过 Spacer ),矩形的 background 会连同顶部的安全区域一起渲染。

处理的办法是:.background(.blue, ignoresSafeAreaEdges: []) ,排除去不希望疏忽的安全区域。

别的,在给定尺度不明的状况下( 未显式为矩形设置尺度 ),上面的代码也需求进行一定的调整。例如,在 List Row 中显现 hello world 视图,希望矩形能够充溢 Row :

List {
    HStack {
        Spacer(minLength: 0)
        hello
        Spacer(minLength: 0)
    }
    .background(.blue)
    .listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0)) // 将 Row 的 Insets 设置为 0
}
.listStyle(.plain)
.environment(\.defaultMinListRowHeight, 80) // 设置 List 最小行高度

hello world 视图并不能充溢 Row 供给的高度。这是因为 HStack 的高度是由容器子视图对齐摆放后的高度决定的。Spacer 在 HStack 中只能进行横向填充,并不具有纵向的高度( 高度为 0 ),因而 HStack 终究的需求高度与 Text 的高度一致。

在 SwiftUI 中实现视图居中的若干种方法

处理的办法是:

HStack {
    Spacer(minLength: 0)
    hello
    Spacer(minLength: 0)
}
.frame(maxHeight: .infinity) // 用满主张高度
.background(.blue)

后文中为了简洁将省掉掉针对给定尺度不明状况的处理方式。统一运用固定尺度(.frame(width: 300, height: 60))。

其他填充物

那么,咱们是否能够运用其它的视图完成与 Spacer 类似的填充效果呢?例如:

HStack {
    Color.clear
    hello
    Color.clear
}
.frame(width: 300, height: 60)
.background(Color.cyan)

很惋惜,运用上面的代码,Text 将只能运用 HStack 三分之一的宽度。

HStack、VStack 在进行布局时,会为每个子视图供给四种不同的主张方式( 最小、最大、清晰尺度以及未指定 ),假如子视图在不同的方式下回来的需求尺度是不一样的,则意味着该视图是可变尺度视图。那么 HStack、VStack 会在清晰了所有固定尺度子视图的需求尺度后,将所剩的可用尺度( HStack、VStack 的父视图给他们的主张尺度 – 固定尺度子视图的需求尺度 )平均分配( 在优先级相同的状况下 )给这些可变尺度视图。

因为 Color、Text 都具有可变尺度的特性,因而,它们三等分了 HStack。

在 SwiftUI 中实现视图居中的若干种方法

可是咱们能够经过调整视图优先级的方式,来确保 Text 能够取得最大的分量,例如:

HStack {
    Color.clear
        .layoutPriority(0)
    hello
        .layoutPriority(1)
    Color.clear
        .layoutPriority(0)
}
.frame(width: 300, height: 60)
.background(Color.cyan)
Text("Hello world,hello world,hello world") // hello 的宽度超出了矩形的宽度

在 SwiftUI 中实现视图居中的若干种方法

至于上图中 Text 仍没有充分运用 HStack 悉数宽度的原因,是因为没有为 HStack 设置清晰的 spacing ,将其设置为 0 即可:HStack(spacing:0)

为布局容器设置清晰的 spacing 是一个好习惯,在未清晰指守时,HStack、VStack 在进行布局时可能会呈现某些反常。下文中也会碰到此种状况。

HStack、VStack 是不会给 Spacer 分配 spacing 的,毕竟 Spacer 本身就代表了空间占用。因而在第一个比如中,即便没有为 HStack 设置 spacing ,Text 仍然会运用悉数的 HStack 宽度。

把握了视图优先级的运用方式,咱们还能够运用其他具有可变尺度的特性的视图来充任填充物,例如:

  • Rectangle().opacity(0)
  • Color.blue.opacity(0)
  • ContainerRelativeShape().fill(.clear)

在运用 SwiftUI 进行开发的过程中,Color、Rectangle 等经常被用来完成对容器的等分操作。别的,因为 Color、Rectangle 会在两个维度进行填充( Spacer 会根据容器挑选填充维度 ),因而,运用它们作为填充物时,将会主动运用悉数的可用空间( 包含高度 ),无需经过 .frame(maxHeight: .infinity) 应对给定尺度不明的场景。

请阅览 SwiftUI 專欄 #4 Color 不只是顏色 ,把握有关 Color 更多的内容

对齐攻略

上节中,咱们经过填充物让 Text 完成了左右居中。上下居中则是运用了 HStack 对齐攻略的默许设定( .center )完成的。本节中,咱们将完全经过对齐攻略来完成居中操作。

ZStack

ZStack { // 运用对齐攻略的默许值,适当于 ZStack(alignment:.center)
    Color.green
    hello
}
.frame(width: 300, height: 60)

上述代码的布局逻辑是:

  • ZStack 为 Color 和 Text 别离给出了 300 x 60 的主张尺度
  • Color 会将主张尺度作为自己的需求尺度( 表现为充溢 ZStack 空间 )
  • Text 最大可用宽度为 300
  • Color 与 Text 将依照对齐攻略 center 进行对齐( 看起来便是 Text 显现在 Color 的中间 )

假如将代码改写成下面的方式就会呈现问题:

ZStack { // 在不清晰设置 VStack spacing 的状况下,会呈现 VStack spacing 不一致的状况
    Color.gray
        .frame(width: 300, height: 60)
    hello // 宽度没有约好,当文本较长时,会超越 Color 的宽度
}

上方代码的布局逻辑是:

  • Color 的尺度为 300 x 60 ( 不关心 ZStack 给出的主张尺度 )
  • ZStack 的尺度为 Color 和 Text 两者的最大宽度 x 最大高度,该尺度是一个可变尺度( 取决于 Text 文本的长度 )
  • 当 ZStack 给出的主张宽度大于 300 时,Text 的可运用宽度将超越 Color 的宽度

因而会呈现两种可能的过错状态:

  • 当文本较长时,Text 会超越 Color 的宽度
  • 因为组成视图具有可变尺度特性,VStack、HStack 在为其增加 spacing 时将可能呈现反常 ( 下图中 spacing 的分配不均匀。显式设置能够处理该问题,请养成显式设置 spacing 的习惯 )
VStack { // 没有设定 spacing ,显式设置可修复 spacing 不均匀的问题
    ZStack {
        Color.green
        hello
    }
    .frame(width: 300, height: 60)
    ZStack { // 在不清晰设置 VStack spacing 的状况下,会呈现 VStack spacing 不一致的状况
        Color.gray
            .frame(width: 300, height: 60)
        hello // 关于文字超越矩形宽度的状况欠好处理
    }
    // Spacer 版本
    HStack {
        Spacer(minLength: 0)
        hello
            .sizeInfo()
        Spacer(minLength: 0)
            .sizeInfo()
    }
    .frame(width: 300, height: 60)
    .background(.blue, ignoresSafeAreaEdges: [])
}

在 SwiftUI 中实现视图居中的若干种方法

frame

hello
    .frame(width: 300, height: 60) // 运用了默许的 center 的对齐攻略,适当于 .frame(width: 300, height: 60,alignment: .center)
    .background(.pink)

布局逻辑:

  • 运用 FrameLayout 布局容器对 Text 进行布局
  • FrameLayout 给 Text 的主张尺度为 300 x 60
  • Text 与占位视图( 空白视图的尺度为 300 x 600 )按对齐攻略 center 进行对齐

这是我个人最喜欢运用的居中手法,应对给定尺度不明的状况也非常方便:

hello
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .background(.pink)

想了解 frame 的完成原理请阅览 SwiftUI 布局 —— 尺度( 下 ) 一文

overlay

Rectangle() // 直接运用 Color.orange 也能够
    .fill(Color.orange)
    .frame(width: 300, height: 60)
    .overlay(hello) // 适当于 .overlay(hello,alignment: .center)

布局逻辑:

  • Rectangle 将取得 300 x 60 主张尺度( Rectangle 将运用悉数的尺度 )
  • 运用 OverlayLayout 布局容器对 Rectangle 及 Text 进行布局,主张尺度采用主视图的需求尺度( Rectangle 的需求尺度 )
  • Text 与 Rectangle 依照对齐攻略 center 进行对齐

那么是否能够用 background 完成类似的样式呢?例如:

hello
    .background(
        Color.cyan.frame(width: 300,height: 60)
    )
    .border(.red) // 显现边框以查看组成视图的布局尺度

在 SwiftUI 中实现视图居中的若干种方法

很惋惜,你将取得与上文中 ZStack 过错用法类似的成果。文字可能会超长,视图无法取得 spacing ( 即便进行了显式设置 )。

请阅览 SwiftUI 布局 —— 对齐 ,了解更多有关 ZStack、overlay、background 的对齐机制

Geometry

虽然有些大材小用,但当咱们需求获取更多有关视图的信息时,GeometryReader 是一个适当不错的挑选:

GeometryReader { proxy in
    hello
        .position(.init(x: proxy.size.width / 2, y: proxy.size.height / 2))
        .background(Color.brown)
}
.frame(width: 300, height: 60)

布局逻辑:

  • GeometryReader 将取得 300 x 60 的主张尺度
  • 因为 GeometryReader 具有与 Color、Rectangle 类似的特征,会将给定的主张尺度作为需求尺度( 表现为占用悉数可用空间 )
  • GeometryReader 给 Text 供给 300 x 60 的主张尺度
  • GeometryReader 中的视图,默许基于 topLeading 对齐( 类似 overlay(alignment:.topLeading) 的效果 )
  • 运用 postion 将 Text 的中心点与给定的位置进行对齐( postion 是一个经过 CGPoint 来对齐中心点的视图润饰器 )

当然,你也能够获取 Text 的 Geometry 信息,经过 offset 或 padding 的方式完成居中。不过除非矩形的尺度清晰,不然里外都需求运用 GeometryReader ,完成将过于烦琐。

总结

本文选取了一些有代表性的处理办法,跟着 SwiftUI 功能的不断增强,会有越来越多的手法可供运用。万变不离其宗,把握了 SwiftUI 的布局原理,不管需求怎么改变都可轻松应对。

我为本文这种经过多种办法来处理一个问题的方式增加了【小题大做】标签,目前运用该便签的文章还有:在 Core Data 中查询和运用 count 的若干办法、在 SwiftUI 视图中打开 URL 的若干办法 。

希望本文能够对你有所协助。一起也欢迎你经过 Twitter、 Discord 频道或下方的留言板与我进行交流。

我正以聊天室、Twitter、博客留言等讨论为灵感,从中选取有代表性的问题和技巧制作成 Tips ,发布在 Twitter 上。每周也会对当周博客上的新文章以及在 Twitter 上发布的 Tips 进行汇总,并经过邮件列表的方式发送给订阅者。

订阅下方的 邮件列表,能够及时取得每周的 Tips 汇总。

原文宣布在我的博客wwww.fatbobman.com

欢迎订阅我的公共号:【肘子的Swift记事本】