在 SwiftUI 中添加元素时如何获取列表的偏移量

How to get the List's offset when adding an element in SwiftUI

我想获取列表的偏移量来实现分页。所以,我写了如下。

import SwiftUI

struct ContentView: View {
  @State var list = ["item1" ,"item2" ,"item3", "item4" ,"item5" ,"item6" ,"item7" ,"item8" ,"item9" ,"item10" ,"item11" ,"item12"]
  @State private var offset: CGFloat = 0
  
  var body: some View {
    NavigationView {
      GeometryReader { outsideProxy in
        List {
          ForEach(list, id: \.self) { item in
            ZStack {
              GeometryReader { insideProxy in
                Color.clear
                  .preference(key: ScrollOffsetPreferenceKey.self, value: [outsideProxy.frame(in: .global).minY - insideProxy.frame(in: .global).minY])
              }
              NavigationLink("\(item), \(self.offset)") {
                Text("\(item), \(self.offset)")
              }
              .frame(height: 100)
            }
            .listRowInsets(EdgeInsets(top: 5, leading: 0, bottom: 5, trailing: 3))
          }
        }
        .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
          self.offset = value[0]
        }
        .listStyle(PlainListStyle())
        .navigationBarTitleDisplayMode(.inline)
        .navigationTitle(Text("title"))
        .toolbar(content: {
          ToolbarItem(placement: .bottomBar) {
            HStack {
              Button("Add") {
                list.insert("hello_\(Date.now.description)", at: 0)
              }
              Spacer()
              Text("\(offset)")
            }
          }
        })
      }
    }
  }
}

struct ScrollOffsetPreferenceKey: PreferenceKey {
  typealias Value = [CGFloat]
  
  static var defaultValue: [CGFloat] = [0]
  
  static func reduce(value: inout [CGFloat], nextValue: () -> [CGFloat]) {
    value.append(contentsOf: nextValue())
  }
}

但是,当我按“添加”按钮添加元素时,offset 值发生了变化。 我不希望 offset 更新,因为我想在列表顶部进一步向上滚动时添加元素。

谁能告诉我如何添加元素而不移动 offset

谢谢,

增加220419

我参考

写了下面的代码
  var body: some View {
    NavigationView {
      List {
        ScrollView {
          VStack {
            ForEach(list, id: \.self) { item in
              NavigationLink("\(item), \(self.offset)") {
                Text("hello\(item), \(self.offset)")
              }
              .frame(height: 100)
              .onAppear {
                print("appear: \(item)")
                print(offset)
              }
              .listRowInsets(EdgeInsets(top: 5, leading: 0, bottom: 5, trailing: 3))
            }
          }
          .background(GeometryReader {
            Color.clear.preference(key: ViewOffsetKey.self, value: -[=11=].frame(in: .named("scroll")).origin.y)
          })
          .onPreferenceChange(ViewOffsetKey.self) {
            self.offset = [=11=]
          }
        }
        .navigationBarTitleDisplayMode(.inline)
        .navigationTitle(Text("title"))
        .toolbar(content: {
          ToolbarItem(placement: .bottomBar) {
            HStack {
              Text("\(thre)")
              Spacer()
              Button("Add") {
                list.insert("hello_\(Date.now.description)", at: 0)
              }
              Spacer()
              Text("\(offset)")
            }
          }
        })
      }
      .coordinateSpace(name: "scroll")
      .listStyle(PlainListStyle())
    }
  }
  var body: some View {
    NavigationView {
      List {
        ForEach(list, id: \.self) { item in
          NavigationLink("\(item), \(self.offset)") {
            Text("hello\(item), \(self.offset)")
          }
          .frame(height: 100)
          .onAppear {
            print("appear: \(item)")
            print(offset)
          }
          .listRowInsets(EdgeInsets(top: 5, leading: 0, bottom: 5, trailing: 3))
        }
        .background(GeometryReader {
          Color.clear.preference(key: ViewOffsetKey.self, value: -[=12=].frame(in: .named("scroll")).origin.y)
        })
        .onPreferenceChange(ViewOffsetKey.self) {
          self.offset = [=12=]
        }
      }
      .coordinateSpace(name: "scroll")
      .navigationBarTitleDisplayMode(.inline)
      .navigationTitle(Text("title"))
      .toolbar(content: {
        ToolbarItem(placement: .bottomBar) {
          HStack {
            Text("\(thre)")
            Spacer()
            Button("Add") {
              list.insert("hello_\(Date.now.description)", at: 0)
            }
            Spacer()
            Text("\(offset)")
          }
        }
      })
      .listStyle(PlainListStyle())
      .onChange(of: offset) { newValue in
        if lock { return }
        self.lock = true
        if newValue < -105 {
          DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
            list.insert("hello_\(Date.now.description)", at: 0)
            self.thre = newValue
            self.lock = false
          }
        } else {
          self.lock = false
        }
      }
    }
  }

最后还是用了Asperi的idea。如下所示。

    var body: some View {
    NavigationView {
      ScrollView {
        LazyVStack {
          ForEach(list, id: \.self) { item in
            NavigationLink("\(item), \(self.offset)") {
              Text("hello\(item), \(self.offset)")
            }
            .frame(height: 100)
            .onAppear {
              print("appear: \(item)")
              print(offset)
            }
            .listRowInsets(EdgeInsets(top: 5, leading: 0, bottom: 5, trailing: 3))
          }
        }
        .background(GeometryReader {
          Color.clear.preference(key: ViewOffsetKey.self, value: -[=10=].frame(in: .named("scroll")).origin.y)
        })
        .onPreferenceChange(ViewOffsetKey.self) {
          self.offset = [=10=]
        }
      }
      .navigationBarTitleDisplayMode(.inline)
      .navigationTitle(Text("title"))
      .toolbar(content: {
        ToolbarItem(placement: .bottomBar) {
          HStack {
            Text("\(thre)")
            Spacer()
            Button("Add") {
              list.insert("hello_\(Date.now.description)", at: 0)
            }
            Spacer()
            Text("\(offset)")
          }
        }
      })
      .coordinateSpace(name: "scroll")
      .listStyle(PlainListStyle())
    }
  }