Sometimes, the best way to achieve the impossible is to not believe it was impossible in the first place.
For the longest time, I have struggled with implementing this very specific implementation of a List View wrapped inside a ScrollView, in SwiftUI. I wanted to be able to recreate the Reminders App interface in my app, Linkeeper. But, as soon as I wrapped the List in a ScrollView, it would instantly disappear. I ended up settling with a compromise that made only half of my view scrollable, but this wasn’t a pleasant experience and would end up limiting the visible content in certain orientations. I still shipped it anyways, but I kept looking for a better solution. Every few months, for over a year, I’ve been going into that code and trying to find a way to make the entire View scrollable, but I haven’t been able to do anything about it- until today.
Just as I was about to look into the world of UIKit for me to be implement this feature, I made a breakthrough within SwiftUI itself. To understand the solution to this problem, it’s important to understand how a List works.
A List in SwiftUI is scrollable by itself, and is designed to be a full fledged view that takes up all available space. The list, by default, does not have a fixed size, which prevents it from being rendered inside the ScrollView.
One solution that people have often suggested is, using a custom implementation of a List using a simple ForEach loop. However, this comes with a huge drawback. You can no longer take advantage of the native UI components that come prebuilt with SwiftUI, and neither can you use SwiftUI modifiers such as .onDrag, .onDelete, or .swipeActions. These are some very real considerations to be made, since rebuilding a List component can be a waste of perhaps the most important resource that we have as developers: time.
Using modifiers like .scaledToFit on the List works, but it leads to there being two scrollable views inside one another, causing there to be two nested scroll targets, which is far from a pleasant experience.
Well then, what is the ideal solution?
The solution turns out to be rather simple, and requires only a few lines of code. I’m guessing your code looks something like this, right now.
Well, let’s start off by wrapping it in a ScrollView.
As expected, the List will disappear. Now, to limit the size while also ensuring the list doesn’t turn into another scrollable view, we need to limit it to exactly the size of the content inside. As it turns out, more often than not, each element inside your list is likely to have a fixed size. So, we can add a frame with a size of the count of elements inside your List, multiplied by the height of each element in your list. In my case, the height of each element was 65px. You can use GeometryReader to find the exact height of each element, or use trial and error to estimate the right value. Keep some extra headroom on top of the actual height for a better experience. Additionally, we need to add an extra buffer of 200px if there are fewer than 4 elements to ensure better results. Implementing this limit, your code should look something like this:
Not only does this method of finding the height make sure that the List is no longer an infinitely expanding view, but it also ensures that the List is always large enough to make sure the List itself isn’t a scrollable view.
Optionally, if you want to use the Inset Grouped style for list, you will have to add this background color to your ScrollView to maintain continuity throughout the interface.
And that’s all you need. Here’s a before and after video, in my app, Linkeeper. (The phone on the left is the improved implementation, and the one on the right is the older one.)
Hopefully, you now have a better implementation of a List embedded inside a ScrollView, and you've built some intuition about how a List works for you to solve future problems. This is one of the few times where I've come up with a solution to a SwiftUI problem entirely on my own, with basically zero online assistance, and I'm kinda proud of myself for doing that. Breaking out of the limitations of SwiftUI can allow developers to create beautiful experiences for their users, and this is a great example of doing so.
Comments