오늘은 ColorScheme을 변경하는 코드를 작성하면서, CaseIterable, Identifiable 그리고 tag 수식어에 대해서 알아보겠습니다.
enum을 생성해서, 앱의 Color Scheme을 light, dark 그리고 automatic 으로 변경하는 데이터를 관리하려고 합니다.
import SwiftUI
enum Appearance: Int, CaseIterable, Identifiable {
case light, dark, automatic
var id: Int { self.rawValue }
var name: String {
switch self {
case .light: return "Light"
case .dark: return "Dark"
case .automatic: return "Automatic"
}
}
func getColorScheme() -> ColorScheme? {
switch self {
case .automatic: return nil
case .light: return .light
case .dark: return .dark
}
}
}
protocol
CaseIterable
a type that provides a collection of all of its values
(공식문서)
공식문서의 내용대로 해석해보면, ''하나의 타입에 콜렉션을 모든 값에 제공해준다'' 라고 합니다. 즉, enum을 collection 처럼 순회할 수 있게 해준다는 뜻 입니다. 단어 뜻 그대로 case를 iterable 하게 해준다는 뜻이죠. 앞으로 for / forEach 와 같은 순회문에 해당 enum 타입을 사용하도록 해준다는 뜻이죠.
Protocol
Identifiable
A class of types whose instances hold the value of an entity with stable identity
(공식문서)
해석해보면, "인스턴스가 안정적인 ID를 가진 엔터티 값을 보유하는 유형의 클래스입니다." 이죠. 인스턴스에 ID를 준다는 뜻입니다.
해당 프로토콜을 준수하려면 id에 값을 줘야합니다.
소스코드에 이렇게 적혀있거든요.
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
public protocol Identifiable {
/// A type representing the stable identity of the entity associated with
/// an instance.
associatedtype ID : Hashable
/// The stable identity of the entity associated with this instance.
var id: Self.ID { get }
}
그래서 제일 위에서 작성한 Appearance Enum에서 id를 주석처리하면, 컴파일에러가 발생합니다. 현재 rawValue를 id로 설정했습니다. 그러므로 Int 입니다. (light == 0 / dark == 1 / automatic == 2)
이제 UI와 데이터를 선언하겠습니다.
먼저 데이터를 선언합니다.
@AppStorage("appearance") var appearance: Appearance = .automatic
그리고 body 변수에 아래와 같은 코드를 추가합니다.
List {
Text("Settings")
.font(.largeTitle)
.padding(.bottom, 8)
Section {
VStack(alignment: .leading) {
Picker(
"Pick",
selection: $appearance) {
ForEach(Appearance.allCases) { appearance in
Text(appearance.name)
}
}
}
} header: {
Text("Pick Appearance")
}
.pickerStyle(.segmented)
}
tag
입니다.ForEach 문 내부 코드블럭을 변경해줍니다.
Text(appearance.name).tag(appearance)
그러면 아래와 같이 동작하게됩니다.
Instance Method
tag(_:)
Sets the unique tag value of this view
(공식문서)
tag는 식별자를 부여해주는 수식어입니다. 해당 뷰를 다른 것들과 구분할 수 있는 id를 준다고 보시면됩니다. ForEach에서 id값을 전달해줘야하죠. 혹은 이미 내부적으로 Identifiable이 구현되어 있어야합니다. 위 Picker를 기준을 말씀드리면, Picker에 Text 3 개를 보여줬지만, 각각이 아직 구분되진 않은 상태입니다. 즉, 보여주기만한겁니다. 그래서 각각을 구분할 수 있도록 .tag를 붙여줬고, 해당 값을 appearance tpye으로 구분했습니다.
tag는 list나 picker 에서 각각의 뷰들을 구분짓는데 사용합니다. tag 수식어는 Hashable Protocol을 따르는 타입을 취할 수 있습니다. Enum의 경우, 자동적으로 그것을 따르고 있습니다. 그래서 바로 사용할 수 있는 것이죠. Section에 Appearance를 바인딩하고 있고, 그것에 맞는 tag를 설정해주면됩니다.
UI와 데이터 바인딩이 끝났으니, 실제 동작하도록 해보겠습니다.
App의 EntryPoint 인 App 프로토콜을 채택한 곳으로 갑시다.
(앱을 처음 생성하면 앱프로젝트이름+App.swift 파일로 이동하시면됩니다.)
그곳에 프로퍼티로 다음 코드를 추가합니다.
@AppStorage("appearance")
var appearnace: Appearance = .automatic
앱 전체에 적용되는 값이므로 AppStorage에 저장했습니다.
다만 UserDefault(=AppStorage) 는 모든 값을 저장할 수는 없습니다.
위 3 가지 경우만 저장이 가능합니다. 그러면 Appearance는 모두 적용이 안되는 것 같은데 AppStorage에 왜 저장이 되는 걸까요?
우리가 정의한 Appearance 를 보면 이렇게 정의하고 있습니다.
enum Appearance: Int ... { ... }
Int를 rawValue로 지정하고 있는거죠. 열거형에서 기본데이터타입으로 rawValue를 지정하면 자동으로 RawPresentable 프로토콜을 따르게 됩니다. 그래서 enum 값이 AppStorage에 잘 저장이 됩니다.
그리고 body에 있는 View에 앱의 ColorScheme을 설정하는 수식어를 추가해줍니다.
WindowGroup {
ContentView()
.preferredColorScheme(appearnace.getColorScheme()) // <--
}
이렇게 코드를 설정한 후, 실행하면 다음과 같은 화면이 나타납니다.
preferredColorScheme
에서 설정해준다.