I’d like to announce the release of one of my side projects: NativeMarkKit. NativeMarkKit is a Swift package implementing the rendering of NativeMark. What’s NativeMark? Well, it’s a flavor of Markdown designed to be rendered by native iOS, macOS, or tvOS apps. i.e. it compiles down to
NSAttributedString (mostly) instead of HTML and CSS. It’s basically CommonMark, but without the raw HTML tag support.
I often get mockups from designers who want to style text like they would on web. So they mix in bold, italics, links and other things within the copy. While that can be done natively, it’s a hassle, requiring manually building up
NSAttributedStrings and concat-ing them together. NativeMarkKit allows me to have one localized Markdown string, and let the framework figure out how to render that down into a
NSAttributedString. As a bonus, NativeMarkKit supports Dynamic Type and Dark Mode. I can also style the Markdown using whatever app-branded
NS/UIColors I have in the app.
This is a quick write-up of an unexpected SwiftUI limitation I encountered recently. In my own searching, I couldn’t find it documented anywhere so I’m hoping this post will help someone in the future.
You can’t wrap a
UIView in SwiftUI’s
UIViewRepresentable if that
UIView changes its
intrinsicContentSize based in its
frame width, because
UIViewRepresentable will ignore
invalidateIntrinsicContentSize(). As a result, the
UIView will have its height clipped.
To make it a bit more concrete, imagine you have a
UIView that draws multiline text that can line wrap.
UILabel would be an example. If you were to adjust the label’s
frame width, it would reflow the text and the intrinsic content height would change to reflect that.
During SwiftUI layout, what
UIViewRepresentable does is ask the
UIView for its intrinsic content size before it has set the
UIView‘s frame. It then takes the minimum of the available size from the superview and the
intrinsicContentSize, and sets that as the
frame. This causes the
UIView to reflow the text and invalidate its intrinsic content size.
UIViewRepresentable ignores this, and as a result the
UIView is clipped too short.
You can find a simplified version of this bug using UILabel on Github.
I tried many suggestions from Stack Overflow, and override any size or layout related
UIView method that I could. None of the suggestions worked, and almost none of the
UIView methods were ever called during layout. I felt I was probably missing something obvious, so I filed a Developer Tech Support incident with Apple to get an official answer. My submission is summarized in the Github repo.
Here’s the official response from Apple:
Thank you for contacting Apple Developer Technical Support (DTS). We have reviewed your request and have concluded that there is no supported way to achieve the desired functionality given the currently shipping system configurations.
If you would like for Apple to consider adding support for such features in the future, please submit an enhancement request via Feedback Assistant (https://feedbackassistant.apple.com). For more information on Feedback Assistant, please visit https://developer.apple.com/bug-reporting/.
While a Technical Support Incident (TSI) was initially debited from your Apple Developer Program account for this request, we have assigned a replacement incident back to your account.
Developer Technical Support
Worldwide Developer Relations
According to the response this is expected behavior, so any change would an enhancement request. I have since filed the enhancement request as feedback
Moment of Zen
I have the exact same view on macOS as an
NSView wrapped up in an
NSViewRepresentable. It works great. When the
NSView invalidates its intrinsic size,
NSViewRepresentable re-queries the intrinsic size and updates the
I guess someone already filed that enhancement request.