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("설정")