Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Aligning the NavigationView's title with the ContentView in SwiftUI

I'm having difficulties aligning the navigation title with my content view in SwiftUI. This operation is trivial to do in UIKit especially using collection view flow layout.

n.b. This is for an experimental project and I'm using iOS 15.

I've tried many different approaches :

1. Using a list

  • This gives us the right alignment between the navigation title, the header and cells on the iPhone 12 Max but not on the iPhone 12

  • The scroll view doesn't extend over the edges as it would in the Apple Music app.

Is there a way to fix the scroll view?

iPhone 12 Max screenshot

enter image description here

iPhone 12 Screenshot

enter image description here

code

  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. Using a VStack and padding ...

  • There doesn't seem to be a straightforward way to align the VStack and first cell with the title. For example on an iPhone 12 Pro Max, a padding of 20 gives the right alignment and on an iPhone 12 we will be off by 2 pixels or so using the same padding.
  • Now the horizontal scrollviews behave as expected by padding only the first cell.

iPhone 12 Max Screenshot

enter image description here

iPhone 12 Screenshot

enter image description here

code

  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. Other ideas
  • Using alignment guides doesn't seem to work
  • Tempted to extend the underlying UINavigation view and capture the exact position of the title to calculate the correct padding.
  • Test all iOS devices and predetermine the correct padding value for each device...

There must be a simpler way and it is crazy that SwiftUI allows so much flexibility and yet we often get stuck for hours or days trying to do trivial things.

like image 666
Mycroft Canner Avatar asked Feb 03 '26 19:02

Mycroft Canner


1 Answers

This is the simplest solution I found in which everything is aligned regardless of the device (not tested on iPad yet).

It uses Introspect to capture the frame of the large title view's label.

        .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
          }
        }

Code

  @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
          }
        }
      }
    }
  }

Screenshot

enter image description here

like image 111
Mycroft Canner Avatar answered Feb 05 '26 08:02

Mycroft Canner



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!