mcpField
M
macos-notch-ui

macos-notch-ui

github.com/fayazaraデザイン・UI685571 回インストール22 週間前に更新
リポジトリ

macos-notch-ui について

macos-notch-uiは、MacBookのノッチに沿うDynamic Island風オーバーレイを作る開発スキルです。SwiftUIのシェイプとNSPanelを使う方法に加え、配置、アニメーション、ノッチのない画面での代替表示を説明します。

主な機能

  1. 01SwiftUIによるノッチ形状
  2. 02NSPanelによる透明オーバーレイ
  3. 03画面上端とノッチへの位置合わせ
  4. 04形状サイズと表示状態のアニメーション
  5. 05ノッチのない画面を検出して代替表示

活用シーン

  1. 01ノッチ領域へのステータス表示
  2. 02録音インジケーターや短いテキストの表示
  3. 03オーバーレイの展開・収縮アニメーション
  4. 04外部ディスプレイでの代替表示

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/fayazara/macos-app-skills --skill macos-notch-ui

インストール後、Claude Code で/macos-notch-uiを入力して読み込まれたか確認します。

同梱ファイルreferences/NotchShape.swiftreferences/NotchWindow.swift

詳細

macOS ノッチ UI(Dynamic Island スタイル)

このスキルは、MacBook のハードウェアノッチから広がる Dynamic Island スタイルのオーバーレイを作成します。オーバーレイは画面上端にぴったりと配置される、透明なフローティングパネルです。物理的なノッチの切り欠きに自然になじむ、凹状の「耳」のカーブを持つカスタムシェイプを使用します。

アーキテクチャ

実装は 3 つの部分で構成されます。

  1. NotchWindow(NSPanel サブクラス) -- CGShieldingWindowLevel に配置され、メニューバーを含むすべての要素の手前に表示される、境界線なし、透明、クリック透過のパネル
  2. NotchShape(SwiftUI Shape) -- 上部の角に凹状の二次ベジェ曲線、下部に凸状の丸角を使用して Dynamic Island のシルエットを描画します
  3. コンテンツビュー -- ノッチ内に表示するもの(ステータスインジケーター、波形、テキスト、アイコンなど)

仕組み

MacBook のノッチは、画面上部中央にある黒い長方形です。黒く塗りつぶした NotchShape を最高のウィンドウレベルでその正確な位置に配置すると、ノッチの領域が視覚的に広がります。シェイプ内のコンテンツは、ハードウェアノッチから「現れる」ように見えます。

位置を決める重要な計算式:

// Use full screen frame (not visibleFrame) to include the menu bar / notch area
let screen = NSScreen.main!
let x = screen.frame.origin.x + (screen.frame.width - totalWidth) / 2
let y = screen.frame.origin.y + screen.frame.height - totalHeight

ノッチがあるメニューバー領域を visibleFrame は除外するため、screen.visibleFrame ではなく screen.frame を使用することが重要です。

参考ファイル

  • references/NotchWindow.swift -- 表示/非表示と位置指定に対応した、そのまま使える NSPanel サブクラス
  • references/NotchShape.swift -- アニメーション可能な角の半径を備えた Dynamic Island シェイプ

統合手順

1. NotchShape を追加する

references/NotchShape.swift をコピーします。これは、設定可能な角の半径を 2 つ持つ SwiftUI の Shape です。

  • topCornerRadius(デフォルト 10)-- ハードウェアノッチの逆向きの角を模した、上部の凹状の「耳」のカーブ
  • bottomCornerRadius(デフォルト 16)-- 下部の標準的な凸状の丸角

どちらも animatableData でアニメーション可能なため、SwiftUI はシェイプの変化を滑らかに補間できます。

2. コンテンツビューを作成する

ノッチ内に表示するものを作成します。コンテンツは NotchShape でクリップし、背景を黒で塗りつぶしてください。

struct NotchContentView: View {
    var isVisible: Bool

    var body: some View {
        HStack {
            Image(systemName: "mic.fill")
                .foregroundStyle(.red)
            Text("Recording")
                .font(.system(size: 13, weight: .medium))
                .foregroundStyle(.white)
        }
        .frame(width: isVisible ? 200 : 0)
        .frame(height: 32)
        .background(NotchShape().fill(.black))
        .clipShape(NotchShape())
        .animation(.spring(response: 0.35, dampingFraction: 0.75), value: isVisible)
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
    }
}
3. NotchWindow を追加する

references/NotchWindow.swift をコピーします。これは、次の機能を持つ汎用 NSPanel です。

  • 境界線なし、透明、非アクティブ化のパネルを作成します
  • CGShieldingWindowLevel(すべての要素の手前)に配置します
  • 画面上部中央の、上端にぴったりの位置に配置します
  • マウスイベントを無視します(完全にクリック透過)
  • すべてのスペースに表示され、フルスクリーンでも維持されます
4. 表示と非表示
// Create once and reuse
let notchWindow = NotchWindow()

// Show with your content
let content = NotchContentView(isVisible: true)
notchWindow.showNotch(content: content)

// Hide with spring collapse animation
notchWindow.hideNotch()

スプリングアニメーションの構成

Dynamic Island の効果は、特定のアニメーションシーケンスによって生まれます。

表示:

  1. ウィンドウを即座に表示します(orderFront)
  2. 次の実行ループのタイミングで、isVisible を true に切り替えます
  3. 幅を .spring(response: 0.35, dampingFraction: 0.75) で 0 から目標の幅までアニメーションさせます

この 2 段階の方法(ウィンドウを即座に表示してからコンテンツをアニメーションさせる)は、SwiftUI がアニメーションを実行する前にビューを階層に追加しておく必要があるため不可欠です。

非表示(3 段階の構成):

  1. 展開されたコンテンツ(テキスト、詳細情報)をクリアし、コンパクトなシェイプに折りたたみます
  2. 0.25 秒後に isVisible = false を設定し、幅を 0 に折りたたむスプリングアニメーションを開始します
  3. 0.65 秒後にウィンドウを削除します(orderOut)

各アニメーションが次のアニメーションの開始前に完了するよう、遅延時間を調整しています。これにより、ノッチに滑らかに「縮んで戻る」効果が生まれます。

// Step 1: collapse content
state.isExpanded = false

// Step 2: shrink width
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
    state.isVisible = false
}

// Step 3: remove window
DispatchQueue.main.asyncAfter(deadline: .now() + 0.65) {
    window.orderOut(nil)
}

画面ジオメトリの詳細

プロパティ値理由
ウィンドウレベルCGShieldingWindowLevel()メニューバーを含むすべての要素の手前
位置の原点NSScreen.main.frame(visibleFrame ではない)ノッチ/メニューバー領域を含める必要があります
水平方向中央揃え:(screen.width - totalWidth) / 2ハードウェアノッチと位置を合わせます
垂直方向上端にぴったり:screen.height - totalHeight上端が画面の端に接します
コレクション動作.stationary, .canJoinAllSpaces, .fullScreenAuxiliary, .ignoresCycleスペースとともに移動せず、フルスクリーンでも維持され、Cmd+Tab には表示されません

ノッチがない Mac 向けのフォールバック

すべての Mac にノッチがあるわけではありません(外部ディスプレイや旧型の MacBook など)。次のように検出できます。

var hasNotch: Bool {
    guard let screen = NSScreen.main else { return false }
    // Notch Macs have a safe area inset at the top
    return screen.safeAreaInsets.top > 0
}

ノッチがないディスプレイでは、screen.visibleFrame と標準の .floating ウィンドウレベルを使い、画面下部にフローティングピルを表示する方法に切り替えます。

デザインガイドライン

  • 形状を黒一色で塗りつぶす — ハードウェアのノッチになじませるポイントです
  • 黒い背景に白またはカラーのテキストやアイコンを使うと、コントラストが際立ちます
  • コンテンツはコンパクトに保つ — ノッチの領域は小さいため、アイコンと短いテキストを組み合わせた1行表示が最適です
  • 録画中のインジケーターには赤を使う — iOS の慣例に合っています
  • 不透明度ではなく幅をアニメーションさせる — Dynamic Island の効果は、形状を拡大・縮小させるものであり、フェードさせるものではありません

コメント