使用SwiftUI在NavigationView中使标题与ContentView对齐

3

我在SwiftUI中对齐导航标题和内容视图遇到了困难。使用集合视图流布局,在UIKit中很容易实现此操作。

n.b. 这是一个试验性项目,我正在使用iOS 15。

我尝试了许多不同的方法:

1. 使用列表

  • 在iPhone 12 Max上,这为我们提供了导航标题、标头和单元格之间正确的对齐,但在iPhone 12上却不行。

  • 滚动视图不像Apple Music应用程序那样延伸到边缘。

有没有一种方法可以解决滚动视图的问题?

iPhone 12 Max截图

enter image description here

iPhone 12截图

enter image description here

代码

  var body: some View {
    NavigationView {
      GeometryReader { proxy in
        let cellWidth = proxy.size.width * 0.4
        List {
          ForEach(0...10, id: \.self) { rowIndex in
            Section(header: Text("Unknown").font(.title2).redacted(reason: .placeholder)) {
              ScrollView(.horizontal, showsIndicators: false) {
                LazyHStack(alignment: .top, spacing: 16) {
                  ForEach(0...10, id: \.self) { index in
                    VStack(alignment: .leading, spacing: 2) {
                      RoundedRectangle(cornerRadius: 8)
                        .foregroundColor(.red)
                        .frame(width: cellWidth, height: cellWidth)

                      VStack(alignment: .leading, spacing: 2) {
                        Text("Example")
                          .foregroundColor(.primary)
                          .font(.subheadline)
                          .lineLimit(1)
                          .redacted(reason: .placeholder)
                        
                        Text("Example")
                          .foregroundColor(.secondary)
                          .font(.subheadline)
                          .lineLimit(1)
                          .redacted(reason: .placeholder)
                      }
                    }
                  }
                }
              }
            }
          }
        }
        .listStyle(InsetListStyle())
        .navigationBarTitle("Experimental")
      }
    }
  }

2. 使用VStack和padding...

  • 似乎没有一种直接的方法来使 VStack 和标题的第一个单元格对齐。例如,在 iPhone 12 Pro Max 上,20 的填充值可以实现正确的对齐,但在 iPhone 12 上,使用相同的填充值会有大约 2 像素的偏差。
  • 现在,水平滚动视图通过仅对第一个单元格进行填充而按预期工作。

iPhone 12 Max 屏幕截图

enter image description here

iPhone 12 屏幕截图

enter image description here

代码

  var bodyUsingVStack: some View {
    NavigationView {
      GeometryReader { proxy in
        let cellWidth = proxy.size.width * 0.4
        ScrollView {
          VStack(alignment: .leading, spacing: 16) {
            ForEach(0...10, id: \.self) { rowIndex in
              Section(
                header: Text("Unknown")
                  .font(.title2)
                  .padding(.leading, 20)
//                  .redacted(reason: .placeholder)
              ) {
                ScrollView(.horizontal, showsIndicators: false) {
                  LazyHStack(alignment: .top, spacing: 16) {
                    ForEach(0...10, id: \.self) { index in
                      VStack(alignment: .leading, spacing: 2) {
                        RoundedRectangle(cornerRadius: 8)
                          .foregroundColor(.red)
                          .frame(width: cellWidth, height: cellWidth)
                        
                        VStack(alignment: .leading, spacing: 2) {
                          Text("Example")
                            .foregroundColor(.primary)
                            .font(.subheadline)
                            .lineLimit(1)
                            .redacted(reason: .placeholder)
                          
                          Text("Example")
                            .foregroundColor(.secondary)
                            .font(.subheadline)
                            .lineLimit(1)
                            .redacted(reason: .placeholder)
                        }
                      }
                      .padding(.leading, index == 0 ? 22 : 0)
                    }
                  }
                }
              }
            }
          }
        }
        .listStyle(InsetListStyle())
        .navigationBarTitle("Experimental")
      }
    }
  }
  1. 其他想法
  • 使用对齐指南似乎不起作用
  • 想要扩展底层的UINavigation视图,以捕获标题的确切位置以计算正确的填充。
  • 测试所有iOS设备并预先确定每个设备的正确填充值...

肯定有更简单的方法,SwiftUI 提供了很多灵活性,但我们经常会因为做一些琐碎的事情而卡上数小时或数天。

3个回答

1

在测量所有 iPhone 型号(和 iPad)的填充时,我意识到如果屏幕宽度小于 414,则填充为 16,否则为 24。

import SwiftUI

public func margin(for width: Double) -> Double {
  guard !width.isZero else { return 0 }
  return width >= 414 ? 20 : 16
}

1

我找到了最简单的解决方案,无论设备如何都可以对齐所有内容(尚未在iPad上测试)。

它使用Introspect来捕获大标题视图标签的框架。

        .introspectNavigationController {
          let padding = $0.navigationBar
            .subviews.first { String(describing: type(of: $0)).hasSuffix("LargeTitleView") }?
            .subviews.first { $0 is UILabel }?
            .frame.origin.x ?? 0
          
          if !padding.isZero, self.padding != padding {
            self.padding = padding
          }
        }

