Or how not planning ahead bites me in the ass once again

On last week’s episode, we were wondering if 13,000 entries was too many. So what I wanted to do was to consolidate them as there are several versions of piece 3001 for instance. Each color having its own unique code as well. I altered the Piece model to hold an array of color IDs. Then, if a piece already existed with the same shape ID, I would instead add to that piece’s color ID array rather than create a new Piece.

The waiting’s the hardest part

More like 30 seconds, but still, not great as loading the pieces from the JSON file is the first thing we do. Who would use an app like that? Click the icon, wait half a minute, now you can use the thing. No.

But, you know, I was doing things simply. Looking for a piece in a list, adding it if not finding it, modifying if finding it. It’s that repeated search that was killing it. Walking the list and comparing shape IDs until we find one. It would be easier if we didn’t have to do that. So, I go to my good ol friend Dictionary. Lookup is going to be much faster.

The good, the bad, the ugly

The ugly

Once I decided to use a dictionary, I changed the type of Piece collection to the proper type. This caused a fair amount of chaos in the app itself, because, like I said earlier, I didn’t plan too far ahead. All of my previews and lists and iterators used arrays and array references. I had to make a fair number of changes across several views to make them dictionary friendly. That was ugly.

The good

However, Swift’s dictionaries are pretty class. Or maybe not the dictionary itself, but things you can do. I’ve done a fair amount of work in python and C#. I’m used to working with their dictionary structures. I was looking for how to do those same things in Swift. Basically trying to write C# or python in Swift. But writing Swift in Swift is much cleaner and efficient.

Subscripting a dictionary returns an Optional. Optionals are special and there are all sorts of rules about them. Wrapping and unwrapping, when to do it, how to do it, etc. One of these ways is designed exactly for doing exactly what I want.

if let found = myDictionary[myKey] {
    //Do stuff with found
}
else {
    //Key was not in dictionary. Pretend we're creating and adding a new object
    let newObject = Object()
    myDictionary.updateValue(newObject, forKey: myKey)
}

Basically you don’t have to explicitly check to see if a key is present in the dictionary before trying to access it. Try to access it and if you can, it will let you use it, if you can’t, you can handle that as well. This idiom seems to be used a lot in Swift when dealing with Optionals. Don’t hate it. It’s good.

The bad

Dictionaries don’t seem to like SwiftUI too much. Trying to use a dictionary as a source for a List view or ForEach builder. You have to coax them into shape by sorting them. I’ll probably eventually add a sorted dictionary on the overall model just so I don’t have to sort it all over the place. I accept that the dictionary order is not guaranteed.

Solve a problem only to do it all over again

Now the pieces were loading at a decent clip again. Start the app, see the screen. Now it’s time to introduce the pictures. I already had issues with this as 13,000 images in the Project Navigator is a disaster. Adding them to the Assets.xcassets file/folder is yet another disaster. Eventually, I decided to cram them all in a zip file and include that in the assets.

Now, before I can even find out how badly I’m going to murder my performance, I need to actually show a picture. SwiftUI’s Image view takes an asset name as an argument and does all the work of extracting it from the Bundle and blah blah blah. But our image is in a file in the Bundle. We need to first take it out.

That’s not too much of a problem. There’s a package out there ZIPFoundation which handles all of the extraction and can access entries individually, so you don’t have to unzip the whole file all at once. The real issue here is that you’re going to be holding a Data object with your image data in it. And SwiftUI’s Image view isn’t down to clown with Data. To get this Data object to show up, you’re going to have to import UIKit, create an UIImage with the Data object, then create an Image with that UIImage. Annoying, but, you know, it works.

Second verse, same as the first

So, I loaded the image data as I loaded the piece list. And I don’t even know how long this was taking, because it was taking so long.

Obviously, loading all the pictures at once isn’t going to cut it. But to be honest, I don’t even need all of the pictures. I just need the pictures of things you’ve actually looked at. So all I have to do is just load pictures as they are needed. Now, I’ll be accessing the Zip file every time I want any particular image, but this bit of overhead is insignificant for the number of times I’m doing this.

I’ve already created a view explicitly for the display of individual pieces. And that, so far, is the only place where the image is shown. So if I could load the picture when that view is loaded, I’m good, right? Just stick the load code in the init and we’ll be good.

Chugga chugga, choo choo

Yeah, we’ll be good, if you like your list jerking about as it waits to load all of these images while it scrolls. Now, once the image is loaded, it scrolls like a champ. But the first time an image comes up, forget it. What would be better would be to just kind of load these images in the background and swap them in on the fly.

Now, SwiftUI does have an AsyncImage, which does all of this. If you’re loading from an URL. Which we’re not. So that was not available to us that I could see. But Swift does do async/await stuff. So I can just declare my image loader async, create a Task, and hear a bunch of complaints about mutating self in init. But I can’t just throw code in the view builders. It “doesn’t conform to View”.

This is where I learn that View has a function called task that does exactly what I want. I also move the loading code into the Piece itself. Make the Piece variable an @ObservableObject and once the data finishes loading, the View will refresh itself.

More issues

Now, the image used is technically the image of the first color of the piece in the JSON file. There’s probably another way to do this. At least make the representative image selectable or something.

It’s also still a beast in the Picker view. I’m likely going to have to abandon that completely and do something else to select pieces. There are other things I’m going to have to manage. Like defaulting to the number keyboard for certain sections, etc.

But it finally feels like I’ve written some actual Swift. For the most part, it’s been messing with SwiftUI and View Builders. It is kind of interesting what you can do with just that much though.

By toast