안녕하세요 iOS 개발자 루크입니다!
오늘은 SwiftUI 의 뷰를 구성함에 있어 빠질 수 가 없는 ForEach 문에 대해서 알아보려고 합니다 ㅎ
ForEach 란?
View 를 구성할 때 사용하는 반복문 같은 놈
Collection의 데이터를 기반으로 View 를 계산하는 구조체
Collection 의 조건
1. Identifiable 을 만족해야함 ← id 프로퍼티가 필요함
Identifiable 이란?
밸류 타입 또는 클래스에게 ID 속성을 부여하는 프로토콜로, 이를 활용해 인스턴스가 항상 유일함을 보장한다.
각 데이터가 각자를 식별할 수 있는 무언가를 가지고 있는 경우에 충족한다
Identifiable 프로토콜을 사용해서 클래스 또는 밸류 타입에 ID 속성을 부여하시먄 됩니다.
ObjectIdentifier 타입을 사용해서 id 프로퍼티를 정의함으로써 Identifiable 프로토콜을 준수할 수 있습니다.
그렇다면 extension 을 활용해서 id 프로퍼티를 추가하고 인스턴스를 생성할 때마다 id 를 넣어주어야할까요?
이는 너무 번거롭기 때문에 ForEach 문에는 Array 의 각 요소에 id 를 부여할 수 있는 방법을 제공한다네요!
ForEach(array, id: \.self) 를 활용하면 ForEach 문 내에서 각 item 에 대해 id를 정해줄 수 있다고 합니다. 위와 같이하면 Array 의 내부요소가 가지고 있는 값 자체를 id 로 가지도록 만들어줍니다.
따라서 ForEach 문에는 Int 같이 Identifiable 하지 않은 타입도 고유한 존재로 만들어서 사용이 가능합니다.
신기하게도 0..<10 같은 (Open) Range<Int>는 가능하지만, 0...10 같은 ClosedRange<Int>는 불가능한데요. 이는 ForEach의 init 구문 중에서 범위를 지원하는 것은 Range<Int> 밖에 없기 때문이라고 하네요 참고하시면 좋을 듯 합니다
2. ID가 Hashable 을 만족해야함
Hashable 이란?
정수 Hash 값을 제공하는 타입임을 보장하는 프로토콜
그 자체로 유일하게 표현이 가능한 방법을 제공해야 만족됩니다
func hash(into:) 이라는 메서드를 구현함으로써 충족이 가능한데요, Int, String, Float, Boolean 같은 경우 Hashable 을 기본적으로 만족하고 있습니다.
하지만 Hashable 함이 나타내어져 있지 않은( Hashable 프로토콜을 준수하지 않은) 타입으로 이루어진 Array 를 ForEach 문에 사용하려고 하면 에러가 발생하겠죠!
이럴때는 건네주고자 하는 타입이 Hashable 프로토콜을 준수하도록 해주면 됩니다. 보통은 타입이 가지고 있는 프로퍼티가 모두 Hashable 을 이미 준수하는 타입으로만 이루어져 있다면, 이름 옆에 Hashable 키워드를 작성해주는 것만으로도 Hashable 을 준수할 수 있습니다.
조건이 존재하는 이유?
ForEach에서 이런 식으로 Identifiable, Hashable을 지키도록 하는 것은 결국 각 item을 제대로 구분하기 위해서라고 합니다.
주의해야할 점은
이런 프로토콜을 다 지키더라도 값이 아예 같으면 SwiftUI 입장에서 당황할 수 밖에 없다는 점입니다. 이렇게 되면 렌더링은 문제없이 되나 의도치 않은 결과가 발생할 수 있어요
그래서 완전히 같은 값이 존재하거나, Hashable하게 데이터를 만들기 힘들 경우.
ForEach 문을 사용하기 위한 두가지 방법이 존재합니다.
1. ForEach 의 id 값을 조정한다
id: \.self.프로퍼티명 id 로 사용할만한 확실한 정보가 있을 경우 이것만을 활용해 객체들을 구분해줄 수 있습니다.
타입의 프로퍼티 전부를 Hashable 하게 할 필요 없이 타입의 한 프로퍼티 만으로 각 인스턴스를 구분하도록 하는 것이죠!
2. indices 를 사용해 인덱스를 통해 접근하기
Collection 을 사용해 데이터를 다룰 것이기 때문에 각 값에는 무조건 인덱스가 있기 마련이죠
struct ContentView: View {
let array: [Info] = [Info(0), Info(1)]
var body: some View {
ForEach(array.indices, id:\.self) { idx in
Text("index : \(array[idx].num")
}
}
}
Swift
복사
이런 식으로 indces 를 사용하면 array 의 인덱스를 담은 Collection 을 얻어낼 수 있고 이를 통해 원본 데이터 접근이 가능해요 ㅎ
이렇게 되면 Hashable 하지 않아도 의도대로 ForEach 문을 사용할 수 있답니다!
오늘은 SwiftUI 를 사용해 반복되는 View 객체를 생성하는 방법과 그에 따라 자주 등장하는 Hashable Identifiable 경고문에 대해서 이해해보는 시간을 가졌습니다.
해결책을 정리해두고 나니 한결 마음이 수월하네요
오늘 포스팅은 여기서 마무리 하겠습니다