🎯 Motivasi
Saya cari fitur untuk membatasi pengisian baterai di MacBook ke 80% biar awet...
dan ketemulah AIDente Pro — fitur bagus tapi yaa... IDR 200K+
😅
Karena gak ada opsi bawaan macOS, akhirnya bikin:
RyBatteryNotifier – macOS menu bar app untuk pantau status baterai, kasih notifikasi saat sudah penuh.
🛠️ Tools yang Digunakan
-
Swift
+SwiftUI
-
IOKit.ps
– untuk baca status baterai -
UserNotifications
– buat kirim notifikasi -
NSStatusBar
– tampil di menu bar - ✨ Optional:
LaunchAgent
untuk auto-start
Fitur Utama
✅ Tampilkan status baterai real-time
✅ Menu bar dengan ikon baterai
✅ Bisa atur limit pengisian (misal 80%)
✅ Notifikasi otomatis saat limit tercapai
✅ Mode Auto Discharge
(simulasi nonaktif charging) masih on progress
✅ Tombol menu untuk "Show App", "Discharge", dan "Quit" di menu bar.
Membuat Proyek Xcode
- Buka Xcode →
New Project
→App
- Pilih SwiftUI dan platform macOS
- Nama project:
RyBatteryNotifier
🧠 Cek Status Baterai via IOKit
Buat file BatteryService.swift
:
import IOKit.ps
import UserNotifications
import Foundation
class BatteryService: ObservableObject {
@Published var batteryPercentage: Int = 0
@Published var isCharging: Bool = false
@Published var isInDischargeMode: Bool = false
@Published var autoDischargeEnabled: Bool = UserDefaults.standard.bool(forKey: "autoDischargeEnabled")
init() {
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { _ in
self.updateBatteryInfo()
}
updateBatteryInfo()
}
func updateBatteryInfo() {
guard let snapshot = IOPSCopyPowerSourcesInfo()?.takeRetainedValue(),
let sources = IOPSCopyPowerSourcesList(snapshot)?.takeRetainedValue() as? [CFTypeRef] else {
return
}
for ps in sources {
if let info = IOPSGetPowerSourceDescription(snapshot, ps)?.takeUnretainedValue() as? [String: Any],
let isCharging = info[kIOPSIsChargingKey as String] as? Bool,
let current = info[kIOPSCurrentCapacityKey as String] as? Int,
let max = info[kIOPSMaxCapacityKey as String] as? Int {
let percent = Int((Double(current) / Double(max)) * 100)
DispatchQueue.main.async {
self.batteryPercentage = percent
self.isCharging = isCharging
}
let limit = UserDefaults.standard.integer(forKey: "maxLimit")
if autoDischargeEnabled && isCharging && percent >= limit {
DispatchQueue.main.async {
self.isInDischargeMode = true
}
self.sendDischargePrompt()
return
}
if isCharging && percent >= limit {
self.sendNotification()
}
}
}
}
func sendNotification() {
let content = UNMutableNotificationContent()
content.title = "⚡ Baterai Penuh"
content.body = "Baterai sudah mencapai limit. Cabut charger ya~"
content.sound = .default
UNUserNotificationCenter.current().add(UNNotificationRequest(
identifier: UUID().uuidString,
content: content,
trigger: nil
))
}
func sendDischargePrompt() {
let content = UNMutableNotificationContent()
content.title = "🔌 Mode Discharge Aktif"
content.body = "Auto Discharge aktif. Cabut charger manual ya~"
content.sound = .default
UNUserNotificationCenter.current().add(UNNotificationRequest(
identifier: UUID().uuidString,
content: content,
trigger: nil
))
}
}
Menu Bar App (Agent Style)
Tambahkan StatusBarController.swift
untuk icon di taskbar:
import AppKit
class StatusBarController {
private var statusItem: NSStatusItem!
init(showApp: @escaping () -> Void, discharge: @escaping () -> Void) {
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
statusItem.button?.image = NSImage(systemSymbolName: "battery.100", accessibilityDescription: "RyBatteryNotifier")
let menu = NSMenu()
let dischargeItem = NSMenuItem(title: "Discharge Mode", action: #selector(doDischarge), keyEquivalent: "d")
dischargeItem.target = self
let showAppItem = NSMenuItem(title: "Show App", action: #selector(doShowApp), keyEquivalent: "s")
showAppItem.target = self
let quitItem = NSMenuItem(title: "Quit RyBatteryNotifier", action: #selector(doQuit), keyEquivalent: "q")
quitItem.target = self
menu.addItem(dischargeItem)
menu.addItem(showAppItem)
menu.addItem(.separator())
menu.addItem(quitItem)
statusItem.menu = menu
self.onShowApp = showApp
self.onDischarge = discharge
}
private var onShowApp: (() -> Void)?
private var onDischarge: (() -> Void)?
@objc func doShowApp() { onShowApp?() }
@objc func doDischarge() { onDischarge?() }
@objc func doQuit() { NSApp.terminate(nil) }
}
Info.plist Tweaks
Tambahkan ke Info.plist
agar app jadi menu bar agent (tanpa dock icon):
LSUIElement
LSApplicationCategoryType
public.app-category.utilities
Install Manual (Karena Gratis Account)
Xcode personal account tidak bisa Archive-distribute, jadi:
cd ~/Library/Developer/Xcode/DerivedData
open .
Masuk ke folder project → Build/Products/Debug/
Temukan RyBatteryNotifier.app
dan copy ke /Applications/:
cp -R RyBatteryNotifier.app /Applications/
Jalankan:
open /Applications/RyBatteryNotifier.app
🔁 Auto Start Saat Login (Optional)
- Buat file
~/Library/LaunchAgents/com.ryfazrin.battery.plist
version="1.0">
Label
com.ryfazrin.battery
ProgramArguments
/Applications/RyBatteryNotifier.app/Contents/MacOS/RyBatteryNotifier
RunAtLoad
- Jalankan:
launchctl load ~/Library/LaunchAgents/com.ryfazrin.battery.plist
Penutup
Kini saya punya macOS app buatan sendiri di menu bar dan bisa bantu rawat baterai MacBook.
💸 Gratis, open-source, dan fun to build.
✨ Gak perlu AIDente Pro, cukup semangat dan Xcode 😄
Source code?
Repo GitHub ada di komentar~ 🧪