1

With navigationTitle we can set a specific Title to the navigation view ex: "Rows" .

So when the view is loaded we get large text title and when it scrolled the title font reduces and aligns to the center.

However is it possible to have a custom view in place of title text? And have the same effect of large to small text transition when scrolled?

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                ForEach(0..<100, id: \.self) { index in
                    Text("Row Item \(index)")
                }
            }.listStyle(.plain)
            .padding()
            .navigationTitle("Rows")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Large Text when loaded

enter image description here

Small Text when scrolled.

enter image description here

NNikN
  • 3,720
  • 6
  • 44
  • 86

1 Answers1

0

I came across the same problem recently. Here is what I came up with:

import SwiftUI

struct LabeledScrollView<Content: View, Label: View>: View {
    /// Scale of the label when shown outside of the toolbar
    let labelScale: Double = 1.4
    @ViewBuilder var content: Content
    @ViewBuilder var label: Label
    @State var showToolbarTitle: Bool = false
    var body: some View {
        ScrollView {
            LazyVStack(alignment: .leading) {
                label
                .scaleEffect(labelScale, anchor: .topLeading)
                .overlay {
                    GeometryReader { geo in
                        EmptyView()
                            .onChange(of: geo.frame(in: .named("container"))) { newValue in
                                /// 5.0 constant value is about the offset of text from the bottom of the label view - it ensures the toolbar label show right when the text moves out of view behind the toolbar - can remove if there is not any space between the bottom of the view and the content inside
                                let heightOfViewShowing = ((newValue.height * labelScale) + newValue.origin.y) - 5.0
                                if heightOfViewShowing <= 0.0 {
                                    withAnimation {
                                        showToolbarTitle = true
                                    }
                                } else {
                                    withAnimation {
                                        showToolbarTitle = false
                                    }
                                }
                            }
                    }
                }
                .padding(.bottom)
                content
            }
            .padding()
        }
        .toolbar {
            ToolbarItem(placement: .principal) {
                label
                .opacity(showToolbarTitle ? 1.0 : 0.0)
            }
        }
        .navigationBarTitleDisplayMode(.inline)
        .coordinateSpace(name: "container")
    }
}

struct LabeledScrollView_Previews: PreviewProvider {
    static var previews: some View {
        /// Preview placed in NavigationStack to show toolbar
        NavigationStack {
            LabeledScrollView {
                ForEach(1...3, id: \.self) { i in
                    Text("Item \(i)")
                }
            } label: {
                HStack {
                    Image(systemName: "checkmark")
                        .foregroundColor(.green)
                    Text("Custom Title View")
                }
                .font(.body.weight(.semibold))
            }
            .toolbar(.visible, for: .navigationBar)
        }
    }
}

While I used a LazyVStack for presenting content, you could adapt to use a List as well.

matthewjselby
  • 112
  • 2
  • 8