PowerKit is a Swift package that helps your app respect Low Power Mode and device thermal states by providing reactive power state monitoring and adaptive UI components for SwiftUI.
It solves the challenge of making iOS apps battery-conscious by offering:
- An observable
PowerModeMonitorfor tracking power states - Reactive SwiftUI environment integration
- Adaptive animation and visual effect modifiers
- Thermal state monitoring
- Battery level tracking (iOS only)
The goal is to provide a lightweight, well-documented way to make your SwiftUI apps respectful of device constraints without boilerplate.
Add PowerKit to your Swift project using Swift Package Manager.
dependencies: [
.package(url: "https://github.com/markbattistella/PowerKit", from: "1.0.0")
]Alternatively, you can add PowerKit using Xcode by navigating to File > Add Packages and entering the package repository URL.
Inject the power monitor into your app's environment:
import SwiftUI
import PowerKit
@main
struct MyApp: App {
@State private var powerMonitor = PowerModeMonitor()
var body: some Scene {
WindowGroup {
ContentView()
.powerKitEnvironment(powerMonitor)
}
}
}The .powerKitEnvironment(_:) modifier injects the monitor and bridges all its properties as individual environment values. This lets views access power state via @Environment(PowerModeMonitor.self) for the full monitor, or via key-path access like @Environment(\.isLowPowerModeEnabled) for individual values.
Manual setup (if you only need specific values)
You can also inject values individually:
ContentView()
.environment(powerMonitor)
.environment(\.isLowPowerModeEnabled, powerMonitor.isLowPowerModeEnabled)
.environment(\.thermalState, powerMonitor.thermalState)
.environment(\.isLowBatteryState, powerMonitor.isLowBatteryState)
.environment(\.shouldReducePerformance, powerMonitor.shouldReducePerformance)Access individual power state values in any view using environment key-paths:
struct ContentView: View {
@Environment(\.isLowPowerModeEnabled) private var isLowPowerModeEnabled
@Environment(\.thermalState) private var thermalState
@Environment(\.isLowBatteryState) private var isLowBatteryState
@Environment(\.shouldReducePerformance) private var shouldReducePerformance
var body: some View {
VStack {
if shouldReducePerformance {
Text("Reducing Performance")
} else {
Text("Normal Mode")
}
}
}
}Alternatively, access the full monitor directly:
struct ContentView: View {
@Environment(PowerModeMonitor.self) private var powerMonitor
var body: some View {
VStack {
if powerMonitor.shouldReducePerformance {
Text("Reducing Performance")
} else {
Text("Normal Mode")
}
}
}
}Automatically reduce animations when Low Power Mode is enabled:
struct AnimatedView: View {
@State private var scale: CGFloat = 1.0
var body: some View {
Circle()
.scaleEffect(scale)
.adaptiveAnimation(value: scale)
.onTapGesture {
scale = scale == 1.0 ? 1.5 : 1.0
}
}
}Custom animation configuration:
Circle()
.scaleEffect(scale)
.adaptiveAnimation(
normal: .spring(response: 0.6, dampingFraction: 0.8),
reduced: .linear(duration: 0.1),
value: scale
)Reduce expensive visual effects during Low Power Mode:
struct BackgroundView: View {
var body: some View {
VStack {
Text("Content")
}
.adaptivePowerMode(
normalContent: {
Color.clear.background(.ultraThinMaterial)
},
reducedContent: {
Color.gray.opacity(0.2)
}
)
}
}Show users when your app is being battery-conscious:
struct DashboardView: View {
var body: some View {
VStack {
PowerModeIndicator()
// Your content
}
}
}Custom message:
PowerModeIndicator(
message: "Battery Saver Active",
showIcon: true
)Access comprehensive power state information:
struct AdvancedView: View {
@Environment(PowerModeMonitor.self) private var powerMonitor
private var thermalStateLabel: String {
switch powerMonitor.thermalState {
case .nominal: "Nominal"
case .fair: "Fair"
case .serious: "Serious"
case .critical: "Critical"
@unknown default: "Unknown"
}
}
var body: some View {
VStack(spacing: 16) {
Text("Low Power Mode: \(powerMonitor.isLowPowerModeEnabled ? "Yes" : "No")")
Text("Thermal State: \(thermalStateLabel)")
#if os(iOS)
Text("Low Battery: \(powerMonitor.isLowBatteryState ? "Yes" : "No")")
#endif
if powerMonitor.shouldReducePerformance {
Text("Reducing Performance")
.foregroundStyle(.orange)
}
}
}
}Adjust update frequencies based on power state:
struct LiveDataView: View {
@Environment(\.isLowPowerModeEnabled) private var isLowPowerModeEnabled
@State private var data: String = "Loading..."
var refreshInterval: TimeInterval {
isLowPowerModeEnabled ? 30.0 : 5.0
}
var body: some View {
VStack {
Text(data)
Text("Refreshing every \(Int(refreshInterval))s")
.font(.caption)
}
.task {
await refreshData()
}
}
func refreshData() async {
while !Task.isCancelled {
data = "Updated at \(Date().formatted(date: .omitted, time: .standard))"
try? await Task.sleep(for: .seconds(refreshInterval))
}
}
}Disable symbol effects during Low Power Mode:
struct SymbolView: View {
@Environment(\.isLowPowerModeEnabled) private var isLowPowerModeEnabled
var body: some View {
Image(systemName: "bolt.fill")
.font(.system(size: 60))
.foregroundStyle(isLowPowerModeEnabled ? .yellow : .green)
.symbolEffect(.pulse, isActive: !isLowPowerModeEnabled)
}
}The PowerModeMonitor provides access to:
isLowPowerModeEnabled: Bool- Whether Low Power Mode is activethermalState: ProcessInfo.ThermalState- Current thermal state (nominal, fair, serious, critical)isLowBatteryState: Bool- Whether battery is below 20% (iOS only)shouldReducePerformance: Bool- Combined check for any power constraint
All monitor properties are available as SwiftUI environment values when using .powerKitEnvironment(_:):
| Environment Key Path | Type | Default |
|---|---|---|
\.isLowPowerModeEnabled |
Bool |
false |
\.thermalState |
ProcessInfo.ThermalState |
.nominal |
\.isLowBatteryState |
Bool |
false |
\.shouldReducePerformance |
Bool |
false |
These can also be set manually without a monitor, which is useful for SwiftUI previews and testing:
#Preview("Low Power Mode") {
MyView()
.environment(\.isLowPowerModeEnabled, true)
.environment(\.thermalState, .serious)
}The package monitors four thermal states:
.nominal- Normal operation.fair- Slight thermal pressure.serious- High thermal pressure, reduce performance.critical- Extreme thermal pressure, minimal operations
PowerKit is released under the MIT licence.