SharedConstants
Centralized configuration values used across the app, widgets, and extensions. Defines app group identifiers, keychain settings, and shared storage keys.
Properties
App group identifier for sharing data between app and extensionsValue: "group.com.mattbolanos.stratiles"
Keychain access group for sharing tokens between app and extensionsValue: "$(AppIdentifierPrefix)com.mattbolanos.stratiles"
selectedActivityTypesDefaultsKey
UserDefaults key for storing selected activity typesValue: "selectedActivityTypes"
Filename for the activity cache JSON fileValue: "activity-cache.json"
sharedDefaults
Returns the shared UserDefaults instance for the app group.
public static var sharedDefaults: UserDefaults?
Returns: UserDefaults instance for the app group, or nil if app group is not available
Example:
if let defaults = SharedConstants.sharedDefaults {
defaults.set(true, forKey: "hasCompletedOnboarding")
}
SharedActivityTypeSettings
Utility functions for loading and saving activity type preferences to shared storage. This allows the app and widgets to stay in sync with the user’s activity filter selection.
loadSelectedTypes
Loads the user’s selected activity types from shared storage.
public static func loadSelectedTypes() -> Set<ActivityType>
Returns: Set of selected activity types. Returns ActivityType.defaultSelected if no preferences are stored or the stored value is empty.
Default Selection:
ActivityType.defaultSelected = [.run, .ride, .walk, .trailRun, .hike, .wheelchair]
Example:
let selectedTypes = SharedActivityTypeSettings.loadSelectedTypes()
print("User selected: \(selectedTypes)")
// Fetch activities for selected types
let activities = try await StravaAPIClient.shared.fetchActivities(
selectedTypes: selectedTypes,
maxPages: 8,
perPage: 100,
after: oneYearAgo
)
saveSelectedTypes
Saves the user’s selected activity types to shared storage.
public static func saveSelectedTypes(_ types: Set<ActivityType>)
types
Set<ActivityType>
required
The activity types to save
Implementation Details:
- Converts activity types to sorted array of raw string values
- Stores in shared UserDefaults with key
SharedConstants.selectedActivityTypesDefaultsKey
- Automatically accessible by app and widgets
Example:
// User toggles activity type in settings
let newSelection: Set<ActivityType> = [.run, .ride, .swim]
SharedActivityTypeSettings.saveSelectedTypes(newSelection)
// Widget automatically picks up the change
WidgetCenter.shared.reloadAllTimelines()
Usage Patterns
App Group Configuration
Ensure your app has the proper entitlements:
<!-- Entitlements.plist -->
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.mattbolanos.stratiles</string>
</array>
Cross-Target Data Sharing
In the main app:
// Save activity type preference
SharedActivityTypeSettings.saveSelectedTypes([.run, .ride])
// Save other shared data
SharedConstants.sharedDefaults?.set(Date(), forKey: "lastSync")
In the widget:
// Load the same preference
let types = SharedActivityTypeSettings.loadSelectedTypes()
// Access cache
let cache = ActivityCache.shared
let cached = await cache.read(selectedTypes: types)
When activity type selection changes:
import WidgetKit
func handleActivityTypeChange(_ newTypes: Set<ActivityType>) {
// 1. Save to shared storage
SharedActivityTypeSettings.saveSelectedTypes(newTypes)
// 2. Reload widget timelines
WidgetCenter.shared.reloadAllTimelines()
// 3. Notify other parts of the app
NotificationCenter.default.post(
name: .activityTypesChanged,
object: newTypes
)
}
Cache File Location
The activity cache is stored in the app group container:
let fileManager = FileManager.default
let container = fileManager.containerURL(
forSecurityApplicationGroupIdentifier: SharedConstants.appGroupIdentifier
)
let cacheURL = container?.appendingPathComponent(SharedConstants.cacheFileName)
// Read cache
if let url = cacheURL, let data = try? Data(contentsOf: url) {
let cache = try JSONDecoder().decode(Store.self, from: data)
}
Complete Example
import StratilesCore
import WidgetKit
// Settings screen
@State private var selectedTypes = SharedActivityTypeSettings.loadSelectedTypes()
var body: some View {
Form {
ForEach(ActivityType.allCases, id: \.self) { type in
Toggle(type.displayName, isOn: binding(for: type))
}
}
.onChange(of: selectedTypes) { _, newValue in
// Ensure at least one type is selected
let nonEmpty = newValue.isEmpty ? ActivityType.defaultSelected : newValue
// Save to shared storage
SharedActivityTypeSettings.saveSelectedTypes(nonEmpty)
// Update widgets
WidgetCenter.shared.reloadAllTimelines()
// Refresh stats view
Task {
await refreshStats(with: nonEmpty)
}
}
}