SwiftUI와 글래스모피즘으로 macOS 네이티브 메뉴바 위젯 만들기

발행: (2026년 6월 6일 AM 10:52 GMT+9)
8 분 소요
원문: Dev.to

출처: Dev.to

fanioz

최근에 SysMonitor를 오픈소스로 공개했습니다. 이 앱은 CPU, RAM, 디스크 I/O, 네트워크 속도 등 시스템 리소스를 추적하는 macOS 네이티브 메뉴 바 앱입니다.

두 가지 극단적인 macOS 모니터링 도구에 지쳤기 때문에 만들었습니다. 메뉴 바에 아주 작은 텍스트만 표시하고 자세한 정보를 전혀 제공하지 않는 도구와, 비행기 조종석처럼 보이는 거대한 대시보드 사이에서 말이죠. 저는 필요할 때만 내려오는 멋지고 투명한 위젯을 가진 깔끔한 메뉴 바 표시를 원했습니다.

아래는 100% Swift와 SwiftUI만을 사용해 어떻게 구현했는지, 그리고 진정한 네이티브 느낌을 주기 위해 사용한 트릭들을 정리한 내용입니다.


NSPopover 대신 커스텀 Glassmorphism 사용하기

메뉴 바 앱을 만들 때 Apple이 권장하는 드롭다운 표시 방법은 NSPopover를 이용하는 것입니다. 위치와 메뉴 바 아이콘을 가리키는 작은 화살표를 자동으로 처리해 줍니다.

하지만 NSPopover는 시각적인 제약이 많습니다. 콘텐츠를 테두리 안에 감싸고, 가장자리까지 투명하게 만들거나 커스텀 glassmorphism을 적용하기가 어렵습니다.

그래서 저는 NSVisualEffectView를 백업으로 하는 테두리 없는 NSWindow를 직접 만들기로 했습니다:

// Create a borderless, transparent window
window.titlebarAppearsTransparent = true
window.titleVisibility = .hidden
window.isOpaque = false
window.backgroundColor = .clear

// Add the glassmorphism backdrop
let visualEffect = NSVisualEffectView()
visualEffect.blendingMode = .behindWindow
visualEffect.material = .hudWindow
visualEffect.state = .active
visualEffect.autoresizingMask = [.width, .height]

이렇게 하면 데스크톱 위에 떠 있는 서리 낀 유리 패널 같은 정확한 시각 스타일을 얻을 수 있었지만, 화면에 윈도우를 정확히 어디에 표시할지 직접 계산해야 했습니다.


메뉴 바 위치 계산의 원리

NSPopover를 사용하지 않았기 때문에 위젯 윈도우는 메뉴 바 아이콘이 어디에 있는지 전혀 알 수 없었습니다. 드롭다운처럼 보이게 하려면 NSStatusItem 버튼의 좌표를 잡아 그에 맞춰 윈도우 위치를 지정해야 했습니다.

AppDelegate 안의 로직은 다음과 같습니다:

if let button = statusItem?.button, let _ = button.window?.screen {
    // 1. Get the button's bounds and convert to global screen coordinates
    let buttonFrameInWindow = button.convert(button.bounds, to: nil)
    let buttonFrameInScreen = button.window!.convertToScreen(buttonFrameInWindow)

    let windowWidth = window.frame.width
    let windowHeight = window.frame.height

    // 2. Center the window horizontally under the button
    let xPos = buttonFrameInScreen.midX - (windowWidth / 2)

    // 3. Place it just below the menu bar
    let yPos = buttonFrameInScreen.minY - windowHeight - 5

    // 4. Update the frame
    window.setFrame(NSRect(x: xPos, y: yPos, width: windowWidth, height: windowHeight), display: true)
}

이 코드는 아이콘이 노치가 있든, 외부 모니터를 사용하든, 메뉴 바에서 아이콘을 어디로 옮기든 윈도우가 아이콘 바로 아래에 완벽히 맞춰지도록 보장합니다.


스마트 배터리 관리 및 자동 숨김

시스템 모니터의 가장 큰 문제 중 하나는 sysctl을 폴링하고 CPU 틱을 백그라운드에서 계산하는 과정이 실제로 많은 CPU를 소모한다는 점입니다. 결국 배터리 소모가 심해져서 “배터리가 소모되고 있다”는 메시지만 알려주는 상황이 됩니다.

이를 해결하기 위해 SysMonitor는 자체적으로 스로틀링(throttling)합니다. 유리 위젯이 보일 때는 부드러운 UI를 위해 2초마다 폴링하고, 사용자가 다른 곳을 클릭하면 windowDidResignKey를 감지해 빠른 페이드아웃 애니메이션으로 자동 숨김하고 폴링 간격을 5초로 늘립니다.

extension AppDelegate: NSWindowDelegate {
    // Hide the widget window automatically when the user clicks elsewhere
    func windowDidResignKey(_ notification: Notification) {
        if let window = widgetWindow, window.isVisible {
            hideWidgetWindow() // Fades out and drops polling rate to 5s
        }
    }
}

최종 결과

결과는 제가 원했던 그대로입니다. 필요한 정보를 빠르게 알려주고, 그 뒤엔 조용히 사라지는 빠르고 네이티브한 도구가 완성되었습니다.

전체 소스 코드를 확인하고(빌드도) GitHub에서 받아볼 수 있습니다:

SysMonitor (sysmonitor)

SysMonitor 스크린샷

macOS 메뉴 바와 통합된 네이티브 시스템 리소스 위젯이며, 아름다운 glassmorphic UI를 제공합니다.

SysMonitor는 중요한 macOS 시스템 메트릭을 우아하고 눈에 거슬리지 않게 추적할 수 있게 해 줍니다. 지속적인 메뉴 바 아이템을 통해 CPU와 RAM 사용량을 한눈에 확인하고, 상세한 코어별 통계, 디스크 I/O, 네트워크 처리량, 메모리 구성을 보여주는 투명 위젯 윈도우를 제공합니다.

기능

  • 메뉴 바 통합: macOS 메뉴 바에 실시간으로 표시되는 간결한 시스템 통계.
  • Glassmorphic 위젯: 메뉴 바에서 바로 내려오는 아름답게 디자인된 투명 윈도우.
  • 낮은 오버헤드: 숨겨졌을 때 폴링 간격을 자동으로 조절해 CPU 사용량 최소화.
  • 스마트 알림: 메트릭이 안전 임계값을 초과하면 macOS 네이티브 알림 전송.

설치

소스에서 SysMonitor를 설치하고 빌드하려면 Mac에 Xcode가 설치되어 있어야 합니다( macOS 13 이상).

git clone https://github.com/fanioz/sysmonitor.git
cd sysmonitor
xcodebuild -scheme sysmonitor -configuration Release build
open build/Release/sysmonitor.app

사용법

앱을 실행하면 SysMonitor가…

토론 질문: macOS 혹은 Windows용 백그라운드 유틸리티를 만들 때, 배터리 수명 제약과 폴링 주기를 어떻게 균형 있게 맞추시나요? 고정된 간격을 유지하시나요, 아니면 앱 가시성에 따라 동적으로 스로틀링하시나요? 아래에 의견을 남겨 주세요!

0 조회
Back to Blog

관련 글

더 보기 »

모바일 한여름 열풍

!Cover image for Mobile Midsommer Madnesshttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploa...