-
웹개발자의 iOS 개발기(6) - [SwiftUI] 리스트iOS 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("설정")
'iOS' 카테고리의 다른 글
웹개발자의 iOS 개발기(8) - [UIKit] UIKit 입문 (0) 2025.01.26 웹개발자의 iOS 개발기(7) - [SwiftUI] 모달, 네비게이션, 탭뷰 (5) 2025.01.26 웹개발자의 iOS 개발기(5) - 예외 처리, 프로토콜, 제네릭 (0) 2025.01.24 웹개발자의 iOS 개발기(4) - 옵셔널, 옵셔널 바인딩, 체이닝 (0) 2025.01.19 웹개발자의 iOS 개발기(3) - 열거형, 스위치 (0) 2025.01.19