iOS 16 text view breakage

September 13 2022 by Jeff Johnson

In the past, I've written about my adventures with UITextView. At present, I'm writing about a new problem I have with UITextView in iOS 16: it crashes while editing, which is one of the worst problems possible! Ironically, the crash is caused by a workaround for another, less serious problem: NSLayoutManager defaultAttachmentScaling doesn't exist on iOS (only on macOS). In order to scale down images proportionally to fit in the text container width, I had to write an NSTextAttachment subclass.

@interface JJTextAttachment : NSTextAttachment
@end
@implementation JJTextAttachment
-(CGRect) attachmentBoundsForTextContainer:(NSTextContainer *)container proposedLineFragment:(CGRect)fragment glyphPosition:(CGPoint)position characterIndex:(NSUInteger)characterIndex
{
	CGRect attachmentBounds = [super attachmentBoundsForTextContainer:container proposedLineFragment:fragment glyphPosition:position characterIndex:characterIndex];
	CGFloat attachmentWidth = CGRectGetWidth( attachmentBounds );
	CGFloat fragmentWidth = CGRectGetWidth( fragment );
	CGFloat padding = [container lineFragmentPadding];
	fragmentWidth -= padding + padding;
	if ( fragmentWidth > 0.0 && attachmentWidth > fragmentWidth )
	{
		attachmentBounds.size.height *= fragmentWidth / attachmentWidth;
		attachmentBounds.size.width = fragmentWidth;
		
	}
	return attachmentBounds;
}
@end

My workaround worked great for five years… until this year, when it came to a crashing halt. You can download a sample Xcode project that works fine on iOS 15 but crashes on iOS 16. The crash is in the method NSTextContentStorage locationFromLocation:withOffset: and the reason is "received invalid location (null)". So I filed a bug (FB11067936) about the crash with Apple's Feedback Assistant on August 4, and I received a response from Apple engineering a few days later.

Thanks for your feedback. This looks like an issue that you need to resolve.

New in iOS 16, the default text engine for UITextView is TextKit 2.

This code is using a TextKit 1 API for implementing the custom text attachment (attachmentBoundsForTextContainer:proposedLineFragment:glyphPosition:characterIndex:), which is not compatible with TextKit 2.

There are a couple different options here.

To restore the previous behavior and avoid the crash, you can explicitly set the UITextView to use TextKit 1 and continue to use the TextKit 1 API. There are multiple ways to do this, based on the app submitted with this report, the easiest way would be to set the Interface Builder “Text Layout” option to “TextKit 1” (instead of “Default”).

Another option is to migrate to the view-based text attachments with the TextKit 2 engine.

For more information on TextKit 1/TextKit 2 compatibility and view-based text attachments, please refer to the WWDC22 video “What’s new in TextKit and text views” and the accompanying sample code, which can be found here: https://developer.apple.com/videos/play/wwdc2022/10090/

This response from Apple was informative, yet dissatisfying. Apple put the burden on me to fix a crash that they caused, through no fault of my own. Under normal circumstances I would just grumble and fix it, but my circumstances here aren't normal. The crash is occurring in an app that I discontinued and removed from the App Store last year.

Why do I care about the crash if the app is discontinued? I care about the customers who previously purchased the app and may still be using it. In fact, I'm still using the app myself! I decided to remove it from sale in the App Store because the sales were low, the support burden was more than the sales were worth, I had no plans to ever update the app, and Apple broke an important feature of the Mac version. Despite my personal fondness for the app, I didn't feel good about selling it anymore (or making it free, which is the worst of all worlds).

In order to fix the crash, I would have to update the source code, deal with any other breakage caused by the new iOS SDK, test the app on various iOS versions and devices, then publish it again on the App Store so that previous customers can install the update with a fix for the crash. (And I'd have to figure out a way to avoid new purchases.) I really don't want to go through all of that crap.

TextKit 2 is Apple's new text layout engine that they introduced in iOS 15 and macOS 12. In iOS 16 and macOS 13, Apple automatically opted in UITextView to TextKit 2, for both Apple's apps and third-party apps. The developer must explicitly opt out of TextKit 2 to use the old TextKit 1 behavior. Apple also decided not to fix the TextKit 2 crash that I reported. I'm not sure what I'm going to do about the crash, but Apple apparently doesn't care about binary compatibility on iOS, and Apple's actions and decisions put third-party developers in a difficult situation, which makes me angry.

Don't make me angry. You wouldn't like me when I'm angry.

Addendum
A reader of this blog post mentioned something that I forgot to mention: Apple's typical way of preserving binary compatibility is to opt in an app to new features only if it's compiled with the new SDK. This would have solved my problem, because my discontinued app is of course not compiled with the iOS 16 SDK.

Addendum September 29 2022
Good news, everyone! This bug is now fixed in the iOS 16.1 beta build 20B5056e if your app is compiled with the iOS 15 SDK or earlier.

If I do say so myself, this demonstrates the power of blogging, and of not taking "works as designed" as an answer.

Jeff Johnson (My apps, PayPal.Me)