iOS
웹개발자의 iOS 개발기(6) - [SwiftUI] 리스트
whh__
2025. 1. 25. 14:16
리스트에 대하여 공부해 보았습니다.
01. 리스트와 반복
import SwiftUI
struct _1_ListLoop: View {
var fruits = ["Apple", "Banana", "Cherry", "Double Kiwi", "Elder berry"]
var price = ["1000", "3000", "4000", "2400", "8000"]
var count = 0
var body: some View {
NavigationStack {
List {
ForEach(fruits, id: \\.self) { fruit in
HStack {
Text(fruit)
Text(price[count])
}
// count = count + 1 // 에러 발생
}
}
.navigationTitle("Fruit List")
}
}
}
#Preview {
_1_ListLoop()
}
리스트 구성요소에 다양한 정보를 담고 싶어 위와 같이 코드를 짜보았지만 오류가 발생하는 코드.
02. 데이터 모델링
struct를 이용하여 해결
import SwiftUI
struct Fruit: Hashable {
let name: String
let matchFruitName: String
let price: Int
}
struct _1_ListLoop: View {
// var fruits = ["Apple", "Banana", "Cherry", "Double Kiwi", "Elder berry"]
// var matchFruits = ["Banana", "Banana", "Double Kiwi", "Elder berry", "Double Kiwi"]
// var price = ["1000", "3000", "4000", "2400", "8000"]
// var count = 0
var favoriteFruit = [
Fruit(name: "Apple", matchFruitName: "Banana", price: 1000),
Fruit(name: "Banana", matchFruitName: "Banana", price: 3000),
Fruit(name: "Cherry", matchFruitName: "Double Kiwi", price: 4000),
Fruit(name: "Double Kiwi", matchFruitName: "Elder berry", price: 2400),
Fruit(name: "Elder berry", matchFruitName: "Double Kiwi", price: 8000),
]
var body: some View {
// NavigationStack {
// List {
// ForEach(fruits, id: \\.self) { fruit in
// HStack {
// Text(fruit)
// Text(price[count])
// }
// // count = count + 1 // 에러 발생
// }
// }
// .navigationTitle("Fruit List")
// }
NavigationStack {
List {
ForEach(favoriteFruit, id: \\.self) { fruit in
VStack(alignment: .leading) {
Text("name: \\(fruit.name)")
Text("machFruitName: \\(fruit.matchFruitName)")
Text("price: \\(fruit.price)")
}
}
}
.navigationTitle("Fruit List")
}
}
}
#Preview {
_1_ListLoop()
}
Hashable
값을 고유하게 식별하고 비교하기 위함
03. 리스트의 추가와 삭제
State
- 데이터의 상태를 나타내는 State
- struct이기에 필요한 기능 → View가 struct인데, struct는 생성 시에 멤버변수를 초기화한다. 화면 재로딩 시 변수의 변경사항을 기억하지 않는다.
- @State를 변수에 사용해주면 해당 변수는 다른 스토리지에 저장해 두었다가 불러온다.
- @State가 사용된 변수의 값이 변경되면 화면 재로딩
Binding
- State에 $를 붙이면 Binding
- State가 붙잡고 있는 상태를 연결해줄 때
- 두 State가 연결된다고 생각
import SwiftUI
struct Fruit: Hashable {
let name: String
let matchFruitName: String
let price: Int
}
struct _1_ListLoop: View {
// var fruits = ["Apple", "Banana", "Cherry", "Double Kiwi", "Elder berry"]
// var matchFruits = ["Banana", "Banana", "Double Kiwi", "Elder berry", "Double Kiwi"]
// var price = ["1000", "3000", "4000", "2400", "8000"]
// var count = 0
@State var favoriteFruit = [
Fruit(name: "Apple", matchFruitName: "Banana", price: 1000),
Fruit(name: "Banana", matchFruitName: "Banana", price: 3000),
Fruit(name: "Cherry", matchFruitName: "Double Kiwi", price: 4000),
Fruit(name: "Double Kiwi", matchFruitName: "Elder berry", price: 2400),
Fruit(name: "Elder berry", matchFruitName: "Double Kiwi", price: 8000),
]
@State var fruitName = ""
var body: some View {
// NavigationStack {
// List {
// ForEach(fruits, id: \\.self) { fruit in
// HStack {
// Text(fruit)
// Text(price[count])
// }
// // count = count + 1 // 에러 발생
// }
// }
// .navigationTitle("Fruit List")
// }
NavigationStack {
VStack {
HStack {
TextField("insert fruit name", text: $fruitName)
Button {
favoriteFruit.append(Fruit(name: fruitName, matchFruitName: "Apple", price: 1000))
} label: {
Text("insert")
.padding()
.background(.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
}
.padding()
List {
ForEach(favoriteFruit, id: \\.self) { fruit in
VStack(alignment: .leading) {
Text("name: \\(fruit.name)")
Text("machFruitName: \\(fruit.matchFruitName)")
Text("price: \\(fruit.price)")
}
}.onDelete { IndexSet in
favoriteFruit.remove(atOffsets: IndexSet)
}
}
.navigationTitle("Fruit List")
}
}
}
}
#Preview {
_1_ListLoop()
}
04. 간단한 리스트를 사용한 앱
import SwiftUI
struct SettingInfo: Hashable {
let title: String
let systemName: String
let backgroundColor: Color
let foregroudColor: Color
}
struct _4_List_Setting_: View {
let data: [[SettingInfo]] = [
[SettingInfo(title: "스크린 타임", systemName: "hourglass", backgroundColor: .purple, foregroudColor: .white)],
[SettingInfo(title: "일반", systemName: "gear", backgroundColor: .gray, foregroudColor: .white),
SettingInfo(title: "손쉬운 사용", systemName: "person.crop.circle", backgroundColor: .blue, foregroudColor: .white),
SettingInfo(title: "개인정보 보호 및 보안", systemName: "hand.raised.fill", backgroundColor: .blue, foregroudColor: .white)],
[SettingInfo(title: "암호", systemName: "key.fill", backgroundColor: .gray, foregroudColor: .white)],
]
var body: some View {
NavigationStack {
List {
ForEach(data, id: \\.self) { section in
Section {
ForEach(section, id: \\.self) { item in
Label {
Text(item.title)
} icon: {
Image(systemName: item.systemName)
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.all, 7)
.background(item.backgroundColor)
.foregroundColor(item.foregroudColor)
.cornerRadius(6)
}
}
}
}
// Section {
// Label {
// Text("스크린 타임")
// } icon: {
// Image(systemName: "hourglass")
// .resizable()
// .scaledToFit()
// .frame(width: 20, height: 20)
// .padding(.all, 7)
// .background(.purple)
// .foregroundColor(.white)
// .cornerRadius(6)
// }
// }
//
//
// Section {
// Label {
// Text("일반")
// } icon: {
// Image(systemName: "gear")
// .resizable()
// .scaledToFit()
// .frame(width: 20, height: 20)
// .padding(.all, 7)
// .background(.gray)
// .foregroundColor(.white)
// .cornerRadius(6)
// }
//
// Label {
// Text("손쉬운 사용")
// } icon: {
// Image(systemName: "person.crop.circle")
// .resizable()
// .scaledToFit()
// .frame(width: 20, height: 20)
// .padding(.all, 7)
// .background(.blue)
// .foregroundColor(.white)
// .cornerRadius(6)
// }
//
// Label {
// Text("개인정보 보호 및 보안")
// } icon: {
// Image(systemName: "hand.raised.fill")
// .resizable()
// .scaledToFit()
// .frame(width: 20, height: 20)
// .padding(.all, 7)
// .background(.blue)
// .foregroundColor(.white)
// .cornerRadius(6)
// }
// }
//
//
// Section {
// Label {
// Text("암호")
// } icon: {
// Image(systemName: "key.fill")
// .resizable()
// .scaledToFit()
// .frame(width: 20, height: 20)
// .padding(.all, 7)
// .background(.gray)
// .foregroundColor(.white)
// .cornerRadius(6)
// }
// }
//
//
//
}
}.navigationTitle("설정")
}
}
#Preview {
_4_List_Setting_()
}
결과물은 위와 같은 구조의 리스트이다.
이런 Section 별 구조의 리스트를 그리기 위해서는 데이터 모델링이 중요한 것 같다.
이 예제에서는 2차원배열 ‘[[ ]]’ 을 이용해서 해당 구조를 그렸다.
let data: [[SettingInfo]] = [
[SettingInfo(title: "스크린 타임", systemName: "hourglass", backgroundColor: .purple, foregroudColor: .white)],
[SettingInfo(title: "일반", systemName: "gear", backgroundColor: .gray, foregroudColor: .white),
SettingInfo(title: "손쉬운 사용", systemName: "person.crop.circle", backgroundColor: .blue, foregroudColor: .white),
SettingInfo(title: "개인정보 보호 및 보안", systemName: "hand.raised.fill", backgroundColor: .blue, foregroudColor: .white)],
[SettingInfo(title: "암호", systemName: "key.fill", backgroundColor: .gray, foregroudColor: .white)],
]
NavigationStack {
List {
ForEach(data, id: \\.self) { section in
Section {
ForEach(section, id: \\.self) { item in
Label {
Text(item.title)
} icon: {
Image(systemName: item.systemName)
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.all, 7)
.background(item.backgroundColor)
.foregroundColor(item.foregroudColor)
.cornerRadius(6)
}
}
}
}
}
}.navigationTitle("설정")