iOS 응용 (SwiftUI)
[iOS - SwiftUI] 2. List 형태 UI - pull to refresh, 페이지네이션 구현 방법 (List, refreshable, @Sendable)
jake-kim
2024. 4. 24. 01:33
1. List 형태 UI - @State를 활용하여 로딩 상태, 로드 상태, 실패 상태 띄우기
2. List 형태 UI - pull to refresh, 페이지네이션 구현 방법 (List, refreshable, pagination, @Sendable)
3. List 형태 UI - 검색된 결과 UI에 보여지게 하는 방법 (searchable)
복습) 1번 글까지 한 것
- @State로 선언된 프로퍼티를 활용하면 매우 쉽게 상태 표현이 가능
- 뷰 코드에서 이 프로퍼티에 관한 switch문을 활용하여 상태에 따라 뷰를 다르게 보여주는 코드를 준비해 놓고, 프로퍼티만 변경되면 자동으로 바인딩되어 뷰가 변경됨
- 코드: https://github.com/JK0369/ExListSwiftUI.git
struct ContentView: View {
enum ViewState {
case loading
case success
case failed
}
@State private var state = ViewState.loading
var body: some View {
Group {
switch state {
case .loading:
...
case .success:
...
case .failed:
...
}
}
...
}
}
List와 pull to refresh (refreshable)
- SwiftUI에서는 refreshable를 사용하면 간편하게 리프레시 구현이 가능
- 위 코드의 success부분에 List를 띄우기 위해서 필요한 데이터 Person 정의
struct Person: Identifiable {
let id: String
let age: Int
let name: String
}
- 이 데이터를 List에 뿌려주기
- List에는 rowContent 인자에 별도의 뷰를 주입해주어야 하므로 이 값을 정의 (closure형태로 구현해도 되지만 뷰를 나누기 위해서 PersonRowView로 따로 만들기)
struct ContentView: View {
...
@State private var items = [Person]()
var body: some View {
Group {
switch state {
case .loading:
...
case .success:
List(items, rowContent: <#T##(Person) -> RowContent#>)
...
}
}
...
}
}
- PersonRowView라는 별도의 뷰로 만들어놓으면 아래처럼 심플하게 표현이 가능
List(items, rowContent: PersonRowView.init)
- PersonRowView 정의
import SwiftUI
struct PersonRowView: View {
let item: Person
var body: some View {
NavigationLink {
Text("detail")
} label: {
HStack {
Text("age: \(item.age), name: \(item.name)")
.font(.caption.weight(.heavy))
}
}
}
}
List형태의 뷰 완성)
struct ContentView: View {
...
@State private var items = [Person(id: "1", age: 10, name: "jake")]
@State private var state = ViewState.loading
var body: some View {
Group {
switch state {
...
case .success:
List(items, rowContent: PersonRowView.init)
...
}
}
}
- refershable을 추가하여, 여기에 값을 더하는 코드를 추가
- loadMoreItems는 @Sendable타입으로 정의 (@Sendable 타입은 아래에서 설명)
List(items, rowContent: PersonRowView.init)
.refreshable(action: loadMoreItems)
@Sendable func loadMoreItems() async {
try? await Task.sleep(nanoseconds: 500_000_000)
let mocks = [
Person(id: Date.now.description, age: 10, name: "jake1"),
Person(id: Date.now.description + "1", age: 20, name: "jake2"),
Person(id: Date.now.description + "2", age: 30, name: "jake3"),
Person(id: Date.now.description + "3", age: 40, name: "jake4"),
Person(id: Date.now.description + "4", age: 50, name: "jake5"),
Person(id: Date.now.description + "5", age: 60, name: "jake6"),
Person(id: Date.now.description + "6", age: 20, name: "jake7"),
Person(id: Date.now.description + "7", age: 30, name: "jake8"),
Person(id: Date.now.description + "8", age: 40, name: "jake9"),
Person(id: Date.now.description + "9", age: 50, name: "jake10"),
Person(id: Date.now.description + "10", age: 60, name: "jake11"),
Person(id: Date.now.description + "11", age: 20, name: "jake12"),
Person(id: Date.now.description + "12", age: 30, name: "jake13"),
Person(id: Date.now.description + "13", age: 40, name: "jake14"),
Person(id: Date.now.description + "14", age: 50, name: "jake15"),
Person(id: Date.now.description + "15", age: 60, name: "jake16"),
]
items.append(contentsOf: mocks)
}
- @Sendable타입이란?
- concurrency 환경에서 value type과 같은 곳에 동시에 수정과 읽기가 일어나면 크래시가 발생하는데, 이를 안전하게 처리해주는 것
- 개발자 입장에서 concurrency 상황에서의 문제들에 관해 고민할 필요 없도록 @Sendable을 선언하여 데이터 업데이트를 편하게 사용
- pull to refresh 완성
페이지네이션 구현 ()
- Swift에서 UITableView를 사용할 때 페이지네이션 구현 방법
- 내장된 델리게이트 함수 prefetchRows 사용
- contentSize.height와 contentOffset.y을 계산하여, contentSize.height값이 얼마 남지 않았을때 loadMoreData 호출
- UITableView에서 빈 footerView를 놓고 이 footerView가 보일 때 loadMoreData와 같은 함수 호출
- SwiftUI에서도 위 방법들 모두 다 가능하지만 가장 간편한 방법은 UITableView의 footerView를 놓는 것과 유사하게 마지막 Row가 보일때 loadMoreData를 호출하는 것
- SwiftUI의 onAppear를 사용하면 매우 쉽게 구현이 가능
- List 코드에서 클로저를 열고, 위에서 구현했던 PersonRowView 삽입
List(items) { person in
PersonRowView(item: person)
}
- onAppear를 활용하여 마지막 id의 아이템이 보일 경우 loadMoreData를 호출
List(items) { person in
PersonRowView(item: person)
.onAppear {
if person.id == items.last?.id {
Task {
await loadMoreItems()
}
}
}
}
페이지네이션 완성)
* 전체 코드: https://github.com/JK0369/ExListSwiftUI
* 참고