Skip to content

Commit eb89c63

Browse files
authored
Merge pull request #2341 from LoopKit/ps2/ios26
Bolus view fixes, and updates for iOS26
2 parents c2e1a73 + 01412d9 commit eb89c63

File tree

4 files changed

+81
-83
lines changed

4 files changed

+81
-83
lines changed

Loop/View Controllers/StatusTableViewController.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,11 @@ final class StatusTableViewController: LoopChartsTableViewController {
380380

381381
override func reloadData(animated: Bool = false) {
382382
dispatchPrecondition(condition: .onQueue(.main))
383+
384+
guard view.window != nil else {
385+
return
386+
}
387+
383388
// This should be kept up to date immediately
384389
hudView?.loopCompletionHUD.lastLoopCompleted = deviceManager.loopManager.lastLoopCompleted
385390

Loop/Views/BolusEntryView.swift

Lines changed: 38 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -18,45 +18,30 @@ struct BolusEntryView: View {
1818
@EnvironmentObject private var displayGlucosePreference: DisplayGlucosePreference
1919
@Environment(\.dismissAction) var dismiss
2020
@Environment(\.appName) var appName
21-
21+
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
22+
2223
@ObservedObject var viewModel: BolusEntryViewModel
2324

2425
@State private var enteredBolusString = ""
25-
@State private var shouldBolusEntryBecomeFirstResponder = false
26-
2726
@State private var isInteractingWithChart = false
28-
@State private var isKeyboardVisible = false
29-
@State private var pickerShouldExpand = false
3027
@State private var editedBolusAmount = false
3128

29+
@FocusState private var bolusFieldFocused: Bool
30+
31+
private var accessoryClearance: CGFloat {
32+
dynamicTypeSize.isAccessibilitySize ? 72 : 52
33+
}
34+
3235
var body: some View {
3336
GeometryReader { geometry in
3437
VStack(spacing: 0) {
3538
List {
3639
self.chartSection
3740
self.summarySection
3841
}
39-
// As of iOS 13, we can't programmatically scroll to the Bolus entry text field. This ugly hack scoots the
40-
// list up instead, so the summarySection is visible and the keyboard shows when you tap "Enter Bolus".
41-
// Unfortunately, after entry, the field scoots back down and remains hidden. So this is not a great solution.
42-
// TODO: Fix this in Xcode 12 when we're building for iOS 14.
43-
.padding(.top, self.shouldAutoScroll(basedOn: geometry) ? -200 : -28)
4442
.insetGroupedListStyle()
4543

46-
self.actionArea
47-
.frame(height: self.isKeyboardVisible ? 0 : nil)
48-
.opacity(self.isKeyboardVisible ? 0 : 1)
4944
}
50-
.onKeyboardStateChange { state in
51-
self.isKeyboardVisible = state.height > 0
52-
53-
if state.height == 0 {
54-
// Ensure tapping 'Enter Bolus' can make the text field the first responder again
55-
self.shouldBolusEntryBecomeFirstResponder = false
56-
}
57-
}
58-
.keyboardAware()
59-
.edgesIgnoringSafeArea(self.isKeyboardVisible ? [] : .bottom)
6045
.navigationBarTitle(self.title)
6146
.supportedInterfaceOrientations(.portrait)
6247
.alert(item: self.$viewModel.activeAlert, content: self.alert(for:))
@@ -73,6 +58,14 @@ struct BolusEntryView: View {
7358
enteredBolusStringBinding.wrappedValue = newEnteredBolusString
7459
}
7560
}
61+
.safeAreaInset(edge: .bottom, spacing: 0) {
62+
if bolusFieldFocused {
63+
// Reserve space so the toolbar doesn’t overlap the field
64+
Color.clear.frame(height: accessoryClearance)
65+
} else {
66+
actionArea
67+
}
68+
}
7669
}
7770
}
7871

@@ -83,12 +76,6 @@ struct BolusEntryView: View {
8376
return Text("Meal Bolus", comment: "Title for bolus entry screen when also entering carbs")
8477
}
8578

86-
private func shouldAutoScroll(basedOn geometry: GeometryProxy) -> Bool {
87-
// Taking a guess of 640 to cover iPhone SE, iPod Touch, and other smaller devices.
88-
// Devices such as the iPhone 11 Pro Max do not need to auto-scroll.
89-
return shouldBolusEntryBecomeFirstResponder && geometry.size.height > 640
90-
}
91-
9279
private var chartSection: some View {
9380
Section {
9481
VStack(spacing: 8) {
@@ -253,18 +240,27 @@ struct BolusEntryView: View {
253240
Text("Bolus", comment: "Label for bolus entry row on bolus screen")
254241
Spacer()
255242
HStack(alignment: .firstTextBaseline) {
256-
DismissibleKeyboardTextField(
257-
text: enteredBolusStringBinding,
258-
placeholder: viewModel.formatBolusAmount(0.0),
259-
font: .preferredFont(forTextStyle: .title1),
260-
textColor: .loopAccent,
261-
textAlignment: .right,
262-
keyboardType: .decimalPad,
263-
shouldBecomeFirstResponder: shouldBolusEntryBecomeFirstResponder,
264-
maxLength: 5,
265-
doneButtonColor: .loopAccent,
266-
textFieldDidBeginEditing: didBeginEditing
267-
)
243+
TextField(viewModel.formatBolusAmount(0.0), text: enteredBolusStringBinding)
244+
.keyboardType(.decimalPad)
245+
.textInputAutocapitalization(.never)
246+
.disableAutocorrection(true)
247+
.font(.title)
248+
.multilineTextAlignment(.trailing)
249+
.foregroundColor(.loopAccent)
250+
.focused($bolusFieldFocused)
251+
.onTapGesture { didBeginEditing() }
252+
.onChange(of: enteredBolusString) { newValue in
253+
if newValue.count > 5 {
254+
enteredBolusString = String(newValue.prefix(5))
255+
viewModel.updateEnteredBolus(enteredBolusString)
256+
}
257+
}
258+
.toolbar {
259+
ToolbarItemGroup(placement: .keyboard) {
260+
Spacer()
261+
Button("Done") { bolusFieldFocused = false }
262+
}
263+
}
268264
bolusUnitsLabel
269265
}
270266
}
@@ -354,7 +350,7 @@ struct BolusEntryView: View {
354350
Button<Text>(
355351
action: {
356352
if self.viewModel.actionButtonAction == .enterBolus {
357-
self.shouldBolusEntryBecomeFirstResponder = true
353+
self.bolusFieldFocused = true
358354
} else {
359355
Task {
360356
if await self.viewModel.didPressActionButton() {

Loop/Views/ManualEntryDoseView.swift

Lines changed: 36 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,59 +15,47 @@ import LoopUI
1515

1616

1717
struct ManualEntryDoseView: View {
18+
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
19+
1820
@ObservedObject var viewModel: ManualEntryDoseViewModel
1921

2022
@State private var enteredBolusString = ""
21-
@State private var shouldBolusEntryBecomeFirstResponder = false
22-
2323
@State private var isInteractingWithChart = false
24-
@State private var isKeyboardVisible = false
24+
25+
@FocusState private var bolusFieldFocused: Bool
2526

2627
@Environment(\.dismissAction) var dismiss
2728

29+
private var accessoryClearance: CGFloat {
30+
dynamicTypeSize.isAccessibilitySize ? 72 : 52
31+
}
32+
2833
var body: some View {
2934
GeometryReader { geometry in
3035
VStack(spacing: 0) {
3136
List {
3237
self.chartSection
3338
self.summarySection
3439
}
35-
// As of iOS 13, we can't programmatically scroll to the Bolus entry text field. This ugly hack scoots the
36-
// list up instead, so the summarySection is visible and the keyboard shows when you tap "Enter Bolus".
37-
// Unfortunately, after entry, the field scoots back down and remains hidden. So this is not a great solution.
38-
// TODO: Fix this in Xcode 12 when we're building for iOS 14.
39-
.padding(.top, self.shouldAutoScroll(basedOn: geometry) ? -200 : -28)
4040
.insetGroupedListStyle()
41-
42-
self.actionArea
43-
.frame(height: self.isKeyboardVisible ? 0 : nil)
44-
.opacity(self.isKeyboardVisible ? 0 : 1)
45-
}
46-
.onKeyboardStateChange { state in
47-
self.isKeyboardVisible = state.height > 0
48-
49-
if state.height == 0 {
50-
// Ensure tapping 'Enter Bolus' can make the text field the first responder again
51-
self.shouldBolusEntryBecomeFirstResponder = false
52-
}
5341
}
54-
.keyboardAware()
55-
.edgesIgnoringSafeArea(self.isKeyboardVisible ? [] : .bottom)
5642
.navigationBarTitle(self.title)
5743
.supportedInterfaceOrientations(.portrait)
44+
.safeAreaInset(edge: .bottom, spacing: 0) {
45+
if bolusFieldFocused {
46+
// Reserve space so the toolbar doesn’t overlap the field
47+
Color.clear.frame(height: accessoryClearance)
48+
} else {
49+
actionArea
50+
}
51+
}
5852
}
5953
}
6054

6155
private var title: Text {
6256
return Text("Log Dose", comment: "Title for dose logging screen")
6357
}
6458

65-
private func shouldAutoScroll(basedOn geometry: GeometryProxy) -> Bool {
66-
// Taking a guess of 640 to cover iPhone SE, iPod Touch, and other smaller devices.
67-
// Devices such as the iPhone 11 Pro Max do not need to auto-scroll.
68-
shouldBolusEntryBecomeFirstResponder && geometry.size.height < 640
69-
}
70-
7159
private var chartSection: some View {
7260
Section {
7361
VStack(spacing: 8) {
@@ -189,16 +177,25 @@ struct ManualEntryDoseView: View {
189177
Text("Bolus", comment: "Label for bolus entry row on bolus screen")
190178
Spacer()
191179
HStack(alignment: .firstTextBaseline) {
192-
DismissibleKeyboardTextField(
193-
text: typedBolusEntry,
194-
placeholder: Self.doseAmountFormatter.string(from: 0.0)!,
195-
font: .preferredFont(forTextStyle: .title1),
196-
textColor: .loopAccent,
197-
textAlignment: .right,
198-
keyboardType: .decimalPad,
199-
shouldBecomeFirstResponder: shouldBolusEntryBecomeFirstResponder,
200-
maxLength: 5
201-
)
180+
TextField(Self.doseAmountFormatter.string(from: 0.0)!, text: typedBolusEntry)
181+
.keyboardType(.decimalPad)
182+
.textInputAutocapitalization(.never)
183+
.disableAutocorrection(true)
184+
.font(.title)
185+
.multilineTextAlignment(.trailing)
186+
.foregroundColor(.loopAccent)
187+
.focused($bolusFieldFocused)
188+
.onChange(of: enteredBolusString) { newValue in
189+
if newValue.count > 5 {
190+
enteredBolusString = String(newValue.prefix(5))
191+
}
192+
}
193+
.toolbar {
194+
ToolbarItemGroup(placement: .keyboard) {
195+
Spacer()
196+
Button("Done") { bolusFieldFocused = false }
197+
}
198+
}
202199
bolusUnitsLabel
203200
}
204201
}
@@ -250,7 +247,7 @@ struct ManualEntryDoseView: View {
250247
}
251248
}
252249

253-
extension InsulinType: Labeled {
250+
extension InsulinType: @retroactive Labeled {
254251
public var label: String {
255252
return title
256253
}

LoopUI/Views/DeviceStatusHUDView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ import LoopKitUI
3232
// round the edges of the progress view
3333
progressView.layer.cornerRadius = 2
3434
progressView.clipsToBounds = true
35-
progressView.layer.sublayers![1].cornerRadius = 2
36-
progressView.subviews[1].clipsToBounds = true
35+
progressView.layer.sublayers!.last!.cornerRadius = 2
36+
progressView.subviews.last!.clipsToBounds = true
3737
}
3838
}
3939

0 commit comments

Comments
 (0)