diff --git a/MinimedKit.xcodeproj/project.pbxproj b/MinimedKit.xcodeproj/project.pbxproj index 9f518b4..3ccd58d 100644 --- a/MinimedKit.xcodeproj/project.pbxproj +++ b/MinimedKit.xcodeproj/project.pbxproj @@ -265,6 +265,7 @@ C1E34B3129C7ABF3009A50A5 /* UseMySentrySelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E34B1929C7ABF3009A50A5 /* UseMySentrySelectionView.swift */; }; C1E34B3229C7ABF3009A50A5 /* DataSourceSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E34B1A29C7ABF3009A50A5 /* DataSourceSelectionView.swift */; }; C1E34B3329C7ABF3009A50A5 /* BatteryTypeSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E34B1B29C7ABF3009A50A5 /* BatteryTypeSelectionView.swift */; }; + B65C7B5E69AAD31D20C66C6A /* View+UIKitNavigationTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4918419DB490FB27EA2EC8FF /* View+UIKitNavigationTitle.swift */; }; C1E34B3429C7ABF3009A50A5 /* ReservoirHUDView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C1E34B1C29C7ABF3009A50A5 /* ReservoirHUDView.xib */; }; C1E34B3529C7ABF3009A50A5 /* MinimedPumpSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E34B1D29C7ABF3009A50A5 /* MinimedPumpSettingsView.swift */; }; C1E34B3629C7ABF3009A50A5 /* ReservoirHUDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E34B1E29C7ABF3009A50A5 /* ReservoirHUDView.swift */; }; @@ -614,6 +615,7 @@ C1E34B1929C7ABF3009A50A5 /* UseMySentrySelectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UseMySentrySelectionView.swift; sourceTree = ""; }; C1E34B1A29C7ABF3009A50A5 /* DataSourceSelectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataSourceSelectionView.swift; sourceTree = ""; }; C1E34B1B29C7ABF3009A50A5 /* BatteryTypeSelectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryTypeSelectionView.swift; sourceTree = ""; }; + 4918419DB490FB27EA2EC8FF /* View+UIKitNavigationTitle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+UIKitNavigationTitle.swift"; sourceTree = ""; }; C1E34B1C29C7ABF3009A50A5 /* ReservoirHUDView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ReservoirHUDView.xib; sourceTree = ""; }; C1E34B1D29C7ABF3009A50A5 /* MinimedPumpSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinimedPumpSettingsView.swift; sourceTree = ""; }; C1E34B1E29C7ABF3009A50A5 /* ReservoirHUDView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReservoirHUDView.swift; sourceTree = ""; }; @@ -1148,6 +1150,7 @@ C1E34B1929C7ABF3009A50A5 /* UseMySentrySelectionView.swift */, C1E34B1A29C7ABF3009A50A5 /* DataSourceSelectionView.swift */, C1E34B1B29C7ABF3009A50A5 /* BatteryTypeSelectionView.swift */, + 4918419DB490FB27EA2EC8FF /* View+UIKitNavigationTitle.swift */, C1E34B1C29C7ABF3009A50A5 /* ReservoirHUDView.xib */, C1E34B1D29C7ABF3009A50A5 /* MinimedPumpSettingsView.swift */, C1E34B1E29C7ABF3009A50A5 /* ReservoirHUDView.swift */, @@ -1681,6 +1684,7 @@ C1E34B2F29C7ABF3009A50A5 /* CommandResponseViewController.swift in Sources */, C1E34B3529C7ABF3009A50A5 /* MinimedPumpSettingsView.swift in Sources */, C1E34B3329C7ABF3009A50A5 /* BatteryTypeSelectionView.swift in Sources */, + B65C7B5E69AAD31D20C66C6A /* View+UIKitNavigationTitle.swift in Sources */, C1E34B2929C7ABF3009A50A5 /* MinimedPumpSentrySetupViewController.swift in Sources */, C1E34B9229C7B46C009A50A5 /* TimeZone.swift in Sources */, C1E34B8E29C7B34F009A50A5 /* NibLoadable.swift in Sources */, diff --git a/MinimedKitUI/Views/BatteryTypeSelectionView.swift b/MinimedKitUI/Views/BatteryTypeSelectionView.swift index 7bb0752..10e900c 100644 --- a/MinimedKitUI/Views/BatteryTypeSelectionView.swift +++ b/MinimedKitUI/Views/BatteryTypeSelectionView.swift @@ -31,6 +31,6 @@ struct BatteryTypeSelectionView: View { } } .insetGroupedListStyle() - .navigationTitle(LocalizedString("Pump Battery Type", comment: "navigation title for pump battery type selection")) + .uikitNavigationTitle(LocalizedString("Pump Battery Type", comment: "navigation title for pump battery type selection")) } } diff --git a/MinimedKitUI/Views/DataSourceSelectionView.swift b/MinimedKitUI/Views/DataSourceSelectionView.swift index 103c66a..fd1e67f 100644 --- a/MinimedKitUI/Views/DataSourceSelectionView.swift +++ b/MinimedKitUI/Views/DataSourceSelectionView.swift @@ -30,6 +30,6 @@ struct DataSourceSelectionView: View { } } .insetGroupedListStyle() - .navigationTitle(LocalizedString("Preferred Data Source", comment: "navigation title for pump battery type selection")) + .uikitNavigationTitle(LocalizedString("Preferred Data Source", comment: "navigation title for pump battery type selection")) } } diff --git a/MinimedKitUI/Views/MinimedPumpSettingsView.swift b/MinimedKitUI/Views/MinimedPumpSettingsView.swift index 69c2cc7..05f1d1d 100644 --- a/MinimedKitUI/Views/MinimedPumpSettingsView.swift +++ b/MinimedKitUI/Views/MinimedPumpSettingsView.swift @@ -156,7 +156,8 @@ struct MinimedPumpSettingsView: View { Section(header: Text(LocalizedString("Configuration", comment: "The title of the configuration section in MinimedPumpManager settings"))) { - NavigationLink(destination: InsulinTypeSetting(initialValue: viewModel.pumpManager.state.insulinType, supportedInsulinTypes: supportedInsulinTypes, allowUnsetInsulinType: false, didChange: viewModel.didChangeInsulinType)) { + NavigationLink(destination: InsulinTypeSetting(initialValue: viewModel.pumpManager.state.insulinType, supportedInsulinTypes: supportedInsulinTypes, allowUnsetInsulinType: false, didChange: viewModel.didChangeInsulinType) + .uikitNavigationTitle(LocalizedString("Insulin Type", comment: "Text for confidence reminders navigation link"))) { HStack { Text(LocalizedString("Insulin Type", comment: "Text for confidence reminders navigation link")).foregroundColor(Color.primary) if let currentTitle = viewModel.pumpManager.state.insulinType?.brandName { diff --git a/MinimedKitUI/Views/UseMySentrySelectionView.swift b/MinimedKitUI/Views/UseMySentrySelectionView.swift index e4b7d7e..8bb0b69 100644 --- a/MinimedKitUI/Views/UseMySentrySelectionView.swift +++ b/MinimedKitUI/Views/UseMySentrySelectionView.swift @@ -29,6 +29,6 @@ struct UseMySentrySelectionView: View { } } .insetGroupedListStyle() - .navigationTitle(LocalizedString("Use MySentry", comment: "navigation title for pump battery type selection")) + .uikitNavigationTitle(LocalizedString("Use MySentry", comment: "navigation title for pump battery type selection")) } } diff --git a/MinimedKitUI/Views/View+UIKitNavigationTitle.swift b/MinimedKitUI/Views/View+UIKitNavigationTitle.swift new file mode 100644 index 0000000..60f649f --- /dev/null +++ b/MinimedKitUI/Views/View+UIKitNavigationTitle.swift @@ -0,0 +1,91 @@ +// +// View+UIKitNavigationTitle.swift +// MinimedKitUI +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import SwiftUI +import UIKit + +extension View { + /// Sets the navigation bar title (and large-title display mode) for a SwiftUI screen pushed + /// via `NavigationLink` while the flow is hosted inside a UIKit `UINavigationController` + /// (as the Medtronic settings screens are, via `MinimedUICoordinator`). + func uikitNavigationTitle( + _ title: String, + displayMode: NavigationBarItem.TitleDisplayMode = .automatic + ) -> some View { + self + .navigationTitle(title) + .navigationBarTitleDisplayMode(displayMode) + .background(NavigationItemTitleSetter(title: title, largeTitleDisplayMode: displayMode.uiKitLargeTitleDisplayMode)) + } +} + +private extension NavigationBarItem.TitleDisplayMode { + var uiKitLargeTitleDisplayMode: UINavigationItem.LargeTitleDisplayMode { + switch self { + case .inline: return .never + case .large: return .always + case .automatic: return .automatic + @unknown default: return .automatic + } + } +} + +/// Sets `navigationItem.title` and `largeTitleDisplayMode` on the enclosing navigation +/// controller's top view controller. See `View.uikitNavigationTitle(_:displayMode:)`. +private struct NavigationItemTitleSetter: UIViewControllerRepresentable { + let title: String + let largeTitleDisplayMode: UINavigationItem.LargeTitleDisplayMode + + func makeUIViewController(context: Context) -> TitleProxyViewController { + TitleProxyViewController(title: title, largeTitleDisplayMode: largeTitleDisplayMode) + } + + func updateUIViewController(_ uiViewController: TitleProxyViewController, context: Context) { + uiViewController.proxyTitle = title + uiViewController.largeTitleDisplayMode = largeTitleDisplayMode + } + + final class TitleProxyViewController: UIViewController { + var proxyTitle: String { + didSet { applyTitle() } + } + + var largeTitleDisplayMode: UINavigationItem.LargeTitleDisplayMode { + didSet { applyTitle() } + } + + init(title: String, largeTitleDisplayMode: UINavigationItem.LargeTitleDisplayMode) { + self.proxyTitle = title + self.largeTitleDisplayMode = largeTitleDisplayMode + super.init(nibName: nil, bundle: nil) + view.isHidden = true + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + applyTitle() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + applyTitle() + } + + private func applyTitle() { + // The pushed SwiftUI screen is the navigation controller's top view controller; + // set the title and display mode the navigation bar actually uses. + guard let host = navigationController?.topViewController else { return } + host.navigationItem.title = proxyTitle + host.navigationItem.largeTitleDisplayMode = largeTitleDisplayMode + } + } +}