Lower deployment target from iOS 26 to iOS 18, gate Foundation Models behind @available

Broadens installable audience to iOS 18+ while keeping AI insights available on iOS 26.
Foundation Models types and service wrapped in @available(iOS 26, *), InsightsViewModel
conditionally instantiates the service with fallback UI on older versions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-23 20:21:45 -06:00
parent 7660521540
commit b2b6931d7c
5 changed files with 53 additions and 26 deletions

View File

@@ -43,7 +43,8 @@ class InsightsViewModel: ObservableObject {
// MARK: - Dependencies
private let insightService = FoundationModelsInsightService()
/// Stored as Any? to avoid referencing @available(iOS 26, *) type at the property level
private var insightService: Any?
private let healthService = HealthService.shared
private let calendar = Calendar.current
@@ -52,7 +53,14 @@ class InsightsViewModel: ObservableObject {
private var dataListenerToken: DataController.DataListenerToken?
init() {
isAIAvailable = insightService.isAvailable
if #available(iOS 26, *) {
let service = FoundationModelsInsightService()
insightService = service
isAIAvailable = service.isAvailable
} else {
insightService = nil
isAIAvailable = false
}
dataListenerToken = DataController.shared.addNewDataListener { [weak self] in
self?.onDataChanged()
@@ -70,7 +78,9 @@ class InsightsViewModel: ObservableObject {
/// Called when mood data changes in another tab. Invalidates cached insights
/// so they are regenerated with fresh data on next view appearance.
private func onDataChanged() {
insightService.invalidateCache()
if #available(iOS 26, *), let service = insightService as? FoundationModelsInsightService {
service.invalidateCache()
}
monthLoadingState = .idle
yearLoadingState = .idle
allTimeLoadingState = .idle
@@ -87,7 +97,9 @@ class InsightsViewModel: ObservableObject {
/// Force refresh all insights (invalidates cache)
func refreshInsights() {
insightService.invalidateCache()
if #available(iOS 26, *), let service = insightService as? FoundationModelsInsightService {
service.invalidateCache()
}
generateInsights()
}
@@ -179,24 +191,34 @@ class InsightsViewModel: ObservableObject {
healthAverages = healthService.computeHealthAverages(entries: validEntries, healthData: healthData)
}
do {
let insights = try await insightService.generateInsights(
for: validEntries,
periodName: periodName,
count: 5,
healthAverages: healthAverages
)
updateInsights(insights)
updateState(.loaded)
} catch {
// On error, provide a helpful message
if #available(iOS 26, *), let service = insightService as? FoundationModelsInsightService {
do {
let insights = try await service.generateInsights(
for: validEntries,
periodName: periodName,
count: 5,
healthAverages: healthAverages
)
updateInsights(insights)
updateState(.loaded)
} catch {
// On error, provide a helpful message
updateInsights([Insight(
icon: "exclamationmark.triangle",
title: "Insights Unavailable",
description: "Unable to generate AI insights right now. Please try again later.",
mood: nil
)])
updateState(.error(error.localizedDescription))
}
} else {
updateInsights([Insight(
icon: "exclamationmark.triangle",
title: "Insights Unavailable",
description: "Unable to generate AI insights right now. Please try again later.",
icon: "brain.head.profile",
title: "AI Unavailable",
description: "Apple Intelligence is required for personalized insights. Please enable it in Settings.",
mood: nil
)])
updateState(.error(error.localizedDescription))
updateState(.error("AI not available"))
}
}
}