Mec*_*cki 2 macos user-interface xcode appkit macos-sonoma
当我使用 Xcode 15 构建 UI 应用程序并在 macOS 14 (Sonoma) 上运行该应用程序时,多个视图中的 UI 大部分缺失。在 macOS 14 之前的任何系统上运行相同的构建时,UI 看起来都很好。使用 Xcode 14.3.1 构建并在 macOS 14 上运行应用程序时,UI 看起来也很好。只是 Xcode 15 和 macOS 14 的组合导致了这种情况问题。
作为一种解决方法,我继续使用 Xcode 14.3.1 构建应用程序,即使我使用的是 Sonoma 并且 Xcode 14.3.1 甚至不支持 Sonoma,但使用这个技巧我还是能够让它工作。这暂时解决了问题,但并不是真正的长期解决方案,因为在未来的某个时候,升级到更新的 Xcode 版本将不可避免。
首先,问题与Xcode版本无关,而是与SDK版本有关。Xcode 15 附带 macOS 14 SDK,而 Xcode 14.3.1 仅附带 macOS 13 SDK。
\n该问题的原因是 AppKit 中的更改,该更改只会影响与 macOS 14 SDK 链接的应用程序,并且仅当也在 macOS 14 上运行时,这解释了为什么在较旧的 macOS 版本上运行或使用较旧的 macOS 版本构建时不会出现该问题SDK。
\nmacOS 14 AppKit 发行说明中提到了这一变化:
\n\n\n\n在 macOS 14 中,AppKit 公开了 NSView 的 ClipsToBounds 属性。
\n对于与 macOS 14 SDK 链接的应用程序,此属性的默认值为 false。与旧版 SDK 链接的应用程序默认为 true。某些类(例如 NSClipView)继续默认为 true。默认值的更改认识到,剪切视图\xe2\x80\x99s 后代比取消剪切视图\xe2\x80\x99s 祖先要容易得多。
\n
这主要影响NSView实现自己drawRect:(NSRect)dirtyRect方法的子类。描述dirtyRect了需要重绘的屏幕数组,并将其传递给该方法,因此任何绘图代码都可以将其绘图操作限制为仅该区域。
这只是速度优化,因为总是重绘整个视图也会产生正确的结果,但它可能会导致视图绘制超出实际需要的程度。这就是为什么许多简单视图甚至不考虑dirtRect并且总是重绘所有内容,但这样他们就白白浪费了 CPU/GPU 资源。
到目前为止clipsToBounds一直都是如此。这意味着在调用此方法之前,系统会将 裁剪dirtyRect到视图的边界。因此,dirtRect传递给视图的任何内容要么是视图当前边界的子矩形,要么在最坏的情况下等于边界矩形,在这种情况下必须重新绘制整个视图。但默认情况下不再是这种情况,这意味着 的某些部分dirtyRect可以位于视图的边界框之外,或者dirtyRect也可以比整个视图大得多,从而完全包围它。
当系统想要更新整个窗口时,它可以只使用dirtRect包围整个窗口或更大块的窗口,并且不裁剪到边界,以下代码将导致UI问题:
- (void)drawRect:(NSRect)dirtyRect\n{\n [[NSColor ...] set];\n NSRectFill(dirtyRect);\n}\nRun Code Online (Sandbox Code Playgroud)\nApple 在发行说明中也解决了这个问题:
\n\n\n\n填充 -drawRect 内视图的脏矩形。一个相当常见的模式是简单地用矩形填充传递给 NSView.draw() 重写的脏矩形。脏矩形现在可以扩展到视图\xe2\x80\x99s 边界之外。可以通过填充边界而不是脏矩形或通过设置 ClipsToBounds = true 来调整此模式。
\n
到目前为止,这个填充指令只会填充整个视图或其子矩形。但是,如果不再进行剪切,它可能会填充其超级视图的大部分甚至整个窗口!
\n苹果提出了两个如何解决这个问题的建议,但恕我直言,这些都是不好的建议。
\n重新启用clipToBounds将解决问题,因为随后dirtyRect会再次被剪辑,并且您会得到旧的行为。但是,请记住,您的自定义绘图视图也可以在 Interface Builder 中使用(在 XIB 文件中),并且在 Interface Builder 中,它Clips Bounds是一个可设置的属性,因此有人使用您的视图,而不知道它取决于剪切行为(因为实际情况并非如此),可能会错误地设置属性,然后不知道为什么整个 UI 又消失了。
当然,始终填充整个边界会正确工作,但这只是过度。也dirtyRect可能小于视图的边界,如果只有一小部分确实需要重绘,则没有理由需要填充屏幕的很大区域。
恕我直言,最好的解决方案是简单地限制自己:
\n- (void)drawRect:(NSRect)dirtyRect\n{\n NSRect clippedRect = NSIntersectionRect(dirtyRect, self.bounds);\n [[NSColor ...] set];\n NSRectFill(clippedRect);\n}\nRun Code Online (Sandbox Code Playgroud)\nclippedRect是一个矩形,描述您的边界和dirtyRect. 在最坏的情况下,它会等于你的界限,但它永远不会比这个更大。在最好的情况下,这将是边界的一个小分区,然后只有该分区需要填充。