With the SwiftUI changes that were added in iOS 15 I started directing a lot of my attention to Lists. I quickly hit a problem.
How do you dynamically create an unknown number of sections in a SwiftUI List, but give the user the ability to move around items in each section? I couldn’t find any answers from my searches. When I started Tweeting about it several iOS developers mentioned that they haven’t been able to figure it out. That was one of my motivations in creating this blog. I’ll dive into the details and how I came to a solution.
In SwiftUI it’s easy to enable the ability for users to move items in a List. Here’s an example I took from Paul Hudson’s site. If you have a List based on a simply array then all you have to do is add the .onMove modifier to your ForEach and pass it your closure that does the move. In this case it’s a function that uses the “move” function from the source array (myList in this case).
With that the user can hit the “Edit” button and reorder items in the List. If you have two source arrays you can create another move function for moving items in that array just like the other one. Not great but it gets the job done.
What about the scenario where you’re dynamically building a List with Sections? My source array is of Objects that contains arrays of other Objects. Since my “move” function in the simple example is explicitly referencing the source array how do I this when I have multiple Sections? How do you let the user move elements around in each section?
My first thought was to create a function that had an inout parameter and I’d modify the array that was passed in. But then I remembered that the .onMove modifier is expecting a “move” function with specific parameters. I took a break and was fixing dinner when it hit me. In the simple example the “move” function was referencing the source array. But if I put that “move” function as a method inside of each source object couldn’t that also reference its source array and accomplish the same thing?
Here’s an example of the code I tried. Please note that I would never smush all my code together like this. This is for the purposes of the blog post only. The key is that I moved my “fancyMove” function that does the move inside of my OuterData class. The code loops through an array of OuterData objects to create the List sections. The .onMove modifier is passed the fancyMove function of each individual object.
When you’re in edit mode you can move around individual items in a section, but you cannot move items out of their section into another section (which is exactly what I wanted). Unfortunately that’s when I realized that my changes were reverted when I hit the “Done” button to exit Edit mode.
Software develpment can be such a roller coaster of emotions. I was so excited that I had come up with this solution on my own. I felt like I had a really good grasp of the concepts and had applied that to a solution. It was such a let down when I realized that it didn’t work. Little did I know that I was two lines away from success.
It turns out all of the changes I came up with work just fine. I just needed to add two more lines of code to have my changes to a List applied when exiting Edit mode. The new Self._printChanges() that I saw Luca Bernardi mention was added to SwiftUI in iOS 15 was really helpful.
SwiftUI has a new, pretty cool, **debugging** utility to help you understand what is causing a view to be reevaluated.— Luca Bernardi (@luka_bernardi) June 7, 2021
Call `Self._printChanges()` inside the body of a view to print out the changes that have triggered the view update.
When I dragged an item (Epcot) at the beginning of a section to the end I saw that in the debugging output. However, this was reverted when I exited Edit mode.
In searching for answers I stumbled across this blog post. What he described isn’t at all what I was experiencing. I had no trouble going in and out of Edit mode and I’m not presenting a new View. I decided to try his code change since it was only two lines. The first line is to create a @State var of type EditMode and the second line is to tie that variable to the environment.
Going back to the output of Self._printChanges() shows a key difference. SwiftUI is reporting that it detected a change in the environment to .editMode!
With these changes we can have a dynamically created List with multiple sections. The user can rearrange the sections of each list as they please and the changes are kept when they leave Edit mode!
If this post was helpful to you I’d love to hear about it! Find me on Twitter.