代码

  @State private var padding: CGFloat = 0
  
  var body: some View {
    NavigationView {
      GeometryReader { proxy in
        let cellWidth = proxy.size.width * 0.4
        ScrollView {
          VStack(alignment: .leading, spacing: 16) {
            ForEach(0...10, id: \.self) { rowIndex in
              Section(
                header: Text("Unknown")
                  .font(.title2)
                  .padding(.leading, padding)
                  .redacted(reason: .placeholder)
              ) {
                ScrollView(.horizontal, showsIndicators: false) {
                  LazyHStack(alignment: .top, spacing: 16) {
                    ForEach(0...10, id: \.self) { index in
                      VStack(alignment: .leading, spacing: 2) {
                        RoundedRectangle(cornerRadius: 8)
                          .foregroundColor(.red)
                          .frame(width: cellWidth, height: cellWidth)
                        
                        VStack(alignment: .leading, spacing: 2) {
                          Text("Example")
                            .foregroundColor(.primary)
                            .font(.subheadline)
                            .lineLimit(1)
                            .redacted(reason: .placeholder)
                          
                          Text("Example")
                            .foregroundColor(.secondary)
                            .font(.subheadline)
                            .lineLimit(1)
                            .redacted(reason: .placeholder)
                        }
                      }
                      .padding(.leading, index == 0 ? padding : 0)
                    }
                  }
                }
              }
            }
          }
        }
        .listStyle(InsetListStyle())
        .navigationBarTitle("Experimental")
        .introspectNavigationController {
          let padding = $0.navigationBar
            .subviews.first { String(describing: type(of: $0)).hasSuffix("LargeTitleView") }?
            .subviews.first { $0 is UILabel }?
            .frame.origin.x ?? 0
          
          if !padding.isZero, self.padding != padding {
            self.padding = padding
          }
        }
      }
    }
  }

截图

enter image description here


-1

我会通过读取填充区域,然后使用它来扩展ScrollView的宽度来完成它。

下面是示例。更改如下:

  • Cells是一个单独的视图,因为否则它无法在合理的时间内编译(对我来说)。这可能因机器而异。
  • 添加了包含GeometryReaderbackground
  • ScrollView内部和外部添加了填充。
struct ContentView: View {
    @State private var padding: CGFloat?

    var body: some View {
        NavigationView {
            GeometryReader { proxy in
                let cellWidth = proxy.size.width * 0.4
                List {
                    ForEach(0...10, id: \.self) { rowIndex in
                        Section(header: Text("Unknown").font(.title2).redacted(reason: .placeholder)) {
                            ScrollView(.horizontal, showsIndicators: false) {
                                Cells(cellWidth: cellWidth)
                                    .background {
                                        if padding == nil {
                                            GeometryReader { geo in
                                                Color.clear.onAppear {
                                                    padding = geo.frame(in: .global).minX
                                                }
                                            }
                                        }
                                    }
                                    .padding(.horizontal, padding ?? 0)
                            }
                            .padding(.horizontal, -(padding ?? 0))
                        }
                    }
                }
                .listStyle(InsetListStyle())
                .navigationBarTitle("Experimental")
            }
        }
        .navigationViewStyle(.stack)
    }
}

struct Cells: View {
    let cellWidth: CGFloat

    var body: some View {
        LazyHStack(alignment: .top, spacing: 16) {
            ForEach(0...10, id: \.self) { index in
                VStack(alignment: .leading, spacing: 2) {
                    RoundedRectangle(cornerRadius: 8)
                        .foregroundColor(.red)
                        .frame(width: cellWidth, height: cellWidth)

                    VStack(alignment: .leading, spacing: 2) {
                        Text("Example")
                            .foregroundColor(.primary)
                            .font(.subheadline)
                            .lineLimit(1)
                            .redacted(reason: .placeholder)

                        Text("Example")
                            .foregroundColor(.secondary)
                            .font(.subheadline)
                            .lineLimit(1)
                            .redacted(reason: .placeholder)
                    }
                }
            }
        }
    }
}

结果:

Result


也许我的问题有点令人困惑...目标是拥有大标题视图、垂直堆栈和水平堆栈的第一个项目对齐,同时允许水平滚动视图占据整个宽度。苹果的音乐应用程序可以做到这一点。请看我在自己的尝试性答案中发布的截屏。祝好! - Mycroft Canner
@MycroftCanner 是的,这个答案只是使用了每个元素的默认间距。我现在完全理解你想要什么了 - 我相信只有通过 Introspect 才能实现,因为我不知道其他获取导航标题间距的方法。 - George
谢谢你的帮助……我为此苦恼了两天。 - Kugutsumen
@Kugutsumen 不幸的是,在SwiftUI中有些东西(目前)并不是很可定制化,这时我也不得不使用Introspect。然而,如果有人想要像这样使ScrollView全宽,我会将这个答案保持可见。 - George

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接