Home > Net >  Trying to understand how .id(_:) works in SwiftUI
Trying to understand how .id(_:) works in SwiftUI

Time:11-21

I am trying to use .id(_:) to do some programmatic scrolling with a ForEach, such that a Form will scroll each time a new section is added to it using the ForEach. However, I am getting some undesirable behaviour and I would like to understand the mechanics of what is causing said behaviour. As you can see in the screenshot, the third text view isn't even showing up. When I remove the id method, all three Text views show up as you'd expect.

import SwiftUI

struct ContentView: View {
    @State private var array: [Int] = [1]
    var body: some View {
        ScrollViewReader { p in
            NavigationView {
            Form {
                ForEach(self.array, id: \.self) { num in
                    Section {
                        Text("Hello")
                        Text("My name is slim shady")
                        Text("number \(num)")
                    }
                    .id(num)
                    .onChange(of: array.count) { count in
                        p.scrollTo((count   1), anchor: .top)
                    }
                }
            }
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    Button("add") {
                        array.append(array.count   1)
                    }
                }
            }
            }
        }
    }
}

Any help would be greatly appreciated! Cheers.

Simulator screenshot

CodePudding user response:

You have a couple of issues going on here. I have never tried to put a Section inside a ForEach like that, but they obviously do not play well. I removed it and used a VStack instead. I am not sure if you can make new sections like that, but that is another question for another day.

The issue you were having with your ScrollTo is a misunderstanding of what id you were actually using. When you instantiate a ForEach with an array that is not otherwise identifiable, you use .self as the id. However, that means you are keeping track of the elements themselves, not the contents of each element. So, when you tried to put an int into your scrollTo(), nothing happened because an int is not an element of your array. What you need to do is supply an element like this:

struct ContentView: View {
    @State private var array: [Int] = [1]
    var body: some View {
        ScrollViewReader { p in
            NavigationView {
                Form {
                    ForEach(self.array, id: \.self) { num in
                        VStack(alignment: .leading) {
                            Text("Hello")
                            Text("My name is slim shady")
                            Text("number \(num)")
                        }
                        // num is an element of your array, not an int
                        .id(num)
                        .onChange(of: array.count) { count in
                            // Supply scrollTo with an array element here
                            p.scrollTo(array.last, anchor: .top)
                        }
                    }
                }
                .toolbar {
                    ToolbarItem(placement: .bottomBar) {
                        Button("add") {
                            array.append(array.count   1)
                        }
                    }
                }
            }
        }
    }
}

CodePudding user response:

Just in case anyone happens to read this, after a bunch of experimenting I have been able to make this work. It appears as though the issue that I posted in my screenshots was being caused by creating sections inside of the ForEach, as Yrb mentioned. However, I tried a different initializer for creating sections and it seems to work as long as you provide a view for the section header. I have no idea why however, if anyone has any insight please share.

struct ContentView: View {
    @State private var array: [Int] = [1]
    var body: some View {
        ScrollViewReader { p in
            NavigationView {
                Form {
                    ForEach(self.array, id: \Int.self) { num in
                        Section {
                            TextVeiwshg(num: num)
                        } header: {
                            Text("section #\(num)")
                        }
                        .id(num)
                    }
                }
                .toolbar {
                    ToolbarItem(placement: .bottomBar) {
                        Button("add") {
                            array.append(array.count   1)
                            print(array)
                        }
                    }
                }
            }
            .onChange(of: array.count) { count in
                print(array.count)
                withAnimation {
                    p.scrollTo((count), anchor: .top)
                }
                print("scrolling to \(count)")
            }
        }
    }
}
  • Related