将 NavigationView 的标题与 SwiftUI 中的 ContentView 对齐

Aligning the NavigationView's title with the ContentView in SwiftUI

我很难将导航标题与 我在 SwiftUI 中的内容视图。这个操作在 UIKit 中很简单,尤其是使用 collection 视图流布局。

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

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

1。使用列表

有没有办法修复滚动视图?

iPhone 12 最大截图

iPhone 12 截图

代码

  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 和填充 ...

iPhone 12 最大截图

iPhone 12 截图

代码

  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. 其他想法

必须有一种更简单的方法,SwiftUI 提供如此多的灵活性真是太疯狂了,但我们经常在尝试做一些琐碎的事情时陷入数小时或数天的困境。

我会通过读取填充来完成,然后使用它来扩展 ScrollView.

的宽度

示例如下。变化:

  • Cells 是一个单独的视图,否则它无法在合理的时间内编译(对我而言)。会因机器而异。
  • 已添加 background,其中包含 GeometryReader
  • 内添加了填充,在 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)
                    }
                }
            }
        }
    }
}

结果:

这是我找到的最简单的解决方案,无论设备如何,一切都对齐(尚未在 iPad 上测试)。

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

        .introspectNavigationController {
          let padding = [=10=].navigationBar
            .subviews.first { String(describing: type(of: [=10=])).hasSuffix("LargeTitleView") }?
            .subviews.first { [=10=] 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 = [=11=].navigationBar
            .subviews.first { String(describing: type(of: [=11=])).hasSuffix("LargeTitleView") }?
            .subviews.first { [=11=] is UILabel }?
            .frame.origin.x ?? 0
          
          if !padding.isZero, self.padding != padding {
            self.padding = padding
          }
        }
      }
    }
  }

截图

测量所有 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
}