์ค๋์ ScrollView์ LazyVStack์ ํ์ฉํ์ฌ SwiftUI์์ ๋ฌดํ ์คํฌ๋กค์ ๊ตฌํํด๋ณด๋ ค๊ณ ํ๋ค.
์ฌ์ค, LazyVStack์ด ์กฐ๊ธ ์์ํ ์ ์๋ค.
LazyVStack์ ๋ง ๊ทธ๋๋ก Lazyํ๊ฒ VStack์ ๊ทธ๋ฆฐ๋ค๋ ๋๋์ผ๋ก,
VStack์ผ๋ก ๋ณด์ฌ์ค ํญ๋ชฉ์ด ์ค์ ๋ก UI์ ๋ณด์ฌ์ง ๋ ๋ ๋๋ง์ ์งํํ๋ View์ด๋ค.
๊ทธ๋ ๋ค๋ฉด ๊ธฐ์กด์ ์ฌ์ฉํ๋ VStack๊ณผ๋ ์ด๋ค ์ฐจ์ด๊ฐ ์์๊น?
ํ์์ ์ฌ์ฉํ๋ VStack์ ๋ทฐ๊ฐ ๋ณด์ฌ์ง ๋(onAppear) ๋ชจ๋ ํญ๋ชฉ์ ๋ ๋๋งํ๋ค.
๊ทธ๋ ๊ธฐ์ ScrollView + VStack ์กฐํฉ์ผ๋ก List๋ฅผ ๋ํ๋ธ๋ค๋ฉด ์ด๊ธฐ์ ๋ง์ ๋ฆฌ์์ค๋ฅผ ์๋ชจํ๊ฒ ๋๋ค.
์ ์ ๊ฐ์์ ๊ฐ๋จํ ํญ๋ชฉ๋ค์ ํ์ํ ๊ฒฝ์ฐ์๋ ์๊ด์ด ์์ง๋ง,
๋ง์ ๊ฐ์์ ํญ๋ชฉ๋ค์ ํ์ํ ๊ฒฝ์ฐ์๋ ์ ์ ํ์ง ๋ชปํ ๋ฐฉ๋ฒ์ผ ๊ฒ์ด๋ค.
๊ฐ๋ น, ์ค๊ณ ๊ฑฐ๋ ํ๋ซํผ ์ฑ์ด ์๋๋ฐ ์ฑ์ ์ผฐ์ ๋ ํน์ ์นดํ ๊ณ ๋ฆฌ์ ์๋ ๋ชจ๋ ํญ๋ชฉ์ ๊ฐ์ ธ์จ๋ค๊ณ ํด๋ณด์.
์์ฒญ๋ ๋ฆฌ์์ค๋ฅผ ์๋ชจํ๊ฒ ๋ ๊ฒ์ด๋ค. (์ฌ์ฉ์์ ์
๋ฃฐ๋ฌ ๋ฐ์ดํฐ๋, CPU ํน์ RAM๊ณผ ๊ฐ์ ์์์ด ๋ ์ ์๋ค.)
๊ทธ๋์ ์ด๊ธฐ์๋ 10๊ฐ, ํน์ 20๊ฐ์ ๊ฐ์ด ์ ์ ๊ฐ์์ ๋ฐ์ดํฐ๋ง ๋ณด์ฌ์ฃผ๊ณ
๋ง์ง๋ง ํญ๋ชฉ์ด ๋ณด์ฌ์ง๋ฉด ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ๋ ๋ฐฉ์์ธ ๋ฌดํ ์คํฌ๋กค ๋ฐฉ์์ผ๋ก ๊ตฌํํ๋ ๊ฒ์ด ์ข๋ค.
์ค์ ๋ก ๋ ๊ฐ์ง ๋ฐฉ์์ผ๋ก ๊ตฌํํ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด ๊ทธ ์ฐจ์ด๋ฅผ ๋ช ํํ๊ฒ ์ ์ ์๋ค.
onAppear์ print๋ฅผ ๋ฌ์์ฃผ๊ณ ํ ์คํธ๋ฅผ ์งํํ๋ค.
ํ์์ ์ฌ์ฉํ๋ VStack์ View๊ฐ ๋ณด์ฌ์ง ๋ ๋ชจ๋ ํญ๋ชฉ์ด ๋ ๋๋ง ๋๋ ๊ฒ์ ์ ์ ์๋ค. (์ค์ ๋ก๋ ๋ณด์ฌ์ง์ง ์๋๋ผ๋)
LazyVStack์ View๊ฐ ๋ณด์ฌ์ง ๋ ๋ชจ๋ ํญ๋ชฉ์ด ๋ ๋๋ง ๋์ง ์๊ณ , ์ค์ ๋ก ๋ณด์ฌ์ง๋ ๊ฒ๋ง ๋ ๋๋ง ๋๋ ๊ฒ์ ์ ์ ์๋ค.
๋ํ ์์ฃผ ์ฐ๋ List ์ญ์ Lazyํ๊ฒ ๋์ํ๋ค. (List๋ฅผ ์ ์ฉํ๋๋ก ํ์)
๊ทธ๋ผ ์ด์ ๋ณธ๋ก ์ผ๋ก ๋ค์ด๊ฐ์, ์ค์ ์ฝ๋๋ฅผ ๋ณด๋ฉด์ ๋ฌดํ์คํฌ๋กค์ ์ด๋ค ์์ผ๋ก ๊ตฌํํ๋์ง ์์๋ณด์.
์ฐ์ ํ์ผ ๊ตฌ์กฐ๋ ์ด๋ ๊ฒ ์ก์๊ณ , MVVM ํจํด์ผ๋ก ๊ตฌํํ๋ค.
1. Model
์ฐ์ Model์ ๊ฒฝ์ฐ, API๋ฅผ ํธ์ถํ์ ๋ ๋ฐ์์ค๋ Json์ ๊ตฌ์กฐ๋ฅผ ๊ทธ๋๋ก ๊ตฌํํด์คฌ๋ค.
import Foundation
struct Lecture: Codable {
let pagination: Pagination
let results: [Result]
}
struct Pagination: Codable {
let count: Int
let numPages: Int
let next: URL
enum CodingKeys: String, CodingKey {
case count
case numPages = "num_pages"
case next
}
}
struct Result: Codable, Identifiable {
let id: UUID = UUID() // List๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด identifiable ์ถ๊ฐ
let name: String
let media: Media
let teachers: String
let shortDesc: String
let startDisplay: String
enum CodingKeys: String, CodingKey {
case name
case teachers
case media
case shortDesc = "short_description"
case startDisplay = "start_display"
}
}
struct Media: Codable {
let image: ImagePerSize
}
struct ImagePerSize: Codable {
let raw: URL
let small: URL
let large: URL
}
์ฌ์ฉํ API์ ๋ํ ์ ๋ณด๋ ์๋ ๋งํฌ์์ ํ์ธํ ์ ์๋ค.
2. ViewModel
ViewModel์์๋ Lecture๋ฅผ ๋ด๊ณ ์๋ ๋ฆฌ์คํธ๋ฅผ @Published๋ก ์ ์ํ๋ค.
๋ํ ์ฑ์ ์ฒ์ ์ผฐ์ ๋ ๊ฐ์ข๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฉ์๋์ ์คํฌ๋กค์ ๋๊น์ง ๋ด๋ ธ์ ๋ ์ถ๊ฐ์ ์ผ๋ก ๊ฐ์ข๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฉ์๋๋ฅผ ์ ์ํ๋ค.
import Foundation
class LectureViewModel: ObservableObject {
@Published var items: [Result] = []
private var nextURL: String = ""
private var APIKEY: String = ""
init() {}
// ์ฑ์ ์ฒ์ ์ผฐ์ ๋ ๊ฐ์ข๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํ ๋ฉ์๋
func getLecturesOnServer() async throws {
let responseData: Lecture = try await WebService.shared.loadJson("https://apis.data.go.kr/B552881/kmooc/courseList?serviceKey=\(APIKEY)&Page=1&Org=FUNMOOC&Mobile=1")
DispatchQueue.main.async {
self.items = responseData.results
self.nextURL = "\(responseData.pagination.next)"
}
}
// ์คํฌ๋กค์ ๋๊น์ง ๋ด๋ ธ์ ๋ ์ถ๊ฐ์ ์ผ๋ก ๊ฐ์ข๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํ ๋ฉ์๋
func getLecturesOnServerAtFinishedScroll() async throws {
let responseData: Lecture = try await WebService.shared.loadJson(nextURL)
// ๊ธฐ์กด ๊ฐ์ข ๋ชฉ๋ก์ ์๋ก ๊ฐ์ ธ์จ ๊ฐ์ข ๋ชฉ๋ก์ ์ถ๊ฐ
DispatchQueue.main.async {
self.items += responseData.results
self.nextURL = "\(responseData.pagination.next)"
}
}
}
ํ๋กํผํฐ ์ค์ nextURL์ ๊ฒฝ์ฐ, json์์ ๋ค์ ๊ฐ์ข ๋ชฉ๋ก์ ํด๋นํ๋ URL์ ๋๊ฒจ์ฃผ๋๋ฐ ์ด๋ฅผ ํ์ฉํ๋ค.
3. WebService
WebService์์๋ API๋ฅผ ํธ์ถํ ๋ ์ฌ์ฉํ๋ ๋ฉ์๋๋ฅผ ์ ์ํด์คฌ๋ค.
import Foundation
class WebService {
static let shared = WebService()
// json์ Tํ์
์ ๋ฐ์ดํฐ๋ก ๋์ฝ๋ฉํ๋ ๋ฉ์๋
func loadJson<T: Decodable>(_ url: String) async throws -> T {
do {
let url = URL(string: url)!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(T.self, from: data)
} catch {
fatalError("Unable to parse data : \(error)")
}
}
}
ํ์ ์ ์๊ด์์ด ํด๋น ํด๋์ค์ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ์ ๋ค๋ฆญ์ ์ ์ฉํ๋ค.
4. View
ViewModel์์ @Published๋ก ์ ์ํ items๋ฅผ ํ์ฉํ๊ณ ,
์ฑ์ ์คํํ์ ๋๋ items์ ์ด๋ ํ ๋ฐ์ดํฐ๋ ๋ค์ด์์ง ์๊ธฐ์, ์ด ๋๋ ProgressBar๊ฐ ๋ณด์ฌ์ง๋๋ก ํ๋ค.
๋ํ ๋์์ API๋ฅผ ํธ์ถํ์ฌ ๊ฐ์ข ๋ชฉ๋ก์ ๊ฐ์ ธ์ค๊ฒ ํ์๊ณ
๊ฐ์ข ๋ชฉ๋ก์ ๊ฐ์ ธ์ค๋๋ฐ ์ฑ๊ณตํ๋ฉด items์ ๋ฐ์ดํฐ๊ฐ ๋ค์ด์๊ฒ ๋๊ณ , ์ดํ์๋ ProgressBar๊ฐ ์ฌ๋ผ์ง๋๋ก ํ๋ค.
ViewModel, ์๋ ObservableObject๋ฅผ ํ์ฉํ๋ฉด ์ด๋ฌํ ํํ์ ์ฑ์ ์ ๋ง ์์ฝ๊ฒ ๊ตฌํํ ์ ์๋ค. (SwiftUI ์งฑ์งฑ!! ๐)
import SwiftUI
struct LectureListView: View {
@EnvironmentObject var lectureVM: LectureViewModel
@State private var num: Int = 0
var body: some View {
if lectureVM.items.isEmpty {
ProgressView()
// ์ฑ์ ์ฒ์ ์คํํ์ ๋๋ง ProgressView๋ฅผ ๋ณด์ฌ์ค
.task {
do {
try await lectureVM.getLecturesOnServer()
} catch (let error) {
print("Unable to get data : \(error)")
}
}
} else {
ScrollView {
LazyVStack {
ForEach(lectureVM.items) { item in
LectureItemView(lecture: item)
.onAppear {
guard let index = lectureVM.items.firstIndex(where: { $0.id == item.id }) else { return }
if index % 10 == 9 {
Task {
do {
try await lectureVM.getLecturesOnServerAtFinishedScroll()
} catch (let error) {
print("Unable to get data : \(error)")
}
}
}
}
}
}
}
.navigationTitle("K-MOOC ๊ฐ์ข ๋ชฉ๋ก")
}
}
}
์์์ ์ค๋ช ํ๋ ๊ฒ์ฒ๋ผ LazyVStack์ ํน์ ์์ดํ ์ด UI์ ๊ทธ๋ ค์ง ํ์ด๋ฐ์ ๋ ๋๋ง์ ์งํํ๋ค.
์ด๋ฅผ ํ์ฉํด์ ๋ถ๋ฌ์จ ๊ฐ์ข ๋ชฉ๋ก์์ ๋ง์ง๋ง ๊ฐ์ข๊ฐ ๋ณด์ฌ์ง๋ฉด,
์๋กญ๊ฒ ๊ฐ์ข ๋ชฉ๋ก์ ๋ถ๋ฌ์ค๋๋ก ๊ตฌํํ๋ค.
๊ฐ์ข ๋ชฉ๋ก์ ๋ณด์ฌ์ฃผ๋ ScrollView์์ ํ๋์ ๊ฐ์ข๋ฅผ ๋ณด์ฌ์ฃผ๋ View์ธ, LectureItemView๋ ์ด๋ ๊ฒ ๊ตฌํํ๋ค.
import SwiftUI
struct LectureItemView: View {
var lecture: Result
var body: some View {
HStack {
AsyncImage(url: lecture.media.image.small) { image in
image
.resizable()
} placeholder: {
ProgressView()
.frame(width: 140)
}
.scaledToFill()
.frame(width: 140)
.clipped()
.cornerRadius(8)
VStack(alignment: .leading) {
Text(lecture.name)
.font(.body)
.fontWeight(.bold)
Spacer()
Text(lecture.teachers)
.font(.footnote)
.lineLimit(1)
Spacer()
Text(lecture.startDisplay)
.font(.footnote)
}
.padding(4)
Spacer()
}
.padding(8)
}
}
์ค์ ๊ตฌํ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์!
๋ง์ง๋ง ๊ฐ์ข๊ฐ ๋ณด์ฌ์ง ๋ API๋ฅผ ํธ์ถํ์ฌ 10๊ฐ์ ๊ฐ์ข๋ฅผ ๋ ๊ฐ์ ธ์ค๋ ๊ฒ์ ๋ณผ ์ ์๋ค!
'๐ป ๊ฐ๋ฐ > iOS' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[iOS / SwiftUI] MapKit, ์ค์๊ฐ์ผ๋ก ๋๋ก๋ช ์ฃผ์ ๋ณํํ๊ธฐ (2) | 2022.12.20 |
---|---|
[iOS / SwiftUI] OnAppear, OnDisappear๋ ์ธ์ ํธ์ถ๋ ๊น? (2) | 2022.12.01 |
[iOS / Swift] Swift ๋ฌธ์์ด ์ ๋ณตํ๊ธฐ (aka 'Character') (0) | 2022.11.03 |
[iOS / SwiftUI] ๋ค์ํ ์ํ ํ๋กํผํฐ๋ค์ ์์๋ณด์! (0) | 2022.10.24 |
[iOS / SwiftUI] ํค๋ณด๋๊ฐ ์ฌ๋ผ์ง์ง ์์์...๐ฉ (0) | 2022.10.21 |