Mac-TreeSpace

Swift Actively developed pushed 3 days ago
Field notes

Why this exists

A minimal native SwiftUI app that answers one question: what is taking up the space? Expandable size-sorted tree, totals grouped by file extension, and a duplicate finder — all readable end to end and yours to hack on.

Duplicates, cheapest test first

Hashing every file to find duplicates is correct and slow. The staged approach spends almost no I/O before committing to a lot:

  1. Group by size. Different byte counts cannot be identical. Costs a directory walk, no file reads.
  2. Hash the first 64 KB. Files that differ early drop out, having cost 64 KB each.
  3. Full SHA-256. Only the survivors get read end to end.

Cost now scales with how many near-duplicates you have, not how many files. Both stages use CryptoKit, so there is no C interop and no extra dependency.

Measuring honestly

Sizes use allocated bytes — the same figure Finder shows as “Size on disk” — not logical length. A folder of 10,000 tiny files can have a trivial logical total and eat gigabytes of real space.

Two rules that prevent the classic bugs:

  • Symlinks are skipped, so a recursive walk can never loop.
  • Bundles count as single items. An .app is technically a directory; nobody wants its innards in a size tree.

The scanner walks once and accumulates on the way back up, so every file is stat’ed exactly once.

Permission reality

Scanning Desktop, Documents, Downloads, or ~/Library triggers a macOS prompt the first time. Reading ~/Library or the whole home folder needs Full Disk Access in System Settings → Privacy & Security.

No API grants that, and no app can ask on your behalf. All it can do is fail clearly and point at the right settings pane.

What it deliberately is not

Not a treemap, not a beautification project. Three views, a per-row bar showing each item’s share of the root, right-click to reveal in Finder or move to trash. The bar is the quietly important part — it turns “what’s eating my disk” into a visual scan rather than mental arithmetic.

From the repository

README

source ↗

TreeSpace

Buy Me a Coffee

A native SwiftUI Mac app that shows what’s eating your disk — like TreeSize for Windows or Disk Inventory X, but minimal and yours to hack on.

Features

  • Size-sorted tree: expandable folder view with per-row bars showing each item’s share of the root.
  • By Extension: totals grouped by file type — tap a row to list every matching file sorted by size.
  • Find Duplicates: groups files by size, then screens with a 64 KB head-hash, then confirms with full SHA-256. Shows wasted space per group.
  • Reveal in Finder / Move to Trash: right-click any row, or use the buttons in the duplicates view.

Files are measured using their allocated size on disk (same number Finder’s Get Info shows for “Size on disk”). Symlinks are skipped to avoid cycles. Bundles like .app are treated as single items.

Running it

You need Xcode (or at least the command-line developer tools) on a Mac running macOS 13 or later.

Option A — Open in Xcode

  1. Double-click Package.swift, or in Xcode: File → Open… and select Package.swift.
  2. Hit the run button (⌘R).

Option B — Command line

cd TreeSpace
swift run

The first build takes a minute while SwiftPM pulls toolchains.

First-time permission prompt

macOS will prompt the app for access the first time you pick a folder that macOS considers sensitive (Desktop, Documents, Downloads). Click Allow. If you want to scan ~/Library or the whole home folder, you’ll need to grant the app Full Disk Access in System Settings → Privacy & Security → Full Disk Access — add the built binary (or Xcode itself, if running from there).

How it works

  • Scanner.swift — single-pass recursive directory walk using FileManager.contentsOfDirectory with resource keys. Accumulates sizes on the way back up.
  • DuplicateFinder.swift — three-stage filter: same-size → same-first-64KB-hash → same-SHA256. Uses CryptoKit.
  • FileNode.swift — reference-type tree so SwiftUI doesn’t copy it. OutlineGroup uses sortedChildren for expansion.
  • AppState.swift — scan/dup work runs on a background DispatchQueue, posts throttled progress back to the main actor.

Tweaking it

  • Minimum duplicate size: DuplicateFinder.find(..., minSize:) defaults to 64 KB. Lower it if you want to catch smaller junk.
  • Hidden files: toggle .skipsHiddenFiles in Scanner.swift.
  • Bundle handling: remove the isPackage check in Scanner.swift if you want to drill into .app contents.
  • Color thresholds: NodeRow.barColor controls when bars turn orange/red.

File layout

TreeSpace/
  Package.swift
  README.md
  Sources/TreeSpace/
    TreeSpaceApp.swift       # @main entry + menu commands
    AppState.swift           # ObservableObject, scan/dup orchestration
    FileNode.swift           # tree model
    Scanner.swift            # recursive sizer
    DuplicateFinder.swift    # SHA-256 duplicate detection
    ExtensionStats.swift     # group-by-extension aggregator
    ContentView.swift        # split view + toolbar + sidebar
    TreeView.swift           # outline tree + row + file icon
    ExtensionsView.swift     # extension totals + file list
    DuplicatesView.swift     # duplicate groups

Support TreeSpace

If this project is useful to you, you can support its maintenance with a coffee:

Buy Me a Coffee

esc
navigate open esc close