Search code examples
listswiftuinavigationview

SwiftUI Why navigationTitle does not scroll with List


There is a toolbar button to change the viewStyle in ContentView.

When the viewStyle is changed, MyPage will regenerate the List based on the viewStyle. However, after generation, the distance between this List and the navigationTitle will automatically increase. Additionally, the navigationTitle originally scrolled with the List, but after changing the viewStyle, it will remain stationary.

What went wrong? Thanks.

import SwiftUI

struct ContentView: View  {
    @State private var viewStyle: ViewStyle = .bold
    
    var body: some View{
        NavigationView {
            TabView {
                MyPage(viewStyle: $viewStyle)
                    .tabItem {
                        Label("Today", systemImage: "1.square.fill")
                    }
                    .tag(0)
            }
            .navigationTitle("title")
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button(action: {
                        viewStyle = viewStyle.next
                    }) {
                        Image(systemName: "pencil")
                    }
                }
            }
        }
    }
}
struct MyPage: View {
    @Binding var viewStyle: ViewStyle
    
    var body: some View {
        switch viewStyle {
        case .bold:
            List{
                ForEach(0..<4, id: \.self){ index in
                    VStack{
                        Label("bold", systemImage: "pencil")
                    }
                }
            }
        default:
            List{
                ForEach(0..<3, id: \.self){ index in
                    VStack{
                        Label("italic", systemImage: "pencil")
                    }
                }
            }
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
enum ViewStyle{
    case bold
    case italic
    var next: ViewStyle{
        switch self {
        case .bold: return .italic
        case .italic: return .bold
        }
    }
}

Solution

  • This happens when you replace the List. Try keeping the List and just replace the content:

    struct MyPage: View {
        @Binding var viewStyle: ViewStyle
    
        var body: some View {
            List { // 👈 Move this out here
                switch viewStyle {
                case .bold:
                    ForEach(0..<4, id: \.self){ index in
                        VStack{
                            Label("bold", systemImage: "pencil")
                        }
                    }
                default:
                    ForEach(0..<3, id: \.self){ index in
                        VStack{
                            Label("italic", systemImage: "pencil")
                        }
                    }
                }
            }
        }
    }
    
    ✅ Thats it!

    💡 More enhancements:

    Always try to minimize changes to prevent unexpected rendering and behavior. For example, here you could have just changed the title and count instead of the whole list! Something roughly like this:

    struct MyPage: View {
        @Binding var viewStyle: ViewStyle
        var range: Range<Int> {
            switch viewStyle {
            case .bold: (0..<4)
            default: (0..<3)
            }
        }
        var title: LocalizedStringKey {
            switch viewStyle {
            case .bold: "bold"
            default: "italic"
            }
        }
    
        var body: some View {
            List {
                ForEach(range, id: \.self) { index in
                    VStack {
                        Label(title, systemImage: "pencil")
                    }
                }
            }
        }
    }