Mycroft Canner
Mycroft Canner

Reputation: 1997

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

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

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

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.

Upvotes: 3

Views: 1107

Answers (3)

Mycroft Canner
Mycroft Canner

Reputation: 1997

Measuring the padding on all iPhone models (and iPad) I realised that if the screen width is less than 414, padding is 16 and 24 otherwise.

import SwiftUI

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

Upvotes: 1

Mycroft Canner
Mycroft Canner

Reputation: 1997

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

Upvotes: 1

George
George

Reputation: 30341

I would do it by reading the padding, then using that to extend the width of the ScrollView.

Example below. Changes:

  • Cells is a separate view because otherwise it couldn't compile in reasonable time (for me). Would change from machine-to-machine.
  • Added background containing the GeometryReader.
  • Added padding inside and outside of 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:

Result

Upvotes: -1

Related Questions