大多数浏览器和
Developer App 均支持流媒体播放。
-
AppKit 的新功能
探索 Mac App 开发的最新进展。我们将分享对控件和菜单的改进,并探索可以帮助您摆脱(视图)束缚的工具。了解如何在你的用户界面上添加动画,如何应用对文本输入的改进,以及如何将你现有的代码与 Swift 和 SwiftUI 集成。
章节
- 1:00 - Controls
- 5:31 - Menus
- 9:19 - Cooperative app activation
- 11:16 - Graphics
- 18:04 - Graphics: Images
- 20:54 - Text improvements
- 23:43 - Swift and SwiftUI
资源
相关视频
WWDC23
-
下载
♪ ♪
Aasim:大家好 我是 Aasim Kandrikar 稍后 Raleigh Ledet 也将加入 我们都是 AppKit 团队的工程师 在本视频中 我们会介绍 macOS Sonoma 中“AppKit 的新功能” 我们将涉及广泛的主题 首先是 AppKit 控件的 新功能和 API 的增强 通过全新的实施方案驱动的菜单改进 macOS 上 App 激活方式的变化 对图形的完善 以及图像和符号的新功能 对于非英语语言的 新的文本输入体验和文本排版的改进 以及对使用 Swift 和 SwiftUI 工作的改进
macOS Sonoma 包含了一些 AppKit 控件中 令人振奋的新功能和 API 的增强 NSTableView 和 NSOutlineView 提供许多开箱即用的功能 这是设计出色的 Mac App 的关键因素
macOS Sonoma 推出了新的 API 用于显示定制列菜单 菜单允许用户 在表格中切换列可见与否 以前 开发者需要进行自定义来 创建和呈现这种菜单 而现在只需要添加三行代码就能实现 使用新的委托方法 tableView userCanChangeVisibilityOf 设定用户可以隐藏的列 剩下的交给 AppKit 比如菜单的本地化和 重启时恢复隐藏的列的状态
Foundation 中的 Progress 类型 代表你的 App 执行的工作 你可以使用这个功能呈现下载进度 或图片处理进度 在 macOS Sonoma上 你现在可以结合使用 Foundation 中的 Progress 类 和 NSProgressIndicator 将进度指派给新的 observedProgress 属性 进度指示器就会随着进度变化 自动更新数值 甚至可以用于后台线程
Button bezel style API 也有更新 首先是全新的按钮样式 automatic 这一按钮样式会根据按钮的内容和 它在视图层次中的位置 自动变为合适的样式 举例来说 如果按钮在一个窗口里 它会变为常规按钮样式 如果按钮在工具栏里 它会变成工具栏样式 在较高的空间 按钮会变为 灵活的常规按钮样式 自动按钮样式现在是 所有按钮的初始化的 默认按钮样式
现存的按钮样式名称也有更新 从对外观的描述 改为基于其实际用法的新名称 比如 之前称为“嵌入式”的按钮 现在叫做“配件栏”按钮 表明这个按钮样式最常在 配件栏上使用 不鼓励使用的按钮样式现已弃用 弃用的按钮样式 现可参照对应的语意上明确用途的 按钮样式 我们引入了全新的 分屏浏览类型: 检查器 检查器是一个后置分割视图项目 可用于显示 文档当前选中内容的上下文信息
和边栏一样 当设置了全尺寸内容显示时 检查器也会利用窗口的全部高度 新的检查器适用的 最早版本是 macOS Big Sur 在你的 App 上添加检查器非常简单直接 首先 用新的 inspectorWithViewController 初始化方法 创建一个新的分割视图项目 接着 在已有的分割视图控制器上 添加新的 splitViewItem 然后 更新工具栏委托 以把新的检查器开关 加入工具栏项目中 你通常希望 把检查器开关 放在检查器上方 窗口的后侧边缘 要想实现这一效果 在检查器开关项目前 添加新的检查器追踪分隔符和 一个灵活空间
我们改进了 NSPopover 首先 我们现在支持 可锚定在工具栏项目的弹出窗口 我们还增加了一种 支持全尺寸弹出式内容的方法 这样你的视图就可以 填满整个弹出窗口的范围 先从工具栏的锚定说起 它是为工具栏项目展示 相关弹出框的新方式
当工具栏项目位于溢出菜单中时 弹出框会优雅地锚定在 延伸部分的指示箭头上
第二 弹出框的内容 现在可以扩展到指示箭头部分 如果你的弹出框的标题部分有底色 它看起来可能会像这样 注意标题的底色没有延伸到 指示箭头的部分 要把弹出框的内容 延伸到指示箭头的区域 需要将新的 hasFullSizeContent 属性设置为 true 使用安全区域矩形来布局 弹出框中内容以便不应被遮挡
接下来让 Raleigh 来谈谈菜单中激动人心的变化 Raleigh:谢谢你 Aasim 为了充分利用 Cocoa 菜单进行了重写 内存和 CPU 使用量显著减少 AppKit 中遗留的 Carbon 也更少了 菜单还增加了新的功能
我将特别讲解组标题 调色板菜单 新的选择模式以及徽标 这些功能会带来新的可能性 而且能减少你需要写的代码的总量
组标题是新增的功能 可以在菜单中 体现分组 而且只需要一行代码 使用新的类函数 sectionHeader(title:) 来创建组标题 并添加到菜单中 就和其他菜单项一样 在这个例子中创建了三个菜单组 每一组都有一个组标题和两个菜单项
调色板菜单也是令人兴奋的新功能 你可以用它创建 各菜单项水平排列的菜单 比如说 这样一个简单的选色器
你可以通过将菜单的 presentationStyle 设置为 .palette 来将任意菜单变为调色板菜单 你需要为每一个菜单项设置图标 对于预设图标 AppKit 会自动添加 合适的选中样式 或者 你也可以自行设置 offStateImage 和 onStateImage onStateImage 用于 表示选中项目
你可以从两个模式中 选择合适的一个 .selectAny 只切换单独的菜单项状态 而不会改变同一组中其他项的状态 .selectOne 会将选中的菜单项 设置为开启状态 而同组的其他项会被设置为关闭状态 你也可以用 selectedItems 属性 读取或设置菜单项的开启状态 注意:selectionMode 和 selectedItems 通过 将相同的 target/action 组合 编入同一逻辑组来生效 在手动创建调色盘菜单时 你需要给每个菜单项 赋予相同的 target/action 组合 才能充分利用这些新行为 还有一个提示:这个技巧 不仅适用于调色盘菜单 也适用于普通的菜单 只要菜单项的 target/action 组合 是一致的即可 NSMenu 也提供了方便的功能 用于创建一般的调色盘菜单 颜色数组决定调色板菜单项的 数量和颜色 标题用于辅助功能 所以一定要加上 可选模板参数指定 着色时所用的模板图标 比如说 这里就用了旗标
如果你不指定模板 AppKit 默认使用 填充圆形 还有一个可选项是 闭包参数 每当用户切换菜单项状态时 闭包就会被调用 菜单会被传递给闭包 这样你就会得到 在 selectedItems 属性中 设定为选中的项的数组
你现在可以为菜单项 添加多种不同的徽标 你可以使用简单的字符串或是数字
还有三个特殊的计数徽标: Newitems、alerts 和 updates 使用它们中的任意一个 AppKit 会自动添加合适的文本 而且 AppKit 还会将这些文本本地化 在这个例子中 文字被翻译成了日语 不过请注意 你仍然需要自己 本地化菜单项本身 以及一般的字符串徽标
以上就是全新的菜单功能 优化了性能、徽标、 调色盘菜单和组标题
在 macOS Sonoma 中, 我们引入了 App 协作激活 App 协作激活可以 减少意外的 App 切换 比如你正在打字时进行的 App 切换
App 协作激活分为两个部分 激活 App 现在是请求而非命令 系统会根据用户操作所在的更宽泛的场景 来判断激活请求是否合适 这个新的 yield API 允许 App 对未来的激活请求的上下文场景产生影响
既然激活 App 变成了一个请求 ignoringOtherApps 参数 和选项就不起作用了 如此一来 在 macOS Sonoma 中 activate(ignoringOtherApps:) 功能和 activateIgnoringOtherApps 选项 就都被废弃了 用 NSApplication 和 NSRunningApplication 的 新激活 API 来替代它们
只有活跃的 App 能影响激活场景 为此它要先让位于一个明确的目标应用 这个目标应用才会被激活 接下来 当目标 App 请求激活 系统将让位作为场景考量的一部分 如果请求被认可 当前活跃的 App 变为不活跃 而目标 App 将会激活 否则 当前活跃的 App 继续活跃 在打开链接或 App 时 NSWorkspace 会自动运行以上功能
在其他情况下 如果你要手动激活另一个 App 对目标 NSRunningApplication 或 bundle identifier 使用 yieldActivation
当目标 App 请求激活 或以其他方式自行激活时 系统会考虑让位的上下文情景 以上就是新的 App 协同激活行为 在 macOS Sonoma 中 我们还为图像和绘制 做了一些重大改变并添加了新的 API 你现在可以从 NSBezierPath 建立 CGPath 反之亦然 NSBezierPath 有了新的 init(cgPath:) 初始化方法和 cgPath 属性 通过 cgPath 初始化 设置和获取 cgPath 都会建立此 path 的副本 之后更改这个 NSBezierPath 不会体现在最初的 CGPath 实例 或其副本中 也就是说 它们不是无缝桥接的 这一补充使我们能同时使用 NSBezierPath 和 CGPath API 比如设定 CAShapeLayer 上的 path 属性 只需要一行代码 现在你可以在 macOS 上 创建 CADisplayLink 对象了 它和你熟悉的 iOS 上的 CADisplayLink 是相同的 如果你不熟悉 CADisplayLink 它是一个计时器对象 用于通过 App 的绘制和 显示屏的刷新率 直接初始化的显示器链接对象 和主显示屏是同步的 但 macOS 不一定只有一个显示器 因此 在 macOS 上 你可以用 新的 displayLink(target:selector:) 方法 直接从 NSView、NSWindow 或 NSScreen 获取显示器链接对象 最好的方式是 直接从最具体的适用的元素 通常是一个视图中 获取 CADisplayLink 对象 这是因为当 CADisplayLink 从视图或窗口被创建的时候 随着窗口在桌面上移动 它会自动跟踪窗口所在的 显示器或视图 也包括 在不出现在显示器上时 将自己挂起
在这个视图子类中 当 startAnimating 被调用 只需两行代码 它就能创建一个 DisplayLink 对象 来调用 stepAnimation 功能 与视图所在的任意显示器同步 并将此 displayLink 添加到 常规模式的 main runloop 中
当动画结束时 它会调用 invalidate 来终止 displayLink 并将它从所有 已注册的 runloop 模式中移除
NSColor 现在提供五种新的系统颜色 用于填充背景图形 对于不同尺寸的形状 填充色用不同的级别来强调 对于滑块轨道或进度栏之类 比较小的形状 背景色通常使用更深的颜色 来突出强调它 比如系统填充色 或二级系统填充色 更大的形状 如分组框和 字体背景更适合 浅一些的背景颜色 比如四级或五级系统填充色 这些填充色是动态的 也就是说它们能自动适应 不同的外观 包括增强对比度和深色模式 在建立自定义 UI 元素时 新的填充色便于 适应系统设计 增强易读性
NSViews 会沿着边缘裁剪绘图内容 有时这会导致图像 无法像你希望的那样显示 比如这个无边记警告窗口中 这行印地语字母的最下方的情况
这种情况常见于字体渲染、 阴影和其他子视图配饰 比如徽标 还有这个 代表 “热卖” 的火焰图标 有一些方法可以解决这个问题 比如 把同级视图组合 在一个更大的视图里 但是 每一种方法都有自己的缺点 在这个例子中 如果把合并视图和 一个普通的按钮横向组合在一起 在默认设置下 文字的基线就无法对齐 于是又增加了新的问题
现在有了更好的解决方案 链接到 macOS Sonoma 后 大部分 NSViews 不再 默认沿边缘裁剪视图
命中测试没有改变 仍然由视图的几何图形决定 当然 你可以替换 hitTest 来改变它 既然视图可能扩展到它的边缘以外 它的计算后的 visibleRect 也可能扩展到边缘以外 你需要检查所有使用了 visibleRect 的代码并作出相应调整 它同样会影响 绘图功能的 dirtyRect 参数 具体来说 dirtyRect 不受视图边缘的限制 AppKit 保留传递大于视图边界的 dirtyRect 的权利 AppKit 也保留将绘图分割为 所需的任意数量的矩形的权利 这意味着你需要用 dirtyRect 决定 绘制什么 而不是绘制在哪里
这是一个可能发生的 错误绘图结果的例子 这个 draw 的重载 用传入的 dirtyRect 填充背景 导致填充色溢出了视图的边缘 盖住了窗口中的其他 UI 这个视图没有用 dirtyRect 绘制边框
同样地 它的背景填充色应该精确地 填充设计所需要的部分 不多不少
在 dirtyRect 之外绘制总是安全的 dirtyRect 的性能优势体现在 通过它确定在这个过程中 可以避免绘制哪一部分的数据 也许计算这样花哨风格名字 的笔画路径很费资源 但如果 dirtyRect 只是在这个小角落 不和文本框架交叉 就可以避免花费过多的资源来计算 你仍需要绘制背景和边框 但是在这个传递过程中填充整个边缘 和绘制整个边框不会影响 视图中其他已经绘制好的部分 因为 AppKit 会 按照 dirtyRect 裁剪绘图
新的 NSView .clipsToBounds 属性 适用的最早版本是 OS X Mavericks 10.9 但要注意 在旧版本的 OS 上 关闭 .clipsToBounds 可能导致边缘粗糙 请做相应的测试 大部分视图都不会 受裁剪属性打开或关闭的影响 但一些容器视图 会做出自己的具体判断 NSClipView 如其名 是这样做的 在一些实例中 视图的 默认裁剪行为不适用于你的设计 你需要有选择性地修改 你需要一项项地考虑你的哪些视图 需要明确的 clipsToBounds 值 能实现 App 的意图的才是正确的选择 现在再让 Aasim 来讲讲图像 Aasim:谢谢你 Raleigh 符号是设计 App 时的重要环节 在 macOS Sonoma 中, 符号有了全新的功能: 符号特效 有了符号特效 可以为符号添加特效 比如弹跳、 渐变过渡和 律动动画
符号特效特别适合用来 强调 App 中一个动作的发生 或者状态的改变 添加符号特效非常简单 首先 将 imageView 的 image 属性 设置为一个 symbol image 接着 当你想要添加特效时 只需在 image view 中 调用 addSymbolEffect 注意 只有在 NSImageView 使用 symbol image 时 这个功能才生效
关于使用符号特效的更多信息 请参考视频“ 让你 App 中的符号生动起来” macOS Ventura 支持自动调整 SF 符号 来适应 用户当前的区域设置 现在 在 macOS Sonoma 中 你的资源目录中的图像和符号 也有了相同的功能 与 macOS Ventura 中的 SF 符号一样 它们默认跟随系统区域设置 你也可以使用 image locale 方法 获取固定地区设置的图像 接下来 我会讲解高动态范围内容 简称 HDR 高动态范围内容能表现 远多于标准内容的亮度 macOS 过去的几个版本 都支持高动态范围 让你可以充分利用 Macbook Pro 的 Liquid 视网膜 XDR 屏和 Pro Display XDR 等显示器 macOS Sonoma 让在 App 上 显示 HDR 内容变得前所未有地简单 只要使用 NSImageView 就能支持 HDR 内容的显示 含有 HDR 内容的图像 在支持扩展动态范围的硬件上 会以 HDR 模式显示 要以标准动态范围显示 HDR 内容 请替换 preferredImageDynamicRange 的属性
关于使用这个 API 的更多信息 请参考视频 “在 App 中支持 HDR 图像” 从 Xcode 15 开始 你的资源目录中的图像和颜色 都会自动地以 NSImage 和 NSColor 数据属性的形式 反映在你的代码里 这使你可以用 清晰的点标记来访问图像 而不需要使用字符串来初始化它 这些图像不是 optional 的 所以你也可以移除任何的 强制解包或 guard 检查 如果你修改了资源目录 移除或重命名了一个图像 在构建 App 时 编译器会 找出代码中不匹配的地方 并报错 这样你就可以立刻改正 而不是在运行时再去查找 macOS Sonoma 在文本输入体验方面 引入了重要的改变 并优化了非英语语言的文本排版
首先是全新的插入指示器 它会自动变为当前的强调色 并在语音输入时 添加跟随的发光效果
接着 在插入指示器下方 添加了光标附件来显示关键信息 如输入模式、语音输入状态 和大写锁定状态 附件会跟随当前的插入位置 当插入位置不在可见视图范围内时 它会固定在文档的底端
使用标准 AppKit 文字视图的 App 会自动使用这个功能 如果你使用的是自定义文字视图 我们也有新的 API 可以使用 你可以用 NSTextInsertionIndicator 视图 来替换自定义的文字插入指示器绘制 将这一视图作为子视图 添加到自定义文字视图上 就能获得各种 OS 都适用的 新的插入指示器 注意 你需要自己更新 插入指示器的框架 并决定它是否可见 当文字视图不再是第一响应者时 你可以将 displayMode 属性 改为 hidden 来隐藏指示器 MacOS Sonoma 对非英语语言的 文字排版进行了一些优化 需要强调的变化是 我们改变了 断行和使用连字符的规则 因为根据语境不同 一些语言有特殊的断行规则 举例来说 在韩文的传统排版中 正文部分可以在词语中间断行 但标题只能在两个词语之间断行 在标题中把一个词语分成两行 在韩文里是不合适的 在这个从地图中摘出的表单里 韩文词语“时间” 被分开在了两行 macOS Sonoma 现在会根据 字体使用的文本风格 进行不同的断行 在韩文中 像这个表单中的 用于标题的字体风格 就不会在词语之间断行 而用于正文部分的字体 如果 情况合适 就可能在词语中间断行 再举一个例子 在较窄的排版中 一些德文词语 可能会比整行的长度还要长 从而导致单独的字母被分割到下一行 我们不想看到这样的断行 它看起来很不平衡 而且语素 也就是组成单词的元素 会被分割在两行里 在 macOS Sonoma 中 如果你的标题文本区域 禁用了连字符 macOS 会 自动在两个语素之间断行 并添加连字符 而不是按照字母断行 这会使得排版看起来更平衡且易读 现在就在你的 App 中 采用文本样式吧 在 macOS Sonoma 中 AppKit 进行了更新 让采用 Swift 优先的功能更简单 比如 Swift concurrency 和 transferable SwiftUI 也进行了更新 允许在 AppKit App 的更多地方 使用 SwiftUI 的视图和修饰符
大部分 AppKit 的类 都被限制在主线程中 在 Swift concurrency 下 这些类被标记为 MainActor 以生成适当的编译器错误 然而 AppKit 里的一些特定类 比如 NSColor 和 NSShadow 在主线程之外也可以安全地访问 在 macOS Sonoma 中 这些类符合 Sendable 协议 也就是说它们可以 在 actor 之间自由传递
Transferable 是一个 Swift 协议 用于描述对象如何被 序列化和去序列化 这就增强了 SwiftUI 中 拖放和共享等功能 在 macOS Sonoma 中 NSImage NSColor 和 NSSound 符合 Transferable 协议 这使得 AppKit App 在 SwiftUI 视图中 使用拖放和共享等功能 变得更简单了
在 macOS Ventura 13.3 中 我们为 NSViewController 引入了一个新的属性包装器 ViewLoading 在用 loadView 初始化的属性上 使用 ViewLoading 如果这些属性之前是 optional 你可以移除其可选性 以及任何相关的检查 通过调用 loadViewIfNeeded 视图控制器能够保证属性已初始化 另一个相似的属性包装器 WindowLoading 也可在 NSWindowController 的属性上使用 有了 Xcode 15 你可以使用预览 显示你的 AppKit 视图和 视图控制器 使用新的 Preview 宏 提供一个名字 返回你的视图或视图控制器 预览会随着代码的改变即时更新 了解更多信息 请查看视频 “使用 Xcode Previews 建立程序 UI”
NSHostingView 和 NSHostingController 是循序渐进地将 SwiftUI 导入 AppKit App 的好方法 在 macOS Sonoma 中 有几个新功能可以让你 在更多的地方采用 SwiftUI SwiftUI 修饰符 如工具栏和导航标题 现在也可以在 NSWindows 中使用 如果窗口的 hostingView 是 contentView SwiftUI 会自动将所有 可用的场景修饰符 桥接到你的 NSWindow 想要实现更强的控制 可以利用 NSHostingView 和 NSHostingController 的 新属性:sceneBridgingOptions 使用这个属性 你可以具体规定 哪些属性需要从你的 SwiftUI 视图中 桥接到 NSWindow
这只是 macOS Sonoma 中 AppKit 的一部分新功能 下一步该做什么? 首先 用 macOS Sonoma SDK 编译你的 App 并检查 确保对裁剪和激活做的改变 不会导致多余的副作用 接着 采用新的控件 API 比如全尺寸检查器 和表格列自定义 API 充分利用 macOS Sonoma 的符号特效 来更新你的 App 设计 最后 使用 AppKit 专门为 Swift 新增添的功能 比如 NSHostingView 的 Transferable 和优化 在你 App 中的更多地方采用 SwiftUI
非常感谢你的观看 希望你喜欢 macOS Sonoma 的新功能! ♪ ♪
-
-
1:36 - Configure NSTableView column customization menu
func tableView(_ tableView: NSTableView, userCanChangeVisibilityOf column: NSTableColumn) -> Bool { return column.identifier != "Name" }
-
1:53 - Configuring NSProgressIndicator to sync with Progress
func fetchData() { let url = URL(string: "https://developer.apple.com/wwdc23/")! let task = URLSession.shared.dataTask(with: .init(url: url)) progressIndicator.observedProgress = task.progress task.resume() }
-
3:48 - Adding an inspector to your app
let inspectorItem = NSSplitViewItem(inspectorWithViewController: inspectorViewController) splitViewController.addSplitViewItem(inspectorItem) func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { [.toggleSidebar, .sidebarTrackingSeparator, .flexibleSpace, .addPlant, .inspectorTrackingSeparator, .flexibleSpace, .toggleInspector] }
-
4:38 - Show a NSPopover relative to a NSToolbarItem
func toolbarAction(_ toolbarItem: NSToolbarItem) { let popover = NSPopover() popover.contentViewController = PopoverViewController() popover.show(relativeTo: toolbarItem) }
-
18:30 - Adding symbol effects to a image view
wifiImageView.image = NSImage(systemSymbolName: "wifi", accessibilityDescription: "wifi icon") wifiImageView.addSymbolEffect(.variableColor.iterative, options: .repeating)
-
24:56 - Using @ViewLoading to remove optionality on properties
class ViewController: NSViewController { @ViewLoading var datePicker: NSDatePicker var date = Date() { didSet { datePicker.dateValue = date } } override func loadView() { super.loadView() datePicker = NSDatePicker() datePicker.dateValue = date view.addSubview(datePicker) } }
-
25:26 - Preview NSView and NSViewController using the Preview macro
#Preview("Tree Species") { let treeCellView = TreeCellView() treeCellView.species = .spruce return treeCellView }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。