diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index 0e14f2167c..94df013244 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -380,6 +380,11 @@ final class StatusTableViewController: LoopChartsTableViewController { override func reloadData(animated: Bool = false) { dispatchPrecondition(condition: .onQueue(.main)) + + guard view.window != nil else { + return + } + // This should be kept up to date immediately hudView?.loopCompletionHUD.lastLoopCompleted = deviceManager.loopManager.lastLoopCompleted diff --git a/Loop/Views/BolusEntryView.swift b/Loop/Views/BolusEntryView.swift index 3a25576f01..10e6fa4ccd 100644 --- a/Loop/Views/BolusEntryView.swift +++ b/Loop/Views/BolusEntryView.swift @@ -18,17 +18,20 @@ struct BolusEntryView: View { @EnvironmentObject private var displayGlucosePreference: DisplayGlucosePreference @Environment(\.dismissAction) var dismiss @Environment(\.appName) var appName - + @Environment(\.dynamicTypeSize) private var dynamicTypeSize + @ObservedObject var viewModel: BolusEntryViewModel @State private var enteredBolusString = "" - @State private var shouldBolusEntryBecomeFirstResponder = false - @State private var isInteractingWithChart = false - @State private var isKeyboardVisible = false - @State private var pickerShouldExpand = false @State private var editedBolusAmount = false + @FocusState private var bolusFieldFocused: Bool + + private var accessoryClearance: CGFloat { + dynamicTypeSize.isAccessibilitySize ? 72 : 52 + } + var body: some View { GeometryReader { geometry in VStack(spacing: 0) { @@ -36,27 +39,9 @@ struct BolusEntryView: View { self.chartSection self.summarySection } - // As of iOS 13, we can't programmatically scroll to the Bolus entry text field. This ugly hack scoots the - // list up instead, so the summarySection is visible and the keyboard shows when you tap "Enter Bolus". - // Unfortunately, after entry, the field scoots back down and remains hidden. So this is not a great solution. - // TODO: Fix this in Xcode 12 when we're building for iOS 14. - .padding(.top, self.shouldAutoScroll(basedOn: geometry) ? -200 : -28) .insetGroupedListStyle() - self.actionArea - .frame(height: self.isKeyboardVisible ? 0 : nil) - .opacity(self.isKeyboardVisible ? 0 : 1) } - .onKeyboardStateChange { state in - self.isKeyboardVisible = state.height > 0 - - if state.height == 0 { - // Ensure tapping 'Enter Bolus' can make the text field the first responder again - self.shouldBolusEntryBecomeFirstResponder = false - } - } - .keyboardAware() - .edgesIgnoringSafeArea(self.isKeyboardVisible ? [] : .bottom) .navigationBarTitle(self.title) .supportedInterfaceOrientations(.portrait) .alert(item: self.$viewModel.activeAlert, content: self.alert(for:)) @@ -73,6 +58,14 @@ struct BolusEntryView: View { enteredBolusStringBinding.wrappedValue = newEnteredBolusString } } + .safeAreaInset(edge: .bottom, spacing: 0) { + if bolusFieldFocused { + // Reserve space so the toolbar doesn’t overlap the field + Color.clear.frame(height: accessoryClearance) + } else { + actionArea + } + } } } @@ -83,12 +76,6 @@ struct BolusEntryView: View { return Text("Meal Bolus", comment: "Title for bolus entry screen when also entering carbs") } - private func shouldAutoScroll(basedOn geometry: GeometryProxy) -> Bool { - // Taking a guess of 640 to cover iPhone SE, iPod Touch, and other smaller devices. - // Devices such as the iPhone 11 Pro Max do not need to auto-scroll. - return shouldBolusEntryBecomeFirstResponder && geometry.size.height > 640 - } - private var chartSection: some View { Section { VStack(spacing: 8) { @@ -253,18 +240,27 @@ struct BolusEntryView: View { Text("Bolus", comment: "Label for bolus entry row on bolus screen") Spacer() HStack(alignment: .firstTextBaseline) { - DismissibleKeyboardTextField( - text: enteredBolusStringBinding, - placeholder: viewModel.formatBolusAmount(0.0), - font: .preferredFont(forTextStyle: .title1), - textColor: .loopAccent, - textAlignment: .right, - keyboardType: .decimalPad, - shouldBecomeFirstResponder: shouldBolusEntryBecomeFirstResponder, - maxLength: 5, - doneButtonColor: .loopAccent, - textFieldDidBeginEditing: didBeginEditing - ) + TextField(viewModel.formatBolusAmount(0.0), text: enteredBolusStringBinding) + .keyboardType(.decimalPad) + .textInputAutocapitalization(.never) + .disableAutocorrection(true) + .font(.title) + .multilineTextAlignment(.trailing) + .foregroundColor(.loopAccent) + .focused($bolusFieldFocused) + .onTapGesture { didBeginEditing() } + .onChange(of: enteredBolusString) { newValue in + if newValue.count > 5 { + enteredBolusString = String(newValue.prefix(5)) + viewModel.updateEnteredBolus(enteredBolusString) + } + } + .toolbar { + ToolbarItemGroup(placement: .keyboard) { + Spacer() + Button("Done") { bolusFieldFocused = false } + } + } bolusUnitsLabel } } @@ -354,7 +350,7 @@ struct BolusEntryView: View { Button( action: { if self.viewModel.actionButtonAction == .enterBolus { - self.shouldBolusEntryBecomeFirstResponder = true + self.bolusFieldFocused = true } else { Task { if await self.viewModel.didPressActionButton() { diff --git a/Loop/Views/ManualEntryDoseView.swift b/Loop/Views/ManualEntryDoseView.swift index e81dccdabb..0c6700e6eb 100644 --- a/Loop/Views/ManualEntryDoseView.swift +++ b/Loop/Views/ManualEntryDoseView.swift @@ -15,16 +15,21 @@ import LoopUI struct ManualEntryDoseView: View { + @Environment(\.dynamicTypeSize) private var dynamicTypeSize + @ObservedObject var viewModel: ManualEntryDoseViewModel @State private var enteredBolusString = "" - @State private var shouldBolusEntryBecomeFirstResponder = false - @State private var isInteractingWithChart = false - @State private var isKeyboardVisible = false + + @FocusState private var bolusFieldFocused: Bool @Environment(\.dismissAction) var dismiss + private var accessoryClearance: CGFloat { + dynamicTypeSize.isAccessibilitySize ? 72 : 52 + } + var body: some View { GeometryReader { geometry in VStack(spacing: 0) { @@ -32,29 +37,18 @@ struct ManualEntryDoseView: View { self.chartSection self.summarySection } - // As of iOS 13, we can't programmatically scroll to the Bolus entry text field. This ugly hack scoots the - // list up instead, so the summarySection is visible and the keyboard shows when you tap "Enter Bolus". - // Unfortunately, after entry, the field scoots back down and remains hidden. So this is not a great solution. - // TODO: Fix this in Xcode 12 when we're building for iOS 14. - .padding(.top, self.shouldAutoScroll(basedOn: geometry) ? -200 : -28) .insetGroupedListStyle() - - self.actionArea - .frame(height: self.isKeyboardVisible ? 0 : nil) - .opacity(self.isKeyboardVisible ? 0 : 1) - } - .onKeyboardStateChange { state in - self.isKeyboardVisible = state.height > 0 - - if state.height == 0 { - // Ensure tapping 'Enter Bolus' can make the text field the first responder again - self.shouldBolusEntryBecomeFirstResponder = false - } } - .keyboardAware() - .edgesIgnoringSafeArea(self.isKeyboardVisible ? [] : .bottom) .navigationBarTitle(self.title) .supportedInterfaceOrientations(.portrait) + .safeAreaInset(edge: .bottom, spacing: 0) { + if bolusFieldFocused { + // Reserve space so the toolbar doesn’t overlap the field + Color.clear.frame(height: accessoryClearance) + } else { + actionArea + } + } } } @@ -62,12 +56,6 @@ struct ManualEntryDoseView: View { return Text("Log Dose", comment: "Title for dose logging screen") } - private func shouldAutoScroll(basedOn geometry: GeometryProxy) -> Bool { - // Taking a guess of 640 to cover iPhone SE, iPod Touch, and other smaller devices. - // Devices such as the iPhone 11 Pro Max do not need to auto-scroll. - shouldBolusEntryBecomeFirstResponder && geometry.size.height < 640 - } - private var chartSection: some View { Section { VStack(spacing: 8) { @@ -189,16 +177,25 @@ struct ManualEntryDoseView: View { Text("Bolus", comment: "Label for bolus entry row on bolus screen") Spacer() HStack(alignment: .firstTextBaseline) { - DismissibleKeyboardTextField( - text: typedBolusEntry, - placeholder: Self.doseAmountFormatter.string(from: 0.0)!, - font: .preferredFont(forTextStyle: .title1), - textColor: .loopAccent, - textAlignment: .right, - keyboardType: .decimalPad, - shouldBecomeFirstResponder: shouldBolusEntryBecomeFirstResponder, - maxLength: 5 - ) + TextField(Self.doseAmountFormatter.string(from: 0.0)!, text: typedBolusEntry) + .keyboardType(.decimalPad) + .textInputAutocapitalization(.never) + .disableAutocorrection(true) + .font(.title) + .multilineTextAlignment(.trailing) + .foregroundColor(.loopAccent) + .focused($bolusFieldFocused) + .onChange(of: enteredBolusString) { newValue in + if newValue.count > 5 { + enteredBolusString = String(newValue.prefix(5)) + } + } + .toolbar { + ToolbarItemGroup(placement: .keyboard) { + Spacer() + Button("Done") { bolusFieldFocused = false } + } + } bolusUnitsLabel } } @@ -250,7 +247,7 @@ struct ManualEntryDoseView: View { } } -extension InsulinType: Labeled { +extension InsulinType: @retroactive Labeled { public var label: String { return title } diff --git a/LoopUI/Views/DeviceStatusHUDView.swift b/LoopUI/Views/DeviceStatusHUDView.swift index 3951aca3ec..904f9d44cb 100644 --- a/LoopUI/Views/DeviceStatusHUDView.swift +++ b/LoopUI/Views/DeviceStatusHUDView.swift @@ -32,8 +32,8 @@ import LoopKitUI // round the edges of the progress view progressView.layer.cornerRadius = 2 progressView.clipsToBounds = true - progressView.layer.sublayers![1].cornerRadius = 2 - progressView.subviews[1].clipsToBounds = true + progressView.layer.sublayers!.last!.cornerRadius = 2 + progressView.subviews.last!.clipsToBounds = true } }