SwiftUI) ForEach를 사용하여 View생성
우선 View를 생성하기전에 ForEach에 대해서 자세히 알아보겠습니다.
ForEach 란?
SwiftUI를 사용해 같은뷰를 반복적으로 생성할 때 ForEach를 자주 사용하게 됩니다.
하지만 재대로 알고 사용하는 느낌이 아니기에 이번에 재대로 정리를 해보려 합니다.
SwiftUI 에서 ForEach는 그 자체로 뷰 구조체 입니다.
즉, 원하는 경우 뷰 본문에서 직접 반환할 수 있습니다.
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension ForEach : DynamicViewContent where Content : View {
}
항목 배열을 제공하고 값이 변경될 때 항목을 업데이트하는 방법을 알 수 있도록 각 항목을 고유하게 식별하는 방법을 SwiftUI에 알려야 할 수도 있습니다. 또한 루프의 각 항목에 대한 보기를 생성하기 위해 실행할 함수를 전달합니다.
범위에 대한 간단한 루프의 경우 범위를 직접 전달하고 ForEach각 숫자를 항목의 고유 식별자로 사용하도록 Swift에 지시할 수 있습니다. 예를 들어 보겠습니다.
import SwiftUI
struct TestView: View {
var body: some View {
VStack(alignment: .leading) {
ForEach((1...10), id: \\.self) {
Text("Text: \\($0)")
}
}
}
}
위 코드에서 조금 생소한 문법이 있습니다.
id : \\.self
부분은 SwiftUI가 배열의 각 요소를 고유하게 식별할 수 있도록 하는데 필요합니다.
즉, 항목을 추가하거나 제거하면 SwiftUI가 정확히 어느 항목인지 알 수 있습니다.
이 id: \.self는 각 배열안에 항목이 고유하게 식별된다는것을 Swift가 알도록 해줍니다.
struct TestView: View {
var array : [String] = ["🍎", "🥝", "🍐", "🍊", "🍏", "🍒", "🍉", "🍇", "🫐"]
var body: some View {
VStack(alignment: .leading) {
ForEach(array, id: \\.self) { fruit in
Text("array: \\(fruit)")
}
}
}
}
배열에 사용자 정의 유형이 있는 경우에는 ForEach를 바로 사용할 수 없습니다.
FruitType이라는 구조체를 새로 정의한뒤 해당 타입으로 배열을 만들어 ForEach를 사용해보겠습니다.
struct FruitType {
var fruitName : String
}
struct TestView: View {
var array : [FruitType] = [FruitType(fruitName: "🍎"),
FruitType(fruitName: "🥝"),
FruitType(fruitName: "🍐"),
FruitType(fruitName: "🍊"),
FruitType(fruitName: "🍏"),
FruitType(fruitName: "🍒"),
FruitType(fruitName: "🍉"),
FruitType(fruitName: "🍇"),
FruitType(fruitName: "🫐")]
var body: some View {
VStack(alignment: .leading) {
ForEach(array, id: \\.self) { fruit in
Text("array: \\(fruit)")
}
}
}
}
해당 코드를 적용하면 분명 문제가 없을듯 하지만 실행이 되지 않습니다.
그리고 아래와 같은 오류를 내보냅니다.
Generic struct 'ForEach' requires that 'FruitType' conform to 'Hashable’
사용자 정의 타입을 사용하기 위해선 Hashable을 준수해야 한다고 나옵니다.
struct FruitType {
let id = UUID()
var fruitName : String
}
위와 같이 id를 추가해 줍니다.
여기서 UUID는 유형, 인터페이스, 및 기타 항목들을 식별하는 범용 고유 값입니다.
따라서 UUID 유형인 id를 추가해주면 ForEach에서
\\.self 대신 \\.id 를 사용하면 됩니다.
var body: some View {
VStack(alignment: .leading) {
**ForEach(array, id: \\.id)** { fruit in
Text("array: \\(fruit.fruitName)")
}
}
}
또 다른 방법으로는 Identifiable 프로토콜을 사용하면 됩니다.
해당 프로토콜을 준수하는 객체는 고유한 id값을 추가하는것을 의미합니다.
struct FruitType : Identifiable{
var id = UUID()
var fruitName : String
}
var body: some View {
VStack(alignment: .leading) {
ForEach(array) { fruit in
Text("array: \\(fruit.fruitName)")
}
}
}
Identifiable 프로토콜을 준수하기 때문에 이미 해당 객체는 고유한 id를 가지고 있는것으로 판단됩니다. 따라서
ForEach 에서 id 인자또한 필요없어지게 됩니다.
요약
- ForEach를 사용하기 위해선 Hashable을 준수하는 타입이 필요합니다.
- 가장 간단한 방법은 루프를 돌리기위한 객체에 id를 부여하는 것입니다.
- id 를 부여하는 가장 간편한 방법은 Identifiable 프로토콜을 준수하는 것 입니다.