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