포스트

HealthKit (9)

Chart Drag시 Haptic 기능 추가.

차트를 드래그할때 Haptic 기능을 제공하여 기기와 접촉할 때 촉감을 자극해 실제로 뭔가를 만지고 있다는 느낌을 전달하려 한다.

sensoryFeedback Modifier를 사용한다.

Step Chart View에서 적용을 해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
@State private var selectedDay: Date?

.background {
    RoundedRectangle(cornerRadius: 4)
        .fill(Color(.secondarySystemBackground))
        .shadow(color: .secondary.opacity(0.3), radius: 2, x: 2, y: 2)
}
.sensoryFeedback(.selection, trigger: selectedDay) // new
.onChange(of: rawSelectedDate) { oldValue, newValue in
    if oldValue?.weekdayInt != newValue?.weekdayInt {
        selectedDay = newValue
    }
}

차트들의 x축은 날짜로 되어있기에 유져가 드래그를 하면 (날짜가 계속 바뀌면) selectedDay의 값이 계속 바뀌기에, Haptic이 작동.

실제 기기에서 테스트를 해보니 잘 되는걸 확인.

이때 Haptic의 강도를 더 세게하려면

.sensoryFeedback(.impact(flexibility: .solid, intensity: 10), trigger: selectedDay) 이런식으로 줄 수 있다.

실기기에서 테스트를 해보니 강도가 더 세진걸 확인할 수 있다.

이걸 나머지 ChartView에도 그대로 적용을 한다.

하나 다른점은 stepPieChartView이다.

다른건 드래그를 통해 날짜의 변화 였으나,

pieChartView는 선택한 요일이 변화하므로

1
2
3
4
5
6
.onChange(of: selectedWeekday) { oldValue, newValue in
    guard let oldValue, let newValue else { return }
    if oldValue.date.weekdayInt != newValue.date.weekdayInt {
        selectedDay = newValue.date
    }
}

그전에는 selectedDay type이 Date였기에, 바로 접근이 가능했으나, 이번엔 selectedWeekday의 type은 우리가 만든 WeekdayChartData이기에,

oldValue, newValue에 date를 통해, weekdayInt 접근이 가능 한것.

CleanShot 2024-12-16 at 20 37 26

이렇게 하고나니 발생하는에러 WeekdayChartData가 Equatable 프로토콜을 준수하고 있지않다고한다.

추가해주자.

1
2
3
struct WeekdayChartData: Identifiable, Equatable { 
    // 내용 생략
}

실행하면 이렇게 된다.

소리가 작으니 키우면 들린다.

Empty State View 만들기

앱을 실행했을때 지금은

Simulator Screenshot - iPhone 16 Pro Max - 2024-12-16 at 23 35 43 이런 빈화면이 보이는데,

이젠 Empty State View를 만들어 관리를 한다.

1
2
3
4
5
6
7
if chartData.isEmpty {
    ContentUnavailableView.search
} else {
    Chart {
        // 생략
    }
}

simulator_screenshot_66A7CAB9-8D46-48EB-B785-90930D5A8164

실행해 보면 위와 같다.

물론 search 뒤에 (Text: " ") 를 사용하여 어떤 특정한것에 대해 찾을 수 없다고 알려줄수도 있다.

1
ContentUnavailableView.search(text: "Step Data")

CleanShot 2024-12-16 at 23 59 06

이렇게 기본적으로 제공이 되지만 Customizing을 해보도록 한다.

ContentUnavailableView Customizing

with Description

CleanShot 2024-12-16 at 23 59 42

여러가지중 이녀석으로 커스터마이징을 진행한다.

1
2
3
4
5
ContentUnavailableView(
                    "No Data",
                    systemImage: "chart.bar",
                    description: Text("There is no step count data from the Health App.")
                )

CleanShot 2024-12-17 at 00 01 01


without Description, with Label

Description 없이 한다고 하면

1
2
3
ContentUnavailableView {
    Label("No Mail", systemImage: "tray.fill")
}

CleanShot 2024-12-17 at 00 03 15

이런식으로도 가능!

Customize whatever we want

아니면 애초에 우리가 원하는대로 디자인도 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ContentUnavailableView {
    Image(systemName: "chart.bar")
        .resizable()
        .frame(width: 32, height: 32)
        .foregroundStyle(.secondary)
        .padding(.bottom, 8)
    
    Text("No Data")
        .font(.callout.bold())
    
    Text("There is no step count data from the Health App.")
        .font(.footnote)
}
.foregroundStyle(.secondary)

ContentUnavailableView 안에 VStack이 내장되어 있으므로, UI 디자인 하듯 작성해주면 된다.

CleanShot 2024-12-17 at 00 06 22

그럼 이렇게 디자인이 된다.

다른 차트에도 사용해야하므로 이부분만 별도로 새롭게 View파일로 만든다.

그리고 SystemImageName, Title, Description이 필요하므로 변수를 만들고 위치에 맞게 넣어주자.

해당 코드는 생략.

1
2
3
let systemImageName: String
let title: String
let description: String

ChartView로 가서

1
2
3
if chartData.isEmpty {
    ChartEmptyView(systemImageName: "chart.bar", title: "No Data", description: "There is no step count data from the Health App.")
}

이렇게 적용해준다.

나머지 chartView에도 그대로 적용하자. (과정은 생략)

적용하면 다음과 같다.

CleanShot 2024-12-17 at 00 14 32CleanShot 2024-12-17 at 00 17 48

Text Input Validation

문제점

만약 사용자가 우리가 원하는 대로 값을 입력하지 않는 경우엔 어떤일이 발생할까?

예를 들면

simulator_screenshot_1D16C039-B6A9-4BF1-B31E-87F038F3993B

이렇게 숫자만 입력을 해야하는데, 글자를 입력하는경우엔 App Crash가 발생하고 앱이 강제 종료 될것이다.

왜냐 강제 언래핑을 했기 때문.

왜냐하면 현재 우리는

1
try await hkManager.addStepData(for: addDataDate, value: Double(valueToAdd)!)

이렇게 값을 강제로 Double로 형변환을 하면서 숫자만 입력하게 했기때문.

물론 이건 어거지 느낌이긴 하다 왜냐면 입력을 할때

1
2
3
4
TextField("Value", text: $valueToAdd)
        .multilineTextAlignment(.trailing)
        .frame(width: 140)
        .keyboardType(metric == .steps ? .numberPad : .decimalPad)

실제로 NumPad가 나오기 때문.

그렇다면 이런 케이스는?

simulator_screenshot_3BA872D7-9BCB-4EDF-8979-9136D2F42853

소수를 입력해야하는데 일부러 올바른 형식을 따르지 않고 등록을 시도할 경우?

위와 같이 또 App Crash가 발생할것이다.

이제 이런부분에 대해 Exception Handling을 하도록 한다.


문제해결

우선 강제 언래핑을 했던 값에 대해서 옵셔널 바인딩을 해주도록 하자.

1
2
3
4
Button("Add Data") {
    guard let value = Double(valueToAdd) else {
        return
    }

이때 이렇게 return으로 끝나는게 아니라 Error Case를 추가하여, Alert를 띄워본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
enum STError: LocalizedError {
    // case 생략
    case invalidValue // new

    var errorDescription: String? {
        switch self {
        // 중략
        case .invalidValue: // new
            "Invalid Value"
        }
    }
    
    var failureReason: String {
        switch self {
        // 중략
        case .invalidValue: // new
            "Must be a numeric value with a maximum of one decimal place."
        }
    }
}

그리고 ListView에 적용

1
2
3
.alert(isPresented: $isShowingAlert, error: writeError, actions: { writeError in
                switch writeError {
                case .authNotDetermined, .noData, .unableToCompleteRequest, .invalidValue: // modified

이후 옵셔널 바인딩한 부분 수정

1
2
3
4
5
6
guard let value = Double(valueToAdd) else {
    writeError = .invalidValue
    isShowingAlert = true
    valueToAdd = ""
    return
}

실행하면?

Dec-17-2024 01-03-04

이렇게 Alert가 뜨는걸 알 수 있다.


Github: Step-Tracker Repository

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.