Interactive Learning Guide

Learning Swift
through Flowbar

A hands-on guide to understanding Swift, SwiftUI, and macOS development by reading a real codebase — a menu bar app for Obsidian notes.

Explore the Code See the Architecture

Getting Started

If you've never touched Swift or Xcode before, start here. We'll get you from zero to "I understand what's going on" in a few minutes.

What to Install

1 Grab Xcode from the Mac App Store (it's free). This one download gives you everything — the Swift compiler, SwiftUI, the macOS SDK, a simulator, and a debugger.

2 Open Xcode once, go to Settings → Locations and make sure Command Line Tools is set. That's the only config you need.

Download Xcode →  |  Swift.org Getting Started →

From Empty Folder to Running App

1 Open Xcode → File → New → Project

2 Pick macOS → App. Choose "SwiftUI" for Interface and "Swift" for Language.

3 Give it a name, pick a folder. Xcode sets up everything — a .xcodeproj, a starter SwiftUI view, the whole structure.

Now hit ⌘R. Xcode compiles your code, signs the app, and launches it. You've got a running macOS app. The whole thing takes under a minute.

Apple's SwiftUI Tutorial →

What is AppDelegate?

When your Mac launches an app, the OS needs someone to talk to. "Hey, you're launching now." "Hey, the user just came back from sleep." That someone is the AppDelegate — it's your app's point of contact with macOS.

macOS AppDelegate your setup code

The big one is applicationDidFinishLaunching() — it fires once at startup. Flowbar uses it to create the state, timer service, and that menu bar icon you click. Think of it as your app's main().

SwiftUI App vs AppDelegate

Here's the thing — modern SwiftUI apps use @main struct MyApp: App as their entry point. It's clean and simple. But menu bar apps need AppKit stuff (the status bar icon, popovers, global hotkeys) that SwiftUI just can't do yet.

So Flowbar uses both. The @main struct declares the app, and @NSApplicationDelegateAdaptor plugs in an AppDelegate that handles the AppKit side. You'll see this pattern in most macOS apps that need low-level system access.

What's in a Swift Project?

.xcodeproj — Xcode's project file. Build settings, targets, which files belong where. You rarely edit this by hand.

.swift files — your actual code. No header files, no makefiles, no boilerplate. Just Swift.

Info.plist — metadata like app name, version, and permissions. Xcode handles most of it for you.

Assets.xcassets — images, app icons, colors. You drag stuff in through Xcode's visual editor.

One nice thing: there's no main.swift when you use @main. The attribute tells the compiler where to start. Less ceremony.

Architecture Map

Hover any node to see what it does and highlight its connections. Click to jump to the code.

Why it's built this way

AppDelegate, not pure SwiftUI — SwiftUI is great for UI, but it can't put an icon in your menu bar, show a popover from it, or listen for global hotkeys. For that you need AppKit, and AppKit talks through the AppDelegate.

SQLite, not Core Data or flat files — Timer sessions need real queries — "give me the total time per todo" is a GROUP BY + SUM. Core Data is way too heavy for one table. A JSON file can't do that. SQLite ships with every Mac, needs no setup, and the whole thing is about 10 lines of C API calls.

@Observable singletons — AppState and TimerService use Swift's @Observable macro and are injected via .environment(). Any view that needs them just asks with @Environment. SwiftUI tracks property access at the call site — only views that actually read a property re-render when it changes. No @Published needed.

GCD file watchers, not polling — Instead of checking the filesystem on a timer, Flowbar asks the OS to notify it the instant a file changes. Your CPU does nothing until something actually happens. That's DispatchSource.

creates creates creates manages manages owns owns uses reads used by AppDelegate AppState @Observable TimerService @Observable PopoverManager NSObject, @Observable NoteFile[] FileWatcher DatabaseService NSPopover FloatingPanel MainView SwiftUI View SidebarView NoteContentView SettingsView TimerContainerView SwiftUI View TimerHomeView TimerTodosView TodoRow MarkdownParser
App Core Models / Data Services Window Management Views

File Explorer

Browse every file in the codebase with syntax highlighting and inline annotations. Click the numbered markers to learn Swift concepts.

Select a file to begin

    ← Choose a file from the sidebar
      

Swift Concepts Reference

A cheat-sheet of Swift and SwiftUI concepts, each with a real example from the Flowbar codebase.

Data Flow Diagrams

Follow the data through two key user interactions.

How a Note Gets Loaded

User clicks a file in SidebarView
The tap triggers a call to appState.selectFile(file).
AppState.selectFile()
Sets activePanel = .file(file), then calls loadFileContent(file).
loadFileContent()
Reads the .md file from disk via String(contentsOf:).
editorContent updates
The @Observable macro detects the property change. SwiftUI re-renders only views that read editorContent.
NoteContentView re-renders
The TextEditor bound to editorContent shows the new note.

How the Timer Works

User clicks play on a TodoRow
Calls timerService.start(todoText:, sourceFile:).
TimerService.start()
Inserts a new session into SQLite via DatabaseService.startSession().
Timer ticks every second
A Timer.scheduledTimer fires, updating elapsed each tick.
elapsed property updates
The @Observable macro tracks access — only views reading timerService.elapsed re-render.
User clicks complete
timerService.complete() ends the DB session, returns the todo info.
MarkdownParser marks done
MarkdownParser.markTodoDone() toggles - [ ] to - [x] in the file.