Skip to content

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.

License

Notifications You must be signed in to change notification settings

markbattistella/PowerKit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

PowerKit

Swift Versions

Platforms

Licence

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 PowerModeMonitor for 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.

Installation

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.

Usage

Basic Setup

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)

Check Power State

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")
            }
        }
    }
}

Adaptive Animations

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
    )

Adaptive Visual Effects

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)
            }
        )
    }
}

Power Mode Indicator

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
)

Advanced Power Monitoring

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)
            }
        }
    }
}

Adaptive Refresh Rates

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))
        }
    }
}

Conditional Symbol Effects

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)
    }
}

Power State Properties

PowerModeMonitor

The PowerModeMonitor provides access to:

  • isLowPowerModeEnabled: Bool - Whether Low Power Mode is active
  • thermalState: 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

Environment Keys

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)
}

Thermal States

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

Licence

PowerKit is released under the MIT licence.

About

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.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Languages