HealthKit (1)
HealthKit을 사용한 앱을 만드는 걸 공부하면서 기록을 적어본다.
가급적 UI디자인은 크게 언급할 부분이 없다면 생략을 하면서 가도록 하겠다.
첫 화면의 디자인은 다음과 같다.
Picker(Segmented) 적용하기
현재 디자인이 된 상태에서 Picker를 사용하여 step을 고를지, weight를 고를지를 정해본다.
Picker를 구현하기 앞서
enum
을 통해 각 케이스를 구별해준다.
1
2
3
4
5
6
7
8
9
10
enum HealthMetricContext {
case steps, weight
var title: String {
switch self {
case .steps: return "Steps"
case .weight: return "Weight"
}
}
}
그리고 이렇게 만든 열거형 HealthMetricContext
을 View에서 사용하기 위해 변수를 만들어 준다.
@State private var selectedStat: HealthMetricContext = .steps
Steps 바로 위에 할것이므로 Vstack 바로 밑에서 적용을 한다.
1
2
3
4
5
6
7
8
ScrollView {
VStack(spacing: 20) {
Picker("Select Stat", selection: $selectedStat) {
ForEach(HealthMetricContext.allCases) { metric in
Text(metric.title)
}
}
.pickerStyle(.segmented)
이때 ForEach에 enum이 들어가기 위해선 두 프로토콜을 충족시켜줘야 하므로
1
2
3
enum HealthMetricContext: CaseIterable, Identifiable {
case steps, weight
var id: Self { self } // new
이렇게 추가해준다.
CaseIterable은 이전글에 작성을 해둔게 있으니 한번 다시 확인해보는것도 좋을듯.
1
var id: Self { self }
- Identifiable 프로토콜 준수: id는 Identifiable 프로토콜의 요구사항으로, 열거형의 각 케이스를 식별하는 고유한 값이다.
- id: Self: 열거형 자체(Self)가 고유하기 때문에, 각 케이스를 그대로 id로 사용한다.
- 열거형의 각 케이스는 자체적으로 고유한 값이므로 추가 작업 없이 self를 반환해도 충분히 구분 가능하다.
- 예를 들어, HealthMetricContext.steps와 HealthMetricContext.weight는 각각 고유한 id를 가지며, 리스트나 반복문에서 중복 없이 사용할 수 있다.
NavigationLink 사용 및 tint 색상 변경
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
NavigationLink(value: selectedStat) {
HStack {
VStack(alignment: .leading) {
Label("Steps", systemImage: "figure.walk")
.font(.title3.bold())
.foregroundStyle(.pink)
Text("Avg: 10K Steps")
.font(.caption)
}
Spacer()
Image(systemName: "chevron.right")
}
}
selectedStat에 따라 다른 값을 보여주기위해 사용했다.
ScrollView의 modifier로 navigationDestination
를 달아준다.
1
2
3
4
5
6
7
8
ScrollView {
// 생략
}
.padding()
.navigationTitle("Dashboard")
.navigationDestination(for: HealthMetricContext.self) { metric in
Text(metric.title)
} // new
이렇게 화면 전환이 되는걸 알 수 있다.
현재 Navigation Button이 Default로 파란색이 되어있다.
1
2
3
4
5
6
var isSteps: Bool { selectedStat == .steps }
NavigationStack {
// 생략
}
.tint(isSteps ? .pink : .indigo) // new
이렇게 추가를 해주자.
실행화면은 생략 어차피 위의 파란색의 색만 변한것.
ListView 생성
이제 이렇게 전환되는 화면에 보여줄 ListView를 만들것이다.
여기에 이제 각 시간대에 걸음이나, 날짜에 해당하는 몸무게를 List 형식으로 보여줄건데.
날짜에 대한 형식의 참고글을 보면 좋을듯.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@State private var isShowingAddData = false
var body: some View {
List(0..<28) { i in
HStack {
Text(Date(), format: .dateTime.month(.wide).day().year())
Spacer()
Text(10000, format: .number.precision(.fractionLength(metric == .steps ? 0 : 1)))
}
}
.navigationTitle(metric.title)
.sheet(isPresented: $isShowingAddData) {
addDataView
}
.toolbar {
Button("Add Data", systemImage: "plus") {
isShowingAddData = true
}
}
}
여기서 짚고가면 좋을 부분은
Text(10000, format: .number.precision(.fractionLength(metric == .steps ? 0 : 1)))
이부분
숫자를 표현하는데 뒤에 소숫점으로 0을 몇개 더 붙일지에 대한 parameter이다.
그리고 sheet를 사용해서 새로운 화면을 띄우게 된다.
UIKit으로 하면 Modal 형식으로 아래에서 위로 올리는 그런 액션.
AddDataView 생성
이렇게 아래에서 위로 올라오는 화면을 구현하는데
특이점이라면
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@State private var addDataDate: Date = .now
@State private var valueToAdd: String = ""
var addDataView: some View {
NavigationStack {
Form {
DatePicker("Date", selection: $addDataDate, displayedComponents: .date)
HStack {
Text(metric.title)
Spacer()
TextField("Value", text: $valueToAdd)
.multilineTextAlignment(.trailing)
.frame(width: 140)
.keyboardType(metric == .steps ? .numberPad : .decimalPad)
}
}
.navigationTitle(metric.title)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Add Data") {
}
}
ToolbarItem(placement: .topBarLeading) {
Button("Dismiss") {
isShowingAddData = false
}
}
}
}
}
우리가 일반적으로 어떤 View를 만들때 Struct를 사용해서 View를 만들어 주었다.
하지만 여기서는 그렇게 하지않고 하나의 View를 변수로 담아내었다.
DatePicker("Date", selection: $addDataDate, displayedComponents: .date)
DatePicker를 사용하여 날짜를 선택하게 해주었다.
그리고
.keyboardType(metric == .steps ? .numberPad : .decimalPad)
TextField에서 값을 입력할때 패드를 다르게 해준다.
전자가 numpad, 후자가 decimalpad
그리고 Dismiss의 경우 나는 예전에 환경변수를 사용했는데, 그때와 지금은 다른게
실행화면
Github: Step-Tracker Repository