Steleton UI ?
먼저 스켈레톤 UI는 앱에서 데이터를 받아오는 지연시간 동안 임시로 뼈대의 뷰를 보여주어 사용자에게 지루함을 덜어주고 UI상으로도 깨지는 현상을 줄여주는 효과가 있는 기법이다.
예) 유튜브의 스켈레톤 UI 적용
구현하기
위의 유튜브 스켈레톤 UI를 목표로 클론해보자..!
먼저 데이터가 있는 것처럼 간단하게 뷰를 그려보았다.
struct ContentView: View {
var body: some View {
ScrollView {
ForEach(0..<5) { i in
youtubeContent
}
.padding()
}
}
private var youtubeContent: some View {
VStack {
Image("thumbnail1")
.resizable()
.aspectRatio(CGSize(width: 4, height: 3), contentMode: .fit)
.frame(maxWidth: .infinity)
.clipped()
HStack(alignment: .top) {
Image("user1")
.resizable()
.frame(width: 36, height: 36)
.clipShape(Circle())
VStack(alignment: .leading) {
Text("Title")
.font(.title2)
.bold()
Text("Uploader")
.foregroundColor(.gray)
Text("Views")
.foregroundColor(.gray)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
자 여기서 이 화면을 스켈레톤 UI로 변경하는 것은 아주 간단하다.
var body: some View {
ScrollView {
ForEach(0..<5) { i in
youtubeContent
}
.padding()
}
.redacted(reason: .placeholder) // 👈 이것만 추가하면 된다!!
}
물론 reason을 .placeholder로 고정해 놓아서 아무리 기다려도 데이터를 볼 수가 없다.
YoutubeData라는 데이터모델을 생성하고 youtubeContent도 약간 수정했다.
struct YoutubeData {
var thumbnailImageName: String?
var uploaderProfileImageName: String?
var title: String?
var uploaderName: String?
var numberOfViews: Int?
}
struct ContentView: View {
@State private var data: YoutubeData? // 👈 data 추가
var body: some View {
ScrollView { ... }
.redacted(reason: data == nil ? .placeholder : []) // 👈 data가 있으면 redacted를 풀어준다.
.onAppear {
// 👇 1초 후 youtube data 주입
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
data = YoutubeData(
thumbnailImageName: "thumbnail1",
uploaderProfileImageName: "user1",
title: "Title",
uploaderName: "Uploader",
numberOfViews: 100
)
}
}
}
private var youtubeContent: some View {
VStack {
Image(data?.thumbnailImageName ?? "thumbnail1") // 👈
.resizable()
.aspectRatio(CGSize(width: 4, height: 3), contentMode: .fit)
.frame(maxWidth: .infinity)
.clipped()
HStack(alignment: .top) {
Image(data?.uploaderProfileImageName ?? "user1") // 👈
.resizable()
.frame(width: 36, height: 36)
.clipShape(Circle())
VStack(alignment: .leading) {
Text(data?.title ?? "Title") // 👈
.font(.title2)
.bold()
Text(data?.uploaderName ?? "Uploader") // 👈
.foregroundColor(.gray)
Text("\(data?.numberOfViews ?? 0) Views") // 👈
.foregroundColor(.gray)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
이제 1초뒤 데이터를 주입하여 스켈레톤 UI를 비슷하게 구현했다. 실제로 사용할 때는 데이터를 주입하는 것을 네트워크 통신이나 오래 걸릴 것 같은 작업후에 적용하면 될 것같다.
이제 코드를 조금 더 다듬어서 데이터 리스트를 받아오고 서로 다른 데이터들을 표시해보자
struct YoutubeData: Identifiable {
var id: Int
var thumbnailImageName: String?
var uploaderProfileImageName: String?
var title: String?
var uploaderName: String?
var numberOfViews: Int?
}
struct ContentView: View {
@State private var datas: [YoutubeData?] = [nil, nil, nil, nil, nil]
var body: some View {
ScrollView {
ForEach(0..<datas.count, id: \.self) { index in
youtubeContent(data: datas[index])
}
.padding()
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
datas =
[
YoutubeData(id: 1,
thumbnailImageName: "thumbnail1",
uploaderProfileImageName: "user1",
title: "자연경관입니다",
uploaderName: "SwiftUI",
numberOfViews: 100000),
YoutubeData(id: 2,
thumbnailImageName: "thumbnail2",
uploaderProfileImageName: "user2",
title: "아름다운 강과 도시",
uploaderName: "Skeleton",
numberOfViews: 200000),
YoutubeData(id: 3,
thumbnailImageName: "thumbnail3",
uploaderProfileImageName: "user1",
title: "호수를 둘러싼 푸른 나무들",
uploaderName: "SwiftUI",
numberOfViews: 3000),
YoutubeData(id: 4,
thumbnailImageName: "thumbnail4",
uploaderProfileImageName: "user2",
title: "넓은 평야",
uploaderName: "Skeleton",
numberOfViews: 400),
YoutubeData(id: 5,
thumbnailImageName: "thumbnail5",
uploaderProfileImageName: "user1",
title: "바다 건너의 설산",
uploaderName: "SwiftUI",
numberOfViews: 55555),
]
}
}
}
private func youtubeContent(data: YoutubeData?) -> some View {
VStack {
Image(data?.thumbnailImageName ?? "thumbnail1")
.resizable()
.aspectRatio(CGSize(width: 4, height: 3), contentMode: .fit)
.frame(maxWidth: .infinity)
.clipped()
HStack(alignment: .top) {
Image(data?.uploaderProfileImageName ?? "user1")
.resizable()
.frame(width: 36, height: 36)
.clipShape(Circle())
VStack(alignment: .leading) {
Text(data?.title ?? String(repeating: " ", count: 25))
.font(.title2)
.bold()
Text(data?.uploaderName ?? String(repeating: " ", count: 30))
.foregroundColor(.gray)
Text("\(data?.numberOfViews ?? 1000000) Views")
.foregroundColor(.gray)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.redacted(reason: data == nil ? .placeholder : [])
}
}
'iOS > SwiftUI' 카테고리의 다른 글
SwiftUI에서 UITextField 커스텀하여 사용하기 (0) | 2022.04.10 |
---|---|
SwiftUI와 상태관리 (0) | 2022.04.03 |
[SwiftUI] EnvironmentObject / ObservedObject / StateObject (0) | 2022.01.30 |
[Swift] Codable로 JSON파싱하기 -확장- (0) | 2022.01.30 |
[Swift] Codable로 JSON파싱하기 -기초- (0) | 2022.01.30 |