오늘은 SwiftUI를 활용해 Pagination 하는 법을 구현하였다. 이 페이지네이션은 앱 개발을 진행하면서 어떤 항목에서 필수로 들어가야하는 부분인 것 같다. UIKit을 통해서 구현할때는 조금 애를 먹었는데 SwiftUI는 보다 손쉽게 구현이 가능한 것 같다.
먼저 페이지 네이션이 필요한 이유는 간단하게 말하자면 성능 최적화에 있다.
대량의 데이터를 한번에 로드하면 앱 성능 저하와 메모리 사용량이 증가할 수 있다. 예를 들어 1000개의 리스트가 업데이트 되어야하는데 이용자가 보는건 10개 정도라면 나머지 990개는 보이지도 않는데 호출이 되어버린다고 했을때 쓸모없는 메모리 사용량이 많이 증가하고 그 만큼 앱 성능은 떨어질 수 있기 때문이다.
또한, 한번에 많은양을 로딩하는 것이 아닌 스크롤 하면서 점진적으로 데이터를 일정량만 호출하게 되면 로딩 시간도 단축되기 때문에 사용자 경험을 개선할 수 있다!
예시를 한번 만들어 보면서 어떤 방식으로 구현되는지 한번 알아보자!
먼저 리스트에 들어갈 데이터를 만들어 주기 위해 더미데이터를 사용하며, 각 데이터를 담아줄 수 있는 데이터 모델을 만들어 주었다.
import Foundation
struct Item {
let title: String
let description: String
}
그 다음 각 데이터 묶음마다 페이지로 나눠 단위 데이터를 호출해 주기 위해 각 아이템들을 페이지에 담아주는 모델도 별도로 구성한다.
struct PaginationItemData {
let page: Int
let isLast: Bool
let items: [Item]
}
extension PaginationItemData {
static let stub1: PaginationItemData = .init(
page: 1,
isLast: false,
items: [
.init(title: "1번 타이틀", description: "1번 설명"),
.init(title: "2번 타이틀", description: "2번 설명"),
.init(title: "3번 타이틀", description: "3번 설명"),
.init(title: "4번 타이틀", description: "4번 설명"),
.init(title: "5번 타이틀", description: "5번 설명"),
.init(title: "6번 타이틀", description: "6번 설명"),
.init(title: "7번 타이틀", description: "7번 설명"),
.init(title: "8번 타이틀", description: "8번 설명"),
.init(title: "9번 타이틀", description: "9번 설명"),
.init(title: "10번 타이틀", description: "10번 설명")
]
)
static let stub2: PaginationItemData = .init(
page: 2,
isLast: false,
items: [
.init(title: "11번 타이틀", description: "11번 설명"),
.init(title: "12번 타이틀", description: "12번 설명"),
.init(title: "13번 타이틀", description: "13번 설명"),
.init(title: "14번 타이틀", description: "14번 설명"),
.init(title: "15번 타이틀", description: "15번 설명"),
.init(title: "16번 타이틀", description: "16번 설명"),
.init(title: "17번 타이틀", description: "17번 설명")
]
)
static let stub3: PaginationItemData = .init(
page: 3,
isLast: true,
items: [
.init(title: "18번 타이틀", description: "18번 설명"),
.init(title: "19번 타이틀", description: "19번 설명"),
.init(title: "20번 타이틀", description: "20번 설명"),
.init(title: "21번 타이틀", description: "21번 설명"),
.init(title: "22번 타이틀", description: "22번 설명"),
.init(title: "23번 타이틀", description: "23번 설명"),
.init(title: "24번 타이틀", description: "24번 설명")
]
)
static func getItems(page: Int) -> PaginationItemData {
if page == 1 {
return PaginationItemData.stub1
} else if page == 2 {
return PaginationItemData.stub2
} else {
return PaginationItemData.stub3
}
}
}
현재는 목데이터를 구성해서 페이지를 나눠 해당하는 페이지만 업데이트 되도록 설정되었지만 추후 네트워크 API와 연동해서 데이터를 가져오는 방식으로 확장이 가능하다.
그 다음 해당 로직을 View에 보낼 수 있는 ViewModel을 구성해보자
class PaginationViewModel: ObservableObject {
@Published var items: [Item] = [] // 로드된 데이터
var currentPage: Int = 1
var isLastPage: Bool = false
var isLoading: Bool = false
@MainActor
func getItems() async {
guard !isLoading, !isLastPage else { return }
isLoading = true
let itemData = PaginationItemData.getItems(page: currentPage)
if currentPage == 1 {
self.items = itemData.items
} else {
self.items.append(contentsOf: itemData.items)
}
isLastPage = itemData.isLast
currentPage += 1
isLoading = false
}
}
현재 페이지, 가장 마지막 페이지, 로딩 중인 상태에 대한 프로퍼티를 만들어 준 뒤 Concurrency를 활용해 해당 데이터 호출을 비동기적으로 처리할 수 있도록 구현하였다.
async/await 를 바탕으로 비동기적으로 데이터를 로드하고, await으로 작업 완료를 기다리도록 한다.
@MainActor를 사용해 UI 업데이트와 관련된 작업은 항상 메인 스레드에서 실행될 수 있도록 로직에 별도로 구현해준다.
UI 스레드와 분리해서 데이터를 비동기적으로 로드해 UI가 블로킹 되지 않도록 처리한다.
또한 뷰에 데이터를 표현할때 모든 데이터가 다 떠있지 않게 하려면 그냥 VStack을 사용하면 나타날때 그려지는 것이 아니라 모든 데이터가 한번에 나오게 되기 때문에 그냥 VStack을 사용하면 안되고
LazyVStack을 사용해 ForEach로 반복을 돌며 뷰를 그려주는 형식으로 구현된다.
그럼 이렇게 페이지네이션 구현이 완료된다!! 오늘은 SwiftUI를 활용한 페이지네이션에 대해서 알아보았다 :)
'◽️ Programming > SwiftUI' 카테고리의 다른 글
SwiftUI에서 Charts를 구현해보자 (1) | 2024.11.18 |
---|---|
SwiftUI와 UIKit의 다른 생명주기 방식 (0) | 2024.10.22 |
[SwiftUI] onChange 역할을 알아보자 (0) | 2024.08.09 |
[SwiftUI] @StateObject , @ObservedObject, @EnviromentObject의 역할과 차이점을 자세하게 알아보자 (1) | 2024.07.23 |
[SwiftUI] State , Binding (0) | 2024.07.20 |