loadNext()
와loadPrevious()
가 있다.loadPrevious()
).List
로 윗 방향 pagination을 구현할때 발생할 수 있는 UI상의 문제와, 이걸 해결하는 방법에 대해 알아본다.List
로 15 ~ 30번 메세지를 보여주고, (message 30이 가장 최신 메세지다)import SwiftUI
struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
ScrollViewReader { proxy in
List(viewModel.dataList, id: \.self) { number in
Text("Message \(number)")
.padding()
.id(number)
}
.onAppear {
proxy.scrollTo(viewModel.dataList.last, anchor: .bottom) // show last row first
}
}
}
}
class ViewModel: ObservableObject {
@Published var dataList = Array(15..<30) // initially, there are messages 15 - 30
}
List
의 row 중 message 15가 있는 row가 보일때 (= 최상단 row가 화면에 나타날때), 예전 메세지를 로딩해와야한다.import SwiftUI
struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
ScrollViewReader { proxy in
List(viewModel.dataList, id: \.self) { number in
Text("Message \(number)")
.padding()
.id(number)
// 최상단 row에 스크롤이 도달하면 loadPrevious 호출.
.onAppear {
if number == viewModel.dataList.first {
print("Message \(number) appeared")
viewModel.loadPrevious()
}
}
}
.onAppear {
proxy.scrollTo(viewModel.dataList.last, anchor: .bottom)
}
}
}
}
class ViewModel: ObservableObject {
@Published var dataList = Array(15..<30)
var hasPrevious = true
// 이전 메세지들을 로딩해오는 함수.
func loadPrevious() {
if hasPrevious {
hasPrevious = false
let newData = Array(0..<15)
self.dataList = newData + self.dataList
}
}
}
// ...
List(...)
// ...
.onChange(of: viewModel.dataList) { oldValue, newValue in
proxy.scrollTo(oldValue.first, anchor: .top)
}
스크롤이 message 0으로 점프되는 이유:
loadPrevious()
호출 -> viewModel.dataList
가 업데이트 됨 -> List가 re-render 됨 -> List가 re-render 되면서 List의 최상단 row부터 보여짐우리가 원하는 동작은 화면 꼭대기에 보이던 message 15가 계속 화면 꼭대기에서 보이는 것이다.
위 코드는 viewModel.dataList
가 업데이트 될때, 기존 viewModel.dataList
의 첫번째 data가 화면의 꼭대기에 위치하도록 List를 스크롤한다.
import SwiftUI
struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
ScrollViewReader { proxy in
List(viewModel.dataList, id: \.self) { number in
Text("Message \(number)")
.padding()
.id(number)
// pagination for prepending previous data
.onAppear {
if number == viewModel.dataList.first {
print("Message \(number) appeared")
viewModel.loadPrevious()
}
}
}
.onAppear {
proxy.scrollTo(viewModel.dataList.last, anchor: .bottom)
}
// prevent being scrolled to top row upon loading previous data
.onChange(of: viewModel.dataList) { oldValue, newValue in
proxy.scrollTo(oldValue.first, anchor: .top)
}
}
}
}
class ViewModel: ObservableObject {
@Published var dataList = Array(15..<30)
var hasPrevious = true
func loadPrevious() {
if hasPrevious {
hasPrevious = false
let newData = Array(0..<15)
self.dataList = newData + self.dataList
}
}
}