Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DynamicNotch

Build polished, notch-aware surfaces for macOS apps with SwiftUI.

DynamicNotch gives you a reusable SwiftUI surface and an AppKit-backed window controller for edge-attached experiences: recording indicators, media controls, build progress, approvals, notifications, and Dynamic Island-style compact states.

macOS 13+ Swift 5.9+ SwiftUI and AppKit

Why DynamicNotch?

Notch-based UI looks simple until window coordinates, rounded shoulders, clipping, hit testing, safe areas, and physical camera housings all meet in one component.

DynamicNotch keeps those responsibilities in the right places:

  • SwiftUI owns your content.
  • AppKit owns the floating window and screen geometry.
  • The package clips rendering and hit testing to the same notch silhouette.
  • Top and bottom windows account for the selected display's safe area.
  • Compact mode reserves the physical camera housing while keeping the background continuous.

Requirements

  • macOS 13 or later
  • Swift 5.9 or later
  • A SwiftUI macOS app with AppKit access

Installation

Xcode

  1. Open your app project in Xcode.
  2. Select File → Add Package Dependencies….
  3. Paste the GitHub repository URL for DynamicNotch.
  4. Choose a version or branch.
  5. Add the DynamicNotch product to your macOS target.

Then import it:

import DynamicNotch

Swift Package Manager

Add the repository as a dependency in your Package.swift:

dependencies: [
    .package(
        url: "https://github.com/govijr/DynamicNotch-MacOS.git",
        from: "0.1.0"
    )
]

Add the product to the target that uses it:

targets: [
    .executableTarget(
        name: "YourMacApp",
        dependencies: [
            .product(name: "DynamicNotch", package: "DynamicNotch")
        ]
    )
]

The package is published at github.com/govijr/DynamicNotch-MacOS.

Two ways to use it

1. Embed the SwiftUI surface

Use DynamicNotch when your app already owns the containing window:

import SwiftUI
import DynamicNotch

struct RecordingSurface: View {
    var body: some View {
        DynamicNotch(
            direction: .top,
            width: 420,
            height: 72,
            background: .black,
            contentInsets: EdgeInsets(
                top: 0,
                leading: 18,
                bottom: 0,
                trailing: 18
            )
        ) {
            HStack(spacing: 10) {
                Circle()
                    .fill(.green)
                    .frame(width: 8, height: 8)

                Text("Recording")
                    .foregroundStyle(.white)

                Spacer()

                Text("00:42")
                    .foregroundStyle(.white.opacity(0.7))
                    .monospacedDigit()
            }
        }
    }
}

Supported directions are .top, .bottom, .left, and .right.

2. Present an edge window

Use DynamicNotchWindowController when the surface should float above the desktop:

import AppKit
import DynamicNotch
import SwiftUI

@MainActor
final class NotchPresenter {
    private let controller = DynamicNotchWindowController()

    func showBuildStatus() {
        controller.show(
            placement: .edge(.top, alignment: .center),
            width: 420,
            height: 72
        ) {
            HStack(spacing: 10) {
                Circle()
                    .fill(.green)
                    .frame(width: 8, height: 8)

                Text("Build finished")
                    .foregroundStyle(.white)

                Spacer()

                Image(systemName: "checkmark")
                    .foregroundStyle(.green)
            }
            .padding(.horizontal, 20)
        }
    }

    func hide() {
        controller.hide()
    }
}

Keep one controller alive for the lifetime of the feature. Calling show again updates the content and animates the window to its new geometry.

Placement

DynamicNotchPlacement controls the edge, alignment, and offset:

.edge(.top, alignment: .center)
.edge(.bottom, alignment: .end, offset: -24)
.edge(.left, alignment: .start, offset: 16)
.edge(.right, alignment: .end)

start, center, and end are relative to the attached edge. Positive offsets move toward the edge's end: rightward on horizontal edges and downward on vertical edges. Placement is clamped to the selected screen.

Pass screen: when targeting a particular display in a multi-monitor setup. Otherwise DynamicNotch uses the main screen.

Safe areas and physical Mac notches

The window controller automatically uses the selected screen's safe-area inset for top and bottom surfaces. This keeps expanded content below a physical camera housing and keeps bottom content away from the exposed edge.

controller.show(
    placement: .edge(.top),
    width: 420,
    height: 72
) {
    Text("Camera-safe content")
        .foregroundStyle(.white)
}

When using DynamicNotch directly, pass the inset yourself because a standalone SwiftUI view cannot infer its containing screen:

let safeTopInset = NSScreen.main?.safeAreaInsets.top ?? 3

DynamicNotch(
    direction: .top,
    width: 420,
    height: 72,
    attachedEdgeRail: safeTopInset
) {
    Text("Camera-safe content")
}

Set attachedEdgeRail: 0 when you explicitly want to opt out of the automatic controller behavior.

Compact mode

Compact mode is designed for the small, always-present state of a top notch. It provides independent leading and trailing regions and automatically measures the physical camera gap when the display exposes it.

The background remains one continuous surface; the center gap affects content layout only, so the physical notch never reveals a transparent hole.

controller.showCompact(width: 400) {
    HStack(spacing: 6) {
        Circle()
            .fill(.green)
            .frame(width: 7, height: 7)

        Text("Live")
    }
    .foregroundStyle(.white)
} trailing: {
    HStack(spacing: 8) {
        Image(systemName: "waveform")
        Text("00:42")
            .monospacedDigit()
    }
    .foregroundStyle(.white.opacity(0.75))
}

The compact height defaults to the larger of the status-bar thickness and the display's top safe area. Override height or centerGap when you are building a fixed hardware-independent layout.

Move between modes using the same controller:

controller.showCompact(width: 400) {
    CompactLeadingView()
} trailing: {
    CompactTrailingView()
}

controller.show(
    placement: .edge(.top),
    width: 520,
    height: 180
) {
    ExpandedView()
}

The controller exposes presentationMode as .compact or .expanded.

Geometry customization

All surfaces support a rounded exposed edge and independent shoulder curvature:

DynamicNotch(
    direction: .top,
    width: 520,
    height: 180,
    cornerRadius: 25,
    shoulderRadius: 30,
    background: .black
) {
    Text("Expanded content")
        .foregroundStyle(.white)
}

Use cornerRadius for the exposed corners and shoulderRadius for the inward transitions from the attached lip.

Demo app

The package includes a small demo with:

  • top compact mode
  • top expanded mode
  • bottom playback mode
  • left build progress
  • right approval action

Run it from the package directory:

swift run DynamicNotchDemo

To build a normal Finder-style app bundle:

zsh Scripts/build-demo-app.sh
open dist/DynamicNotchDemo.app

Scope

DynamicNotch provides the geometry, clipping, safe-area handling, placement, and presentation primitives. The consuming app owns product behavior such as:

  • hover expansion
  • drag gestures
  • notifications and timers
  • persistence
  • menu-bar integration
  • application-specific transitions and state

Keeping those decisions in the consuming app keeps the package small and makes it work for more kinds of macOS apps.

Project status

DynamicNotch is currently intended for early adopters and production prototypes. The core API is stable enough to integrate, while the package continues to evolve toward a 1.0 release with broader integration and visual test coverage.

License

DynamicNotch is available under the MIT License.

Copyright (c) 2026 Govi

See LICENSE for the full license text.

About

A SwifUI package to create dynamic notch interfaces easily in all 4 directions.

Topics

Resources

Stars

15 stars

Watchers

0 watching

Forks

Contributors

Languages