Swift

Image of Author
October 7, 2023 (last updated September 16, 2024)

Swift is a programming language created by Apple and is primarily for development of Apple ecosystem applications, like on iOS, watchOS, macOS, tvOS, etc.

XCode is the IDE created by Apple for development of Apple ecosystem projects, and is thus well integrated with Swift. It has a useful app preview mode that simulates devices.

First learning Swift reminded me of Kotlin, though I'm not sure why. Perhaps because it a modern OO-friendly language with first-class functional primitives vis-a-vis closures.

File storage in previews, simulators, etc

The preview is what is within XCode. Running a simulator will actually open up the "Simulator" app. Previews are placed in some kind of sandbox that cycles on every preview render. I could see that something was being preserved in the directory I cared about, but it was essentially being wiped on each re-render of the preview. This was not the case for the simulator. It will preserve state changes made to the device, even beyond what I was expecting, e.g., other apps were still on the simulated device (you can wipe it if you want).

Arrays

I cannot find exact documentation on this, but I was struck by this syntax,

let data: Data = ...
let bytes: [UInt8] = [UInt8](data)

From what I understand Data is essentially "just" a [UInt8] array anyways, but if you peruse it's methods there's no obvious way to turn Data into a pure byte array. I think it is the array sequence initializer of [UInt8], and this is possible because of the Sequence Protocol.

XCode Simulator display scaling and positioning issues

I have a problem where the initial iPhone simulators have a large gap between the top of the iPhone simulation and the menu bar for the simulation. This pushed the bottom of the iPhone simulation off the bottom of my computer screen.

The fix for this is in the "Window" drop down menu. For example "Window > Physical Size" with both fix the gap problem I have and resize the whole simulation smaller (at least on my machine). It also has a hotkey "cmd+1". There are a few other hotkey'd scaling options as well.

Using the REPL

To open the swift REPL on the command line type the following,

❯ swift repl
Welcome to Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4).
Type :help for assistance.
  1> import Foundation
  2> UUID()
$R0: Foundation.UUID = 8BF49C91-B76A-40F0-8674-CA5BD40A7F55
  3>

You can import Apple SDKs as well, it seems.

ctrl-D to quit.

Recording audio

https://www.hackingwithswift.com/example-code/media/how-to-record-audio-using-avaudiorecorder

I had to do a lot of trial and error and reading docs and experimenting before I finally got audio record plus playback working. It worked in the preview macro, on the simulator, and cast to a local device.

The biggest piece that was missing for me was AudioSession. You have to set it to some version of record. Here is a snippet of my AudioRecorder class.

@Observable
class AudioRecorder {
    init() {
        let dir = FileManager.default.temporaryDirectory
        let file = UUID().uuidString + ".m4a"
        let filepath = dir.appendingPathComponent(file)
        self.audioUrl = filepath

        let audioSession = AVAudioSession.sharedInstance()
        do {
            try audioSession.setCategory(.playAndRecord, mode: .default, options: .defaultToSpeaker)
            try audioSession.setActive(true)

            let recorder = try AVAudioRecorder(url: self.audioUrl, settings: [ AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue ])
            recorder.prepareToRecord()

            self.recorder = recorder
        } catch {
            print("Failed to initialize recorder and/or audio session: \(error.localizedDescription)")
        }
    }
}

Streaming audio and video

https://developer.apple.com/documentation/avfoundation/avplayer

A player is a controller object that manages the playback and timing of a media asset. Use an instance of AVPlayer to play local and remote file-based media, such as QuickTime movies and MP3 audio files, as well as audiovisual media served using HTTP Live Streaming.

import AVFoundation

@Observable
class AudioPlayer {
    private var player: AVPlayer
    private var isPlaying = false

    init(url: URL) {
        let playerItem = AVPlayerItem(url: url)
        self.player = AVPlayer(playerItem: playerItem)
    }

    func toggle() {
        if (isPlaying) {
            player.pause()
            self.isPlaying = false
        } else {
            player.play()
            self.isPlaying = true
        }
    }
}

Subscripts are pretty cool

Subscripts are essentially an abstraction over element access for collections. It might seem like overkill at first because that would 'only' abstract away accessors for enumerations/enumerable entities like Arrays, Ranges, Dictionarys, etc. But, the abstraction also allows you to implement subscripts for customer classes and structs. It also allows for creative access logic like accessing elements from a two dimensional array, which can itself be abstracted to n-dimensions.

SwiftUI Previews with custom data

There are development assets you can declare which aren't included in final builds. There is WWDC sample code using a pattern of extending a class in the Preview\ Content/ folder which is declared a development asset. But using that sample data in the #Preview macro will cause an archive failure because the archive cannot find the extensions that defined the sample data, since they are a development asset. There is an 2020 stackoverflow post about this issue that is unresolved and I have commented on. The work around that seems to be the most common is wrapping #Preview macros in #if DEBUG ... #endif flags. This is only necessary when you reference development asset data. If you hard code the data within the previews, or define the sample data extensions outside of the development assets, then presumably you can archive successfully.

SwiftUI onDelete

This article, Deleting items using onDelete does a good job at explaining some of the quirks of the .onDelete function, like why you have to use a List and a ForEach. Another quirk is that the provided data to the action: param is an IndexSet, which is a set of integers from the ForEach array that are 'to be deleted'.

return from a preview macro

If you are like me the preview macro is one of the first places you run into a trailing closure with an implicit return.

#Preview {
    YourView()
}

If you want to instantiate some entities to pass in to that preview, your preview will break unless you return from it. This is because the initial preview used an implicit return since it was a single line.

#Preview {
    let note = Note(content: "some content")
    return YourView(note: note)
}

Namespacing

I don't know enough to recommend this, but just noting it's possibility. In the blog post The Power of Namespacing in Swift, the author mentions using enumerations to create logical namespaces. You can use a struct as well. I'm experimenting with the following. In my situation I want a Text struct inside a SwiftUI.View struct. Calling import SwiftUI means I have SwiftUI.Text in scope, which is in conflict with my customer Text struct.

enum Notes {
    struct Text {
        let id: UUID,
        let content: String
    }
}

URLSession: probably use a default session and not a shared session

From the Foundation / URLSession / shared documentation:

In other words, if you’re doing anything with caches, cookies, authentication, or custom networking protocols, you should probably be using a default session instead of the shared session.

Prefer structs over classes

From the TSPL section on comparing structs and classes:

As a general guideline, prefer structures because they’re easier to reason about, and use classes when they’re appropriate or necessary. In practice, this means most of the custom types you define will be structures and enumerations.

Testing

https://github.com/Quick

There is also a first-party swift testing library that was announced at WWDC 24

SwiftData and the Preview macro

This is a subtle thing that can cause a lot of frustration. A lot of work is done on your behalf, and if you get that automagic instantiations out of order, things don't render in preview. This article on How to use SwiftData in SwiftUI Previews helped me better understand what I was doing wrong.

Many-to-many relationship are a fickle beast as well. Easy to get this stuff wrong and stare at full on system crashes for hours. For many-to-many relationship you cannot forget to insert via container.mainContext.insert or else the associations won't persist, and reading 'through' the associations won't work in the test setup. See this article on How to create many-to-many reationships, also by Hacking with Swift, which is quick becoming a favored resource of mine.

SwiftData seems designed for interacting with internet databases?

https://developer.apple.com/documentation/SwiftData

SwiftData has uses beyond persisting locally created content. For example, an app that fetches data from a remote web service might use SwiftData to implement a lightweight caching mechanism and provide limited offline functionality.

The sample code from Maintaining a local copy of server data is the best holistic example of how to leverage SwiftData as a cache of remote data.

SwiftData .modelContainer

You only need one .modelContainer for an entire view hierarchy. If you add more later down in the view hierarchy, that will be a different container. Views attached to the original .modelContainer will not see the other container's data.

Resources