blog.iankulin.com

Posts

git stash

When I was writing the blog post for the last project, I needed the “before” code to paste into the post. I’d committed that code, so a quick way to go back without losing my changes. I hadn’t committed the new code, so there is a super easy way to accomplish this.

git stash

This grabs the code since the last commit and stashes it away, reverting the directory to the last committed version. I was able to copy the code I needed to the blog post, then to go back to my changes:

Project 12 Feedback

As usual, I watch Paul’s solution video, and compare it to mine.

Task 1

This was passing in the predicate as a String. I passed the whole thing, but as I figured out along the way, Paul meant just the operator word. He also added some buttons to test it better, which I didn’t think of till Task 3 - it would have saved me some simulator runs.

Task 2

This was changing to enums. As I mentioned, I am a fan. Meanwhile, my solution was exactly the same as Paul’s, down to where he put the enum.

Project 12 Challenges

Project 12 was a series of code tutorials around developing CoreData concepts rather than a real app, but the challenges are based on a very small app that uses a subview to allow dynamic (ie changeable at runtime) filtering of a list of data. The reason this would be tricky is that the @FetchRequest is a property of a view - and therefore mutable. The trick is to have a subview to build that part of the view, and to pass parameters into it which build the fetchrequest using an underscore.

You Can Take Big Steps When You Feel Safe

Day 58 of #100Days feels like complex topics are being dropped in pretty fast. We tackle one:many data relationships and how to set them up in CoreData, using CoreData constraints and setting a merge policy to manage conflicts, and even the underscore to access the actual property inside a wrapped property struct (needed for dynamic filtering in a view).

I’ve mentioned before that I think Paul Hudson is an excellent teacher, and an example of this is that even though this was a day with a lot of challenging material, I’m not worried. I followed the discussion and tried the code, and more importantly I’m anticipating these new skills will be practiced in the next app, and probably shortly after I’ll be writing an app using them.

git - Rollback to last commit

I’m on Project 12 of the #100Days course, and like a number of earlier “projects” it’s not really a project, but a series of type-along tutorials. Often these have the same format - there’s a base amount of code to provide the setup, then this base is used to try each of the tutorial techniques. At the end of each technique, you delete all the new code you’ve done back to the original setup, and you’re ready for the next one.

Bookworm Feedback

I did so well on this one that it’s not going to make a very interesting post. My first two challenge solutions were pretty much character for character the same - so not much to report.

On the third challenge, there was a minor difference in the display process. I had done this:

let date = book.date ?? Date()
Text(date.formatted(.dateTime.day().month().year()))
    .foregroundColor(.secondary)
    .opacity(date == book.date ? 1 : 0)

But @twostraws went:

if let date = book.date {
    Text(date.formatted(date:.abbreviated, time:.omitted))
}

I agree the if let is neater. I was heading a warning from earlier in the series about avoiding if statements when building views as they can cause the view to have to be destroyed and recreated if it contains different elements (as opposed to a property change - which I don’t fully understand since I also believed those modifier properties where causing the views to be recreated inside other views?), but I don’t know if that’s an issue at all for list elements - I’m guessing not if Paul is doing it this way. I have noticed (when playing with print statements in init methods) that SwiftUI is very good at only recreating the views that need recreated.

Bookworm Challenges

Another set of challenges for a #100DaysofSwiftUI tutorial app. Project 11 was a book tracking app - the big new thing was using CoreData. Here’s the challenges for it.

Right now it’s possible to select no title, author, or genre for books, which causes a problem for the detail view. Please fix this, either by forcing defaults, validating the form, or showing a default picture for unknown genres – you can choose.

@Binding - data between views

In C world, if we want to pass a parameter down into a functional call, and allow the receiving function to change it’s value, we’d pass a pointer to the variable. Something like this:

#include <stdio.h>
#include <stdlib.h>

void increment(int* b) {
    *b=*b+1;
}

int main() {
    int a = 5;
    increment(&a);
    printf("%d", a);
    return 0;
}

// prints '6'

For youngsters, what’s happening is that we’ve set the value of a to 5, then passed the memory address of a into the increment() function. That’s what the @a means.

CoreData and the Preview

I’ve noticed Paul is inclined to ignore the preview and run his code in the simulator to check its operation. That’s valid, but it seems quicker, and reassuring, to see it in the preview as I type.

This led to a small problem with Day 53 that uses CoreData. When I added a student in the preview, it looked like this, and was immediately followed with a crash report.

It ran fine in the simulator, and in the text for Day 53 there was a comment about adding the managed object context to the preview, but without a hint about how.

Cupcake Corner Feedback

As usual, here’s my thoughts comparing my attempts at the challenges to Paul’s. Usually he’s better!

1) Whitespace

The task was to validate the order address properties, not just by checking they are not empty, but also that they don’t just contain spaces. I went the bruteforce route since there was no .isEmptyIncludingWhitespace method.

var hasValidAddress: Bool {
    let trimmedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
    let trimmedStreetAddress = streetAddress.trimmingCharacters(in: .whitespacesAndNewlines)
    let trimmedCity = city.trimmingCharacters(in: .whitespacesAndNewlines)
    let trimmedZip = zip.trimmingCharacters(in: .whitespacesAndNewlines)

    if trimmedName.isEmpty || trimmedStreetAddress.isEmpty || trimmedCity.isEmpty || trimmedZip.isEmpty {
        return false
    }
    return true
}

As soon as Paul mentioned extending String, I facepalmed - of course, just create the method I want on string. Paul’s is a one line extension - neater, and Swiftyier.

Cupcake Corner challenges

Day 52 of #100Days was the challenges to the Cupcake Corner app - an app that allows you to build a one-row order, encode it as JSON and submit it to an API with a URLSession. To allow the order to be passed around, it’s an @ObservedObject which meant that a few extra hoops needed to be jumped through to make it Codable.

1) Whitespace validation

The tutorial app validates the order address by checking that each field is not empty, but it can be fooled by just entering some spaces. The first challenge was to fix that.

Top Four Reasons why @TwoStraws is a Good Teacher

Good Questions

At various points in the 100 Days of SwiftUI course, you get asked sets of questions to check you’ve understood the preceding material. They’re usually presented as two different statements, one of which is true, and the other false. It’s actually a really good technique - the student feels like they’ve got a couple of opportunities to figure it out, plus they are forced to read both statements and think about them. Paul does a similar thing in the Unwrapped app - there, the questions are often presented as “Is this valid Swift code” and the user needs to scan through it all looking for mistakes. It’s checking your understanding, and making you a thoughtful debugger!

Codable when the keys don't match

A common issue when working with JSON that you vacuum up from internet APIs will be that the key names in the JSON don’t match your property names. The JSON de facto standard of using snake_case in key names could be one cause, or perhaps you just take variable naming more seriously than the person who wrote the API.

We saw yesterday how using codable and the JSONEncoder in Swift makes moving between an object/struct in the code and a stringish representation of it simple. With a couple of small changes, we can also deal with the mismatched key/property name issue.

Codable & JSON

If we mark a type with the protocol Codable, we’re specifying that this type has the capability of having it’s properties encoded to some format, and decoded back again.

So far in the #100Days this has been used to write and read data in UserDefaults, and to encode an object to send it as a URLRequest, then receive data back and create a new object from it. It’s a handy, powerful feature baked into Swift that just requires the developer to ensure any types that need this functionality comply with the Encodable and Decodable protocols that make up the Codable.

Git - make all the commits into a single commit

When I’m following a tutorial app, I generally pause and type up the code as I go, and make local commits with appropriate messages. This is almost completely unnecessary, but it seems like a good habit and doesn’t cost me anything - I just tick the box for creating the git when I start the project, then it’s a couple of keystrokes (option-command-C) and I’m done.

Most of the apps have a follow-along portion, then some challenges which involve minor changes to the app. When I get to the challenges I like to throw it up on Github - it’s conceivable it could help someone one day, or at the least, I’m helping to train Microsoft’s AI to write shitty beginner code in exchange for free git server access.

Day 50 - @State vs @Observed again

Way back when, I was unclear about @StateObject and @ObservedObject (here, and here). I still am.

But in today’s tutorial video, Paul clearly says that the @StateObject is for the single place in your app where the object is created, then everywhere else, use @ObservedObject. Trouble is, I just know from the Simple MVVM app I made if you wrap the single instance of the data model with the @ObservedObject, it still works.

Day 47 - Habits App

I’ve been mucking around with the Habits app too long - it’s started to look like procrastination. It already meets the specification, so I’m calling it an MVP and moving on.

This is the first app of mine I’ve loaded onto my phone and started using, and there are a couple of things I’d like to do with it. It currently just lets you specify how many days between an activity repeating - so if you say you should go to the gym every second day, and you complete that activity on Monday, “Gym” will make it’s way to the top of the list on Wednesday. While it’s waiting in the list for Wednesday to come around, it will show the “Due” time as being exactly 48 hours after you last pressed “done” on it. But if the habit you want is to go to the gym after work at 6:00pm that’s when you want it to be due. I’d like that.

Why?

Why do I have to resize this preview window every time I open Xcode?

Updating stored JSON due to a struct change

I mentioned yesterday “I could use a renamed old version of my struct to load the existing data, and convert it across to the new model.”. Since I’ve been testing the app on my phone, and using plausible data, it was going to be painful enough to lose it that I thought I should go through those steps.

First, I make a copy of the old struct, and renamed it with the app version number that used it. No need to bring all the computed properties into this struct, just the bits that get encoded into the JSON.

JSON encode/decode

Screenshop of Habits app

As usual, I’m spending way more time on the apps written from scratch in the 100Days series. The Habit tracking app I’m working on has been good practice, especially of the architecture of the simple list based app.

My version has a couple of refinements I quite like. I’m using a checkmark in a rectangle as the button to mark that activity as done, and I’ve added a nice fade to the checkmark as time goes on to represent the percentage of time from when it is done until it becomes due again.