SwiftUI 的正确布局方式(类似于 Autolayout)

Posted

技术标签:

【中文标题】SwiftUI 的正确布局方式(类似于 Autolayout)【英文标题】:Correct way to layout SwiftUI (similar to Autolayout) 【发布时间】:2020-07-08 17:34:53 【问题描述】:

问题:

我正在努力使用 SwiftUI 有效地布局视图。 我对 UIKit 和 Autolayout 非常熟悉,并且总是觉得它很直观。

我知道 SwiftUI 还很年轻,才刚刚开始,所以也许我期望太高了,但举个简单的例子:

假设我有 HStackText() 视图。

|--------------------------------|
| Text("static") Text("Dynamic") |  
|________________________________|

当我有 dynamic 内容时,static 文本字符串会随着 HStack 的大小发生变化,而 Text("Dynamic") 发生变化...

我尝试了很多东西,Spacers()Dividers(),查看了使用PreferenceKeys (link)、Alignment Guides (link) 的方法

最接近答案的似乎是对齐指南,但它们很复杂。

复制 Autolayout 将视图基本锚定到屏幕边缘附近并正确布局而不跳动的典型方法是什么?

我想锚定静态文本“纬度”,这样它就不会跳来跳去。

还有其他示例,因此我们将不胜感激就如何最好地进行布局提供更一般的答案...

有了自动布局,我觉得我选择了一切顺利。使用 SwiftUI 就像是彩票。

示例,显示“纬度”一词随着坐标的变化而跳跃:

示例,代码:

HStack 
    Text("Latitude:")
    Text(verbatim: "\(self.viewModelContext.lastRecordedLocation().coordinate.latitude)")

当我的观点有变化/动态的背景时,我真的很挣扎。所有 WWDC 视频中显示的静态内容都可以正常工作。

潜在解决方案:

像这样使用HStack

HStack(alignment: .center, spacing: 20) 
    Text("Latitude:")
    Text(verbatim: "\(self.viewModelContext.lastRecordedLocation().coordinate.latitude)")
    Spacer()

.padding(90)

结果很好锚定,但我讨厌幻数。

【问题讨论】:

你能提供可重现的例子吗?我不清楚什么是“跳来跳去”以及在什么情况下...... @Asperi 确定只是一秒钟.. 更新了@Asperi .font(.monospacedDigit()) (我知道这不能回答更深层次的问题,而且我知道有很多情况它不能解决,这就是为什么我不提供它作为答案,但根据我的经验,这确实是数量惊人的案例的答案。) 谢谢罗伯!欣赏它。 .monospacedDigit() 现在似乎是 .font(.system(.body, design: .monospaced)) 【参考方案1】:

正如您所发现的,第一部分是您需要决定您想要什么。在这种情况下,您似乎需要左对齐(基于您的填充解决方案)。这很好:

    HStack 
        Text("Latitude:")
        Text(verbatim: "\(randomNumber)")
        Spacer()
    

这将使 HStack 与其包含的视图一样宽,并将文本推到左侧。

但是从你后来的 cmets 看来,你似乎不希望它在最左边。在这种情况下,你必须准确地决定你想要什么。添加.padding 可以让您将其从左侧移入(可能仅通过添加.leading),但您可能希望将其与屏幕尺寸相匹配。

这是一种方法。重要的是要记住 HStack 的基本算法,即给每个人最小的空间,然后将剩余空间分配给灵活的视图。

    HStack 
        HStack 
            Spacer()
            Text("Latitude:")
        
        HStack 
            Text(verbatim: "\(randomNumber)")
            Spacer()
        
    

外部 HStack 有 2 个孩子,所有孩子都可以灵活调整到最低限度,因此如果可以容纳,它会为每个孩子提供等量的空间(总宽度的 1/2)。

(我最初是用 2 个额外的 Spacers 做的,但我忘记了 Spacers 似乎有特殊处理才能最后获得它们的空间。)

问题是如果randomNumber 太长会怎样?正如所写,它会换行。或者,您可以添加 .fixedSize() 以阻止其换行(并将 Latitude 向左推以使其适合)。或者你可以添加.lineLimit(1) 来强制截断。这取决于你。

但重要的是添加了灵活的 HStack。如果每个孩子都很灵活,那么他们都会得到相同的空间。

如果您想将事物强制为三分之一或四分之一,我发现您需要添加除 Spacer 之外的其他东西。例如,这将给出纬度和可用空间的 1/4 而不是 1/2(注意添加 Text("")):

    HStack 
        HStack 
            Text("")
            Spacer()
        
        HStack 
            Spacer()
            Text("Latitude:")
        
        HStack 
            Text(verbatim: "\(randomNumber)")//.lineLimit(1)
            Spacer()
        
        HStack 
            Text("")
            Spacer()
        
    

在我自己的代码中,我经常做这种事情,我有类似的东西

struct RowView: View    
    // A centered column
    func Column<V: View>(@ViewBuilder content: () -> V) -> some View 
        HStack 
            Spacer()
            content()
            Spacer()
        
    

    var body: some View 
        HStack 
            Column  Text("Name") 
            Column  Text("Street") 
            Column  Text("City") 
        
    

【讨论】:

超级,谢谢 Rob。真的很感激。我看到很多都是巧妙地使用垫片 :) 这个答案中的好技巧。

以上是关于SwiftUI 的正确布局方式(类似于 Autolayout)的主要内容,如果未能解决你的问题,请参考以下文章

如何以正确的方式在 SwiftUI 中使用颜色? (特别适用于具有 Light 和 DarkMode 的应用程序)

SwiftUI 列表中某些行的布局问题

带有部分的 SwiftUI 动态列表无法正确布局

SwiftUI 中的 List 是不是复用类似于 UITableView 的单元格?

减少部分 SwiftUI 之间的表单间距

在 TVOS 上的 SwiftUI 布局中苦苦挣扎