iOS Widgets, Loops, and Memory

So, you’re building a widget for your iOS application. Did you know that they can stop working properly if they use up too much memory? It’s true!

I found myself unknowingly in a memory usage hole. The system will take an archived snapshot of your view to display for a given point in time. However, apparently it is possible to have it fail to work properly if your view uses up too much memory when producing the snapshot.

Imagine you have a data source with a list of items you want to display.

let widgetData = yourDataSource.getLatestData()
print(widgetData.count) // 20

You want to display a list of your data items in your widget. But because you don’t know how much space you have to use, you want to show the maximum number of items that can fit. So you think you’re clever and try something like this in your Widget’s view.

func viewForData(_ yourData: YourDataType) -> some View { ... }

@ViewBuilder
func buildDataStack(count: Int) -> some View {
VStack {
ForEach(entry.widgetData.prefix(count)) { yourData in
viewForData(yourData)
}
}
}

var body: some View {
VStack {
ViewThatFits(in: .vertical) {
Group {
ForEach((1..<20).enumerated().reversed(), id: \.element) { _, element in
buildDataStack(count: element)
}
}
}
}
}

Well, let’s see what we have here. First, we’re defining a function that returns a view for your data model. Then we have a function that builds a stack of those views depending on the count provided. And then, in our view body, we’re just looping through from 20 down to 1 to see the largest group of items that’ll appear in our widget. That sounds like a neat solution to our initial problem!

Except that, because of the memory used in creating all of those iterations, we’ve now run out of memory for our Widget and it will fail to produce a valid snapshot! We have essentially _broken_ our widget!

If you’re going to try something similar to the above, make sure you do it with a smaller range of values. Trying to build 20 separate lists to see what fits will likely cause issues. Trying 5 times may be more successful.