I learned several things with this one!
Intro
I have a feeling that running in Release mode is something I should have known how to do already, but I didn’t. My hope is that this helps anyone that is in a similar situation.
ViewThatFits
I recently spotted an interesting post called “Accessibility That Fits” by Soroush Khanlou on iOS Dev Weekly. His post described how he’s using SwiftUI’s ViewThatFits to have his date strings adjust to a format that best fits in the space available. I use ViewThatFits extensively in my widgets, but I never though of using it in this manner.
I immediately thought of this part of Please Don’t Rain. On an iPad in fullscreen mode there’s plenty of room to also show the rain amount and/or snow amount. But in scenarios where there’s not much space to work with, on an iPad in Slide Over for example, then that’s not possible with several font sizes. What I’ve been doing in those smaller space scenarios is forcing only the percent chance column to be shown while leaving out the rain and snow amount columns.
This made me realize that if I adjusted the dates I might be able to squeeze in some precipitation amounts in some scenarios.
Initially I wasn’t sure how do approach this with ViewThatFits. After taking some time to think about it (I mean that literally, not asking AI) I came up with this approach. First the app would try to display all of the precipitation data using all of the various date formats. If that didn’t work then try all the date formats with the precipitation amounts not shown. By the way, both of the enums used with ForEach conform to Identifiable.
ViewThatFits(in: .horizontal) {
ForEach(NearbyDisplayChoices.allCases) { oneCase in
ForEach(AlternativeDateFormatStyle.preferSmaller) { oneFormat in
NearbyDays(
processedDate: chosenDate,
foundLocation: locationDetails,
dayInfo: dayDetails,
allDays: allDays,
demoStatus: demoStatus,
whatToShow: oneCase,
alternateDateFormat: oneFormat,
theSource: .regularApp
)
}
}
}
As you can see below, it worked great. With this font size there’s room to show the rain amount if we change the month to be a digit.
Ship It
After some testing (all on device) everything seemed great and I was thinking I was completely done with the feature. As I always do I made a TestFlight build for a little more testing. I was shocked that when I launched the TestFlight app on my device it crashed instantly. Since it was a TestFlight I was able to submit feedback about the crash, which made it show up in App Store Connect.
According to it the problem was in SizeFittingState.applyChildren(selectLast:to:), which sure sounds related to my ViewThatFits changes.
Honestly I wasn’t sure what to think or how to proceed. Initially I thought that I must have been overloading ViewThatFits with too many choices. But that didn’t make sense because when I built locally my changes were running great on my device.
I posted about this on Bluesky and Mastodon and got some interesting replies.
Release Mode vs Debug Mode
Daniel Saidi and Duncan Babbage both suggested trying to run the app as a Release build locally instead of in Debug mode. I had to look up how to do this because it’s something that I’ve never done before.
Under Product, Scheme there’s an option for “Edit Scheme” where you can change the Build Configuration from Debug to Release.
The Answer Revealed
This worked better than I could have hoped for. The app refused to launch and displayed an error with so much valuable data.
From this one message I now knew all these:
- I had confirmation it was indeed a ViewThatFits problem.
- The problem was that a View ID was getting reused.
- The problem ID was the first case in my enum for date formats, which must have been the second iteration of the outer loop.
This time I did ask AI for help and the solution it offered worked. Both of my enums already conformed to Identifiable. Therefore adding a View ID that was a combination of both of their current values would guarantee that each value for the ID was unique.
ViewThatFits(in: .horizontal) {
ForEach(NearbyDisplayChoices.allCases) { oneCase in
ForEach(AlternativeDateFormatStyle.preferSmaller) { oneFormat in
NearbyDays(
processedDate: chosenDate,
foundLocation: locationDetails,
dayInfo: dayDetails,
allDays: allDays,
demoStatus: demoStatus,
whatToShow: oneCase,
alternateDateFormat: oneFormat,
theSource: .regularApp
)
// HUGE BUG FIX. view ids must be unique.
// Error only showed up in release mode.
.id("\(oneCase.id)_\(oneFormat.id)")
}
}
}
It felt very strange that Release mode would give me so much information and refuse to run while Debug mode acted as if there was no problem.
But Why?
Both Duncan and Vincent Friedrich thought this might be related to a recent change where Xcode 16.0 will implicitly wrap some Views in AnyView. Here’s what the Xcode 16.2 release notes say:
Fixed: In Xcode 16.0, unoptimized debug builds implicitly wrap usages of SwiftUI some View types (such as the return type from body) in AnyView. This is done to allow SwiftUI Previews to dynamically replace the implementation of methods like body without building separate copies of the entire project.
Alternatively, there is a new build setting SWIFT_ENABLE_OPAQUE_TYPE_ERASURE that controls the implicit wrapping of views in AnyView. To get fast SwiftUI previews, the setting defaults to YES, but can be set to NO if you want to experiment with how this affects debug-time performance in specific targets in your project. This will have no effect on release builds, which is where performance should be accurately evaluated. Xcode 16.2 adds experimental support for disabling the build setting while retaining preview usage, though preview performance will degrade significantly. (138952991)
Vincent mentions this in his Common SwiftUI Pitfalls blog post and suggested that I try setting the build setting SWIFT_ENABLE_OPAQUE_TYPE_ERASURE to NO to stop the wrapping in AnyView from happening and then running the app in Debug mode. Sure enough, when I did that the app acted just like it had in Release mode.
I decided to file feedback FB16413376 about this to let Apple know that this situation can occur.
Success
I was this close to thinking this was a bad idea and removing this new feature. I got to learn something new as I explored this fix. Hopefully this post will help someone else.
Big thanks to Daniel, Duncan, and Vincent!
Contact
If this post was helpful to you I’d love to hear about it! I’m @chriswu.com on Bluesky, @MuseumShuffle@mastodon.social on Mastodon, and my email is museumshuffle at gmail dot com.
iOS Dev Happy Hour
Also, whether you’re interested in becoming an iOS developer or you’re a veteran of the App Store, iOS Dev Happy Hour is a great place to meet and connect with other iOS Developers. Join us at our monthly event! I’m one of the organizers and would be happy to welcome you.
Social Media:
- Bluesky - @iOSDevHappyHour.com
- Mastodon - @iOSDevHappyHour@mastodon.iosdevhappyhour.com
- Threads - @iOSDevHappyHour
- Twitter - @iOSDevHappyHour