When I build my UI application with Xcode 15 and run the app on macOS 14 (Sonoma), the UI is largely missing in several views. The UI looks fine when running that same build on any system prior to macOS 14. The UI also looks fine when building with Xcode 14.3.1 and running the app on macOS 14. It's just the combination of Xcode 15 and macOS 14 that causes this problem.
As a workaround I keep building the app with Xcode 14.3.1, even though I'm on Sonoma and Xcode 14.3.1 doesn't even support Sonoma, but using this trick I was able to get it working anyway. This solves the problem for now but is not really a long term fix, since at some point in the future, upgrading to an newer Xcode version will become unavoidable.
First of all, the issue is not related to the Xcode version but to the SDK version. Xcode 15 ships with a macOS 14 SDK, while Xcode 14.3.1 only ships with a macOS 13 SDK.
The reason for the issue is a change in AppKit, that will only affect apps linked against the macOS 14 SDK and only when also running on macOS 14, which explains why the problem does not appear when running on older macOS versions or building with an older SDK.
The change is mentioned in the macOS 14 AppKit release notes:
Source: AppKit 14 Releas Notes
This mainly affects subclasses of
NSView
that implement their owndrawRect:(NSRect)dirtyRect
method. ThedirtyRect
describes the array of screen that needs redrawing and it is passed to the method, so any drawing code can limit its drawing operations to that area only.This is just a speed optimization, as always redrawing the entire view will also produce correct results, but it may cause the view to draw way more than would really been necessary. That's why many simple views don't even consider the
dirtRect
and just always redraw everything but that way they waste CPU/GPU resources for nothing.So far
clipsToBounds
has always been true. This means that prior to calling this method, the system would clip thedirtyRect
to the bounds of the view. So anydirtRect
ever passed to the view was either a sub-rect of the view's current bounds or, in the worst case, equaled the bounding rect, in which case the entire view had to be redrawn. But that's no longer the case by default, meaning parts of thedirtyRect
can lie outside the view's bounding box or thedirtyRect
can also be way bigger than the entire view and thus completely enclose it.When the system wants to update the whole window, it can just use a
dirtRect
that encloses the entire window, or larger chunks of it, and without clipping to bounds, the following code will cause the UI problem:This is also addressed by Apple in the release notes:
Source: AppKit 14 Releas Notes
So far this fill instruction would only fill either the entire view or a sub-rect of it. But with no clipping anymore, it may fill large parts of its superview or even the entire window!
Apple makes two suggestions how to fix that but IMHO those are both are bad suggestions.
Re-enabling
clipToBounds
will solve the problem, as thendirtyRect
is clipped again and you get the old behavior. However, keep in mind, that your custom drawing view can also be used within Interface Builder (in a XIB file) and in Interface Builder,Clips Bounds
is a settable property, so somebody using your view, not knowing that it depends on that clipping behavior (as that's not really expected to be the case), may set the property incorrectly and then having no idea why the entire UI is gone again.Always filling the entire bounds will, of course, work correctly, but that's simply overdoing. The
dirtyRect
may also be smaller than the view's bounds and there is no reason why you need to fill a huge area of screen if only a small portion of it really needs redrawing.IMHO the best solution is to simply clip to bounds yourself:
clippedRect
is a rect describing the area that is common to both, your bounds and thedirtyRect
. In the worst case, it will equal your bounds, but it will never get bigger than that. And in the best case, it will be a small subarea of your bounds and then only that subarea requires filling.