Swift5-7


Dec. 11, 2022

ChatGPT's code writing

room full of monkeys typing at computers - stable diffusion

This week, the internet has been all about ChatGPT , the rather remarkable natural language AI with a very large model. If you’re a twitter user, you were probably amazed, but now eventually tired of seeing examples of it’s output. I’ll add to that with an example of a SwifUI CoreData based todo app it wrote for me from a single sentence prompt below. Rather than look at other people’s examples you should definitely go and play with it yourself - it is very impressive. Along with the image based AI’s it’s made 2022 into a historical year for AI.

Dec. 10, 2022

Sharing is caring

Continuing on with the demo project from yesterday, in which we used the ImageRenderer class to turn a view into an image, today we want to let the user share it somehow.

Typically, apps have a button using the square.and.arrow.up SF Symbol to share something from the current screen. It’s probably not an accident that it’s literally the first symbol in the app.

Pressing it generally opens the “share sheet” which has options for opening whatever is being shared in another app, printing it, saving it to photos, or whatever.

Dec. 9, 2022

Clean code

young woman cleaning a computer, painting - stable diffusion

I’ve been listening to the latest episode of the Empower Apps podcast, this one with Jill Scott talking about “Humane” development - in the sense of being humane to whoever (probably you) is going to be reading this code in the future. It helped me clarify my thoughts about a couple of things.

None of these ideas are particularly new or groundbreaking, and although I think of them as my personal style, they are very common, and in Swift could be regarded as part of the culture. Some of these concepts support each other, some represent a trade off between two opposing ideas that require us to make a choice.

Dec. 8, 2022

ImageRenderer()

ImageRenderer () is a SwiftUI class that creates an image from a view. You just initialize it with the view, then extract a cgImage (Core Graphics) or uiImage that can be cast to a SwiftUI Image.

I’ll need a view to work with, so here it is; a crude version of my behaviour ticket.

struct TicketView: View {
    var body: some View {
        ZStack {
            Color(.cyan)
                .frame(width: 300, height: 350)
            VStack {
                Text("Fred Bloggs")
                    .font(.largeTitle)
                Text("")
                HStack {
                    Text("Putting rubbish in the bin")
                    Image(systemName: "trash")
                }
                .foregroundColor(.purple)
                Text("")
                Text("Green Faction")
                Text("")
                Text("")
                Text("\(Date().formatted())")
            }
        }
    }
}

Here it is, with a couple of buttons underneath:

Nov. 30, 2022

Regex to split a string with two different characters

young woman cutting string, painting by - StableDiffusion

I’m working on the behaviour tickets app, and wanted a visually functional version to share with stakeholders this week to get some feedback. As usual in this situation, I’m pressed for time so feeling the pressure to take some liberties with code quality that I’ll come back and fix one day.

In a salient lesson of why that’s usually a bad idea, I’ve ended up googling to try and understand regex instead of writing code.

Nov. 26, 2022

FriendFace 61 Feedback

As usual after a challenge, I compare my efforts to Paul’s model solution. Just to quickly recap the app, it sucks up some data (Users who have multiple friends) and displays it. The change in this challenge was to convert it to add that data to a Core Data store so that if a future network error prevented accessing new data, it could still display the old.

Merge Policy

The first difference is that Paul adds a merge policy. A Merge policy tells Core how to deal with any constraints defined in the data model. In this app, I’d defined the CachedUser.id as a constraint. The purpose of this is that under normal conditions the app would be picking up mostly the same data each time it started up. We don’t want scabs of duplicate data, so constraining users based on their unique id is smart.

Nov. 25, 2022

61 Done

I think I’ve finally completed the minimum work for Day 61 of #100DaysOfSwiftUI . The task was to suck up some data in JSON, decode it. back it up into a Core Data graph then display the data from the Core Data.

I got stuck on dealing with the one:many relationship and had to revisit that from a different source to get my head around it, after that it was straightforward. Other small problem I ran into was that I created the id in the CachedUser as a UUID from (newly formed) habit. Then when I went to copy it in from the JSON version, it wouldn’t let me. When I realised my mistake and changed it in the data model, I still could not figure out why it wasn’t working - but of course I hadn’t regenerated the code for the ManagedObject. I just had to change the property type in the already generated code from UUID to string and I was back in business.

Nov. 24, 2022

Core Data basics – Part Three

If you’re just stumbling across this, perhaps have a look at Part 1 where I layout a simple master/detail app with the data held as arrays of structs, and Part 2 where I convert that into the simplest possible Core Data version. In this post, I’m going to add the mechanics for the one:many relationship - Each Garden can be associated with multiple Plants.

I should also mention I figured out some of this with help from this video from Jonathan Rasmusson .

Nov. 23, 2022

Core Data basics - Part Two

Yesterday I roughed out a master/detail app with a list of gardens, and for each garden a detail screen including some plants. It used arrays of structs for the data. Today I’m going to convert that app to use Core Data, and explain my understanding of each step. This won’t be the entire app - I’m going to include the plants in my data structure, but not actually use them in this version. I’ll save that 1:many relationship stuff for another post.

Nov. 22, 2022

Core Data basics - Part One

To help me get clear on the Core Data basics (so I can master one of the #100Days challenges ), I’ll write a simple master/detail app with arrays of structs, then convert it to Core Data listing out of the steps. Almost everything I know about Core Data, I learned from Paul Hudson’s 100 Days of SwiftUI course - of which I’m up to day 61. So shout out to him. I highly recommend that course, and most of the code you’ll see in this post is either inspired by, or directly copied from 100 Days, except of course the errors - those are mine. This post - Part One - just describes the app and shows the struct/array version.

Nov. 21, 2022

Something weird 'append

I’m noodling around making sure I understand how Core Data works. Thought I’d start with a master/detail app with an array of structs, then replicate it in a Core Data implementation. I’m using an array of this struct for my data:

struct Garden {
    var id = UUID()
    var name = ""
    var address = ""
    var plants: [Plant] = []
}

And I thought this code to load up some sample data was pretty sweet.

Nov. 16, 2022

Console spam - No wall clock alignment

When I was working on the Day 60 app, I noticed I kept getting a message in the console “No wall clock alignment provided at SwiftUI/ResolvableStringAttribute.swift:86” every time I went into the detail view. Via elimination by commenting bits out, I’ve narrowed it down to a date formatting call. Here is the code to reproduce it in Xcode Version 14.0.1, Swift 5.7.0.127.4

struct ContentView: View {
    var body: some View {
        Text("Date: \(Date(), style: .date)")
    }
}

It’s to do with the style - if I change it to .time or .relative the message does not appear.

Nov. 14, 2022

FriendFace

The Day 60 Milestone is a demo app that vacuums up some JSON and displays it in a list in a NavigationView that links to a details page. Nothing super strenuous, the steps were something like this:

  1. Download the JSON and have a look at the structure. Firefox has a simple JSON viewer built in, so it was straightforward to see this is an array of users, which along with some (mostly string) properties contains an array of tag strings, and another array of friends.

Nov. 13, 2022

Profile Photo Rabbit Hole

down the rabbit hole, children’s book illustration - stable diffusion

I’m on day 60 of #100Days , and have just wasted most of an evening’s coding time going down a rabit hole I didn’t need to. The app for this challenge is called “FriendFace” and is pretty straightforward: download a heap of JSON which is an array of users. Show it in a list that can be clicked through to see the details of that user.

Nov. 10, 2022

Project 12 Challenges

taylor swift with ed sheeran and adel, watercolor painting - Stable Diffusion

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.

Nov. 7, 2022

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:

Nov. 6, 2022

Bookworm Challenges

cartoon worm on a book - Stable Diffusion

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.

Nov. 5, 2022

@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.

Nov. 4, 2022

CoreData and the Preview

looking through a keyhole to a room, diagram, colorful - Stable Diffusion

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.

Nov. 3, 2022

Cupcake Corner Feedback

cute cupcake, cartoon drawing - Stable Diffusion

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.