forked from LoopKit/LoopKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathQuantityPicker.swift
More file actions
143 lines (128 loc) · 4.88 KB
/
QuantityPicker.swift
File metadata and controls
143 lines (128 loc) · 4.88 KB
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
//
// QuantityPicker.swift
// LoopKitUI
//
// Created by Michael Pangburn on 4/23/20.
// Copyright © 2020 LoopKit Authors. All rights reserved.
//
import SwiftUI
import HealthKit
import LoopKit
private struct PickerValueBoundsKey: PreferenceKey {
static let defaultValue: [Anchor<CGRect>] = []
static func reduce(value: inout [Anchor<CGRect>], nextValue: () -> [Anchor<CGRect>]) {
value.append(contentsOf: nextValue())
}
}
public struct QuantityPicker: View {
@Binding var value: HKQuantity
var unit: HKUnit
var isUnitLabelVisible: Bool
var colorForValue: (_ value: Double) -> Color
private let selectableValues: [Double]
private let formatter: NumberFormatter
private let unitLabelSpacing: CGFloat = -6
public init(
value: Binding<HKQuantity>,
unit: HKUnit,
guardrail: Guardrail<HKQuantity>,
formatter: NumberFormatter? = nil,
isUnitLabelVisible: Bool = true,
guidanceColors: GuidanceColors = GuidanceColors()
) {
let selectableValues = guardrail.allValues(forUnit: unit)
self.init(value: value,
unit: unit,
guardrail: guardrail,
selectableValues: selectableValues,
formatter: formatter,
isUnitLabelVisible: isUnitLabelVisible,
guidanceColors: guidanceColors)
}
public init(
value: Binding<HKQuantity>,
unit: HKUnit,
guardrail: Guardrail<HKQuantity>,
selectableValues: [Double],
formatter: NumberFormatter? = nil,
isUnitLabelVisible: Bool = true,
guidanceColors: GuidanceColors
) {
self.init(
value: value,
unit: unit,
selectableValues: selectableValues.map { unit.roundForPicker(value: $0) },
formatter: formatter,
isUnitLabelVisible: isUnitLabelVisible,
colorForValue: { value in
let quantity = HKQuantity(unit: unit, doubleValue: value)
return guardrail.color(for: quantity, guidanceColors: guidanceColors)
}
)
}
public init(
value: Binding<HKQuantity>,
unit: HKUnit,
selectableValues: [Double],
formatter: NumberFormatter? = nil,
isUnitLabelVisible: Bool = true,
colorForValue: @escaping (_ value: Double) -> Color = { _ in .primary }
) {
self._value = value
self.unit = unit
self.selectableValues = selectableValues
self.formatter = formatter ?? {
let quantityFormatter = QuantityFormatter(for: unit)
return quantityFormatter.numberFormatter
}()
self.isUnitLabelVisible = isUnitLabelVisible
self.colorForValue = colorForValue
}
private var selectedValue: Binding<Double> {
Binding(
get: {
unit.roundForPicker(value: value.doubleValue(for: unit))
},
set: { newValue in
self.value = HKQuantity(unit: unit, doubleValue: newValue)
}
)
}
public var body: some View {
picker
.labelsHidden()
.pickerStyle(.wheel)
.overlayPreferenceValue(PickerValueBoundsKey.self, unitLabel(positionedFrom:))
.accessibility(identifier: "quantity_picker")
}
@ViewBuilder
private var picker: some View {
// NOTE: iOS 15.1 introduced an issue where SwiftUI Pickers would not obey the `.clipped()`
// directive when it comes to touchable area. I have submitted a bug (Feedback) to Apple (FB9788944).
// This uses a custom Picker that works around the issue, but not perfectly (it isn't a 1 to 1 match).
// If they ever do fix this, consider restoring the code from the commit prior to this change.
// See LOOP-3870 for more details.
ResizeablePicker(selection: selectedValue,
data: selectableValues,
formatter: { self.formatter.string(from: $0) ?? "\($0)" },
colorer: colorForValue)
.anchorPreference(key: PickerValueBoundsKey.self, value: .bounds, transform: { [$0] })
}
private func unitLabel(positionedFrom pickerValueBounds: [Anchor<CGRect>]) -> some View {
GeometryReader { geometry in
if self.isUnitLabelVisible && !pickerValueBounds.isEmpty {
Text(self.unit.shortLocalizedUnitString())
.foregroundColor(.gray)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.offset(x: pickerValueBounds.union(in: geometry).maxX + unitLabelSpacing)
}
}
}
}
extension Sequence where Element == Anchor<CGRect> {
func union(in geometry: GeometryProxy) -> CGRect {
lazy
.map { geometry[$0] }
.reduce(.null) { $0.union($1) }
}
}