A lightweight menu bar countdown timer for macOS. It lives in the menu bar (with a Dock icon), no main window. Click the menu bar item or the Dock icon, pick a duration, and watch the countdown next to the clock. When time is up the title flashes FINISH in red/white and a sound loops for ~10 seconds.
Why "WorkFlow"? That's just the Xcode project / product name. The app itself is a menu bar timer.
Made with the support of TradeCloud. Without TradeCloud, this app would not exist.
- Menu bar + Dock - a menu bar item plus a Dock icon; click either one to open the timer (also appears in Cmd+Tab).
- Quick-start presets (5 / 10 / 25 / 45 min) and a custom H:M:S input.
- Pause / Resume / Reset from a click-through menu while running.
- FINISH alert - the title flashes red/white (~2 Hz) with a looping sound.
- Sleep-proof - the remaining time is computed from a wall-clock
Date()delta, so it stays correct across system sleep, not from countingTimerticks. - No width jitter - monospaced digits keep the item from resizing as the numbers change.
- Optional notification via
UNUserNotificationCenter.
| State | Menu bar shows | Click does |
|---|---|---|
| Idle | PRESS ME |
Opens the popover (presets + custom + START) |
| Running | MM:SS / HH:MM:SS |
Opens a menu: PAUSE, RESET, Quit |
| Paused | ` | |
| Finished | flashing FINISH + sound |
Any click stops the alert and returns to Idle |
After 10 seconds the Finished state returns to Idle automatically.
- macOS 13 (Ventura) or later
- Xcode 15+ (the project uses the Xcode 16+ synchronized-folder format)
- Open
WorkFlow.xcodeprojin Xcode. - Select the WorkFlow scheme and press ⌘R.
- Look at the right side of the menu bar, next to the clock - you'll see
PRESS ME.
There is no main window - that is intended. The app shows a Dock icon and a menu bar item; click either to open the timer. To quit, choose Quit from the menu bar item, the popover, or ⌘Q.
If you build from the command line instead, the sources compile cleanly under
swiftcwith-strict-concurrency=completeand even Swift 6 language mode.
- Start: click
PRESS ME→ tap a preset (starts instantly) or type H:M:S and press START. - While running: click the countdown → PAUSE / RESET.
- When finished: the title flashes
FINISHand a sound loops; click anywhere on the item to stop it early.
WorkFlow/
├── WorkFlowApp.swift # @main App entry, windowless Settings scene + app delegate adaptor
├── AppDelegate.swift # wiring + clean teardown on quit
├── TimerEngine.swift # state machine + Date-delta time logic
├── StatusBarController.swift # NSStatusItem: title rendering, clicks, popover, menus
├── TimerPopoverView.swift # SwiftUI popover: presets + H:M:S + START
├── AlertPlayer.swift # looping alarm sound (NSSound)
└── Notifier.swift # optional UserNotifications alert
Architecture: AppKit owns the status item (needed for attributedTitle with a
.backgroundColor to flash, and for raw click handling), while SwiftUI renders the
popover content via NSHostingController. TimerEngine is UI-agnostic and exposes
only its state and an onUpdate callback.
- Sleep resilience: the countdown and the 10s finish window are both measured
with
Date()deltas (endDate.timeIntervalSinceNow), so a missed tick during sleep self-corrects on the next tick. .commonrun loop mode: timers are added withRunLoop.main.add(timer, forMode: .common)so the countdown keeps updating while a status bar menu is open.- Flashing: the finished title is redrawn from a 0.25s timer, alternating
NSAttributedString.backgroundColorbetween red and white (~2 Hz). - Concurrency: classes are
@MainActorand timers use the target/selector API, so the code is clean under strict concurrency / Swift 6 mode.
The item is created correctly - it is just hidden. On a MacBook with a notch, and/or with a menu bar manager such as Hidden Bar / Bartender / Ice, a newly added status item can land in the hidden/overflow zone behind the notch.
Fixes:
- Expand the hidden section (click the Hidden Bar / Bartender chevron).
- Hold ⌘ (Command) and drag menu bar items to reorder - pull
PRESS MEout from behind the notch into the visible area. - Temporarily quit other menu bar apps to free up space, or switch focus to an app with a short menu (e.g. Finder) so the bar is less crowded.
The app runs sandboxed. System sounds work, but if NSSound("Funk") is unavailable
it falls back to NSSound.beep(). Make sure the system volume isn't muted.
This app was built with the support of TradeCloud. Without TradeCloud, this project would not exist. Thank you.
MIT © 2026 danila
Лёгкий таймер обратного отсчёта в строке меню для macOS. Живёт в строке меню (с иконкой в Dock), без главного окна. Кликаешь по пункту или иконке в Dock, выбираешь длительность и видишь отсчёт рядом с часами. Когда время выходит - заголовок мигает FINISH красным/белым, и ~10 секунд звучит зацикленный сигнал.
Название «WorkFlow» - это просто имя Xcode-проекта. Сама программа - таймер в меню-баре.
Сделано при поддержке TradeCloud. Если бы не TradeCloud, этой программы бы не было.
- Меню-бар + Dock - пункт в строке меню и иконка в Dock; клик по любому открывает таймер (есть и в Cmd+Tab).
- Пресеты (5 / 10 / 25 / 45 мин) и ручной ввод Ч:М:С.
- Pause / Resume / Reset через меню во время работы.
- Оповещение FINISH - заголовок мигает красным/белым (~2 Гц) + зацикленный звук.
- Устойчивость к сну - остаток считается по дельте
Date(), а не по тикамTimer. - Ширина не прыгает - моноширинные цифры.
- Опциональное уведомление через
UNUserNotificationCenter.
| Состояние | В меню-баре | Клик делает |
|---|---|---|
| Idle | PRESS ME |
Открывает popover (пресеты + ввод + START) |
| Running | MM:SS / HH:MM:SS |
Меню: PAUSE, RESET, Quit |
| Paused | ` | |
| Finished | мигающий FINISH + звук |
Любой клик гасит оповещение и возвращает в Idle |
Через 10 секунд состояние Finished само возвращается в Idle.
- macOS 13 (Ventura) или новее
- Xcode 15+ (проект в формате синхронизированных папок Xcode 16+)
- Открой
WorkFlow.xcodeprojв Xcode. - Выбери схему WorkFlow и нажми ⌘R.
- Смотри в правую часть строки меню, рядом с часами - там будет
PRESS ME.
Главного окна не будет - это правильно. Есть иконка в Dock и пункт в строке меню; клик по любому открывает таймер. Выход - Quit из меню-бара, из попапа или ⌘Q.
WorkFlow/
├── WorkFlowApp.swift # точка входа @main, безоконная Settings-сцена + делегат
├── AppDelegate.swift # связывание компонентов + корректная остановка при выходе
├── TimerEngine.swift # конечный автомат + логика времени по дельте Date
├── StatusBarController.swift # NSStatusItem: отрисовка, клики, popover, меню
├── TimerPopoverView.swift # SwiftUI popover: пресеты + Ч:М:С + START
├── AlertPlayer.swift # зацикленный звук (NSSound)
└── Notifier.swift # опциональное уведомление
Архитектура: статус-баром владеет AppKit (нужен доступ к attributedTitle с
.backgroundColor для мигания и к «сырым» кликам), а контент popover рисует SwiftUI
через NSHostingController. TimerEngine не знает про UI - отдаёт только state
и коллбэк onUpdate.
- Устойчивость к сну: отсчёт и 10-секундное окно финиша считаются по дельте
Date()- пропущенный во сне тик самокорректируется на следующем. - Режим
.common: таймеры добавляются какRunLoop.main.add(timer, forMode: .common), поэтому отсчёт обновляется даже при открытом меню. - Мигание: заголовок финиша перерисовывается по таймеру 0.25с, чередуя
.backgroundColorкрасный/белый (~2 Гц). - Конкурентность: классы помечены
@MainActor, таймеры на target/selector - код чист в строгой конкурентности и в режиме Swift 6.
Пункт создаётся корректно - он просто скрыт. На MacBook с чёлкой и/или при менеджере меню-бара (Hidden Bar / Bartender / Ice) новый статус-item попадает в скрытую зону за вырезом.
Что делать:
- Разверни скрытую секцию (стрелка Hidden Bar / Bartender).
- Зажми ⌘ и перетащи иконки меню-бара - вытащи
PRESS MEиз-за чёлки в видимую зону. - Временно закрой другие менюбар-приложения или переключись на приложение с коротким меню (например, Finder), чтобы бар был свободнее.
Приложение работает в песочнице. Системные звуки доступны; если NSSound("Funk")
недоступен, используется NSSound.beep(). Проверь, что громкость не в mute.
Программа сделана при поддержке TradeCloud. Если бы не TradeCloud, этого проекта бы не было. Спасибо.
MIT © 2026 danila