关于 macos-notch-ui
macos-notch-ui 是一项为 MacBook 刘海实现 Dynamic Island 风格浮层的开发技能,面向构建 macOS 界面的开发者。它提供 SwiftUI 形状和 NSPanel 窗口方案,并说明定位、动画及无刘海屏幕的回退设计。
主要功能
- 01提供 SwiftUI 刘海轮廓形状
- 02通过 NSPanel 创建透明置顶浮层
- 03对齐屏幕顶部与硬件刘海
- 04支持形状尺寸动画与显示隐藏
- 05检测无刘海屏幕并回退显示
适用场景
- 01在刘海区域显示状态信息
- 02显示录音指示或简短文本
- 03为浮层实现展开与收起动画
- 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(灵动岛风格)
此技能会创建一个灵动岛风格的浮层,从 MacBook 的硬件刘海处延伸出来。该浮层是一个透明的悬浮面板,与屏幕顶部齐平,并使用自定义形状;其顶部带有凹形“耳朵”曲线,可与实体刘海开孔无缝融合。
架构
实现分为 3 个部分:
NotchWindow(NSPanel 子类)——一个无边框、透明、可穿透点击的面板,位于CGShieldingWindowLevel层级,显示在所有内容之上,包括菜单栏NotchShape(SwiftUI Shape)——绘制灵动岛轮廓,顶部角采用凹形二次贝塞尔曲线,底部采用凸形圆角- 你的内容视图——你想在刘海区域中显示的任何内容(状态指示器、波形、文本、图标)
工作原理
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
必须使用 screen.frame(而不是 screen.visibleFrame)——visibleFrame 不包含刘海所在的菜单栏区域。
参考文件
references/NotchWindow.swift——可直接使用的 NSPanel 子类,支持显示、隐藏和定位references/NotchShape.swift——灵动岛形状,支持可动画的圆角半径
分步集成
1. 添加 NotchShape
复制 references/NotchShape.swift。这是一个 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()
弹簧动画编排
灵动岛效果来自特定的动画序列:
显示:
- 窗口立即出现(
orderFront) - 在下一个运行循环周期,将
isVisible切换为true - 宽度通过
.spring(response: 0.35, dampingFraction: 0.75)从 0 动画到目标宽度
采用两步方式(先立即显示窗口,再为内容添加动画)是必要的,因为 SwiftUI 需要先将视图加入视图层级,才能对其执行动画。
隐藏(3 步编排):
- 清除所有展开的内容(文本、详情)——收起为紧凑形状
- 0.25s 后,将
isVisible设为false——触发弹簧动画,将宽度收缩至 0 - 0.65s 后,移除窗口(
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 窗口级别,在屏幕底部显示悬浮胶囊作为回退方案。
设计指南
- 用纯黑色填充形状——这样才能让它与硬件刘海融为一体
- 在黑色背景上使用白色或彩色文字/图标,以形成对比
- 保持内容简洁——刘海区域很小。最适合使用带图标和简短文字的单行布局
- 录制指示器使用红色——符合 iOS 惯例
- 对宽度而非不透明度进行动画处理——灵动岛效果的关键在于形状的扩展和收缩,而非淡入淡出