View Clipping Changes in macOS 14 Sonoma

One of the most impactful changes to AppKit in the forthcoming macOS 14 Sonoma update is a change to the default clipping behavior for views. Apple announced that a long-present internal property, “clipsToBounds”, is now public. In tandem with this change, they are changing the default value for this property to false for apps that link against the macOS 14 SDK.

What does it mean for a view to “clip to bounds”? It simply means that no matter what the view does, it will not succeed in drawing outside its own bounds. This sounds like a reasonable thing, but it has historically been a headache for views with shadowed borders, for example, or views that render text that might extend slightly outside the bounds of the view.

Apple acknowledges in the AppKit Release Notes that this change will pose problems for some custom views. One example has to do with the “drawRect:” method and the value of the “dirtyRect” parameter:

Filling the dirty rect of a view inside of -drawRect. A fairly common pattern is to simply rect fill the dirty rect passed into an override of NSView.draw(). The dirty rect can now extend outside of your view’s bounds.

I ran up against this with my own app, MarsEdit, where I noticed the date editor panel was missing the “OK” button:

CalendarClipping

What’s happening here is my custom calendar view is filling the “dirtyRect” with its background color before continuing to draw the rest of its content. Unfortunately, what Apple promised in the excerpt above has come to pass: the dirtyRect encompasses nearly the entire window! So when my calendar view redraws, it is evidently overwriting the “OK” button, as well as a date picker that is supposed to appear below the calendar.

The fix is relatively simple in this case: because I don’t anticipate the view being very large, and filling a rect is a pretty cheap operation, I simply changed it to fill “self.bounds” instead of “dirtyRect”.

Filling the dirty rect is such a pervasive pattern that I expect many apps will see problems like this. It also seems odd that views such as mine are being passed such a large dirtyRect. It makes me wonder if, during the beta testing phase, Apple is intentionally passing extra-large dirty rects to views, to expose issues like this.

The takeaway is you probably want to do a global search in your projects for “dirtyRect” and evaluate whether the pertinent code in each case assumes the rect is constrained to your view’s bounds. As soon as you start compiling and linking against the macOS 14 SDK, it won’t be.