0

I want to get the list's offset to achieve pagination. So, I wrote like below.

  • ContentView.swift
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())
  }
}

However, when I press the Add button to add an element, the offset value shifts. I don't want the offset to update because I want to add elements when I scroll further up at the top of the List.

Can anyone tell me how to add elements and not shift the offset?

Thanks,

Addition 220419

I wrote a below code in reference to this

  • List and ScrollView(body only)
  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: -$0.frame(in: .named("scroll")).origin.y)
          })
          .onPreferenceChange(ViewOffsetKey.self) {
            self.offset = $0
          }
        }
        .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())
    }
  }
  • List only(don't work)
  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: -$0.frame(in: .named("scroll")).origin.y)
        })
        .onPreferenceChange(ViewOffsetKey.self) {
          self.offset = $0
        }
      }
      .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
        }
      }
    }
  }
tbt
  • 399
  • 3
  • 16
  • 1
    This can be helpful https://stackoverflow.com/a/62588295/12299030, or this one https://stackoverflow.com/a/65062892/12299030. – Asperi Apr 18 '22 at 13:36
  • @Asperi Thanks. However, I could do it if I used List and ScrollView as in the code I added to the question, but I could not achieve it with only List. I would like to do it with only List, but do you know how to do it? – tbt Apr 18 '22 at 15:24
  • What does `offset` represent? If you just want to do pagination (i.e fetch more from the data store) there are easier methods that don't involve geometry. – atultw Apr 18 '22 at 15:34
  • @atultw Thanks. The `offset` is trying to represent the scroll position of the List, and I would like to use it to achieve upward pagination of the List. – tbt Apr 19 '22 at 10:37

1 Answers1

0

Finally, I use Asperi's idea. Like below.

    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: -$0.frame(in: .named("scroll")).origin.y)
        })
        .onPreferenceChange(ViewOffsetKey.self) {
          self.offset = $0
        }
      }
      .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())
    }
  }
tbt
  • 399
  • 3
  • 16
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Apr 25 '22 at 17:21
  • This doesn't make much sense to me. How are you using `offset` to preserve the scroll offset when adding items on top? – damirstuhec Jun 24 '22 at 07:09