Categories
Meta

A Good Old-Fashioned Internet Rabbit Hole

Prevailing wisdom suggests any chunks of time spent online will be steeped in outrage, anger and division. I recently spent a bit of time online and experienced none of the above. As an added bonus I learned quite a few apparently unrelated facts.

When is a condominium not a condominium? What is the email address for the Austrian Consul-General in BC? And what country governs Pheasant Island?

These are just some of the questions whose answers I learned on my recent random walk. And if you manage to read to the end of this post, you too will learn the answers to these questions and so much more…

It all started when I wanted to find out if there were any diplomatic offices in the building at 800 Pender. (But I’ll save that story for another day.) It turns out the BC Provincial Government keeps a comprehensive list of all consular offices in the province. Well done BC Government.

It turned out there are no consular offices at 800 Pender, but holy cow, the email addresses were all over the map. To be fair, about half of them were explicitly affiliated with the home country government. eg, El Salvador

Of the remainder, they seemed to fall into two categories. In the first category, the email seemed to be the personal email of the consul general, using one of the usual providers (eg. gmail, outlook, shaw.ca etc.) Well done Jamaica scoring a free sfu email address.

The third category is email addresses associated with website that is to some degree dedicated to the consul office. eg Bosnia and Herzegovina (BiH).

This got me wondering, what do I see if I go to bcbih.com? It’s a pretty snazzy website, with sections for Tourism, the CV of the Consul, investing in BiH, and investing in RS… Wait, what the heck is RS? Oh, the Republic of Srpska. Wait, what the heck is the Republic of Srpska, and who stole all their vowels?!

It turns out (according to Wikipedia) Republic of Srpska (RS) is an entity within BiH. It further turns out BiH contains 2 entities of roughly the same geographical area:

  • Republic of Srpska (primarily populated by ethnic Serbs)
  • Federation of Bosnia and Herzegovina (primarily populated by Bosniaks, and to a lesser extent Croats)

I was definitely not aware that BiH was divided geographically and ethnically in this way. If somebody told me Bosnia and Herzegovina was composed of two approximately same-sized entities, I’d have guessed one was Bosnia, the other Herzegovina. Wrong! Bosnia and Herzegovina is composed of Srpska and Bosnia Herzegovina. I had so many questions here, but I got distracted by Brčko. (pink in the map above)

It turns out Brčko is a condominium. Wait, what? According to Wikipedia, this flavour of condominium is defined as: a territory … in or over which multiple sovereign powers formally agree to share equal dominium … and exercise their rights jointly, without dividing it into “national” zones.

It’s like joint custody, but for countries instead of parents. Antarctica and Post-WWII Germany are two high profile examples of condominia. Wikipedia includes an impressive list of the current condominia. The one that caught my eye was Pheasant Island.

Pheasant Island is near the mouth of a river (Bidasoa) that defines the border of France and Spain. According to Wikipedia, Pheasant Island became a condominium when the Treaty of the Pyrenees was signed in 1659. According to the treaty the island lives with Spain from 1 February until 31 July. For the remainder of the year (1 August until 31 January) it lives with papa France.

In a world where things seem fairly firmly bolted down, geopolitically, I love the fact that there are quirky things like islands that switch from one country to another every six months.

Again, according to Wikipedia “there are no pheasants on Pheasant Island.” I wonder if there are any condos…

Categories
Software

A Simple Way to Conform to Identifiable Without adding an ID to a Legacy Data Model Object

As the title suggests, I have a mature data model, and I want to add conformance to Identifiable but (for backwards compatibility reasons) not actually add id as an instance variable

My model object in question already implements Hashable, so plan A was to use the hashValue generate id. Sadly hashValue is an Int and id needs to be a UUID. So how does one convert an Int to a UUID?

As is often the case there was a discussion on StackOverflow offering alternatives. The option that leapt out at me introduced to Swift language options I had never used before.

  1. withUnsafeBytes
  2. load(fromByteOffset: as:)

withUnsafeBytes is an array ‘operator’ that will (I believe) iterate over all the bytes in the array. load will pour the bytes into a new object whose type is specified by the as: parameter. As somebody who has done a small amount of writing c and ZERO c++ this code feels very foreign to me.

I chose to solve my problems with two pieces of code. First I extended Int like this:

extension Int {
    public var asID: UUID {
        let vals: [Int64] = [Int64(self), 0]
        return vals.withUnsafeBytes { $0.load(as: UUID.self) }
    }
}

I then added Identifiable conformance like this:

extension Thread: Identifiable {
    public var id: UUID {
        return self.hashValue.asID
    }
}

Solving this problem reminded me just how much complexity there is ‘under the hood’ in almost any code we write. I find it amazing that:

  1. There are smart people who are well versed in the (obscure?) corners of the software engineering information domain.
  2. By invoking an appropriate combination of keywords in the search bar, I can be connected to the helpful guidance created by these smart people.

Yay technology!

Categories
Software

SwiftUI TextField Focus

When a new view gets presented on the screen (for example a password entry view) most of the time it makes sense to start with the focus in the text field. The keyboard should be visible and the user should be able to just start typing. Sure it’s a minor inconvenience to force users to first tap in the field, and then start typing, but heck we’re not savages are we?

UIKit’s UITextfield always had support for programmatically setting focus via UIResponder. [myTextField makeFirstResponder]

I get the sense this functionality has been gradually evolving in SwiftUI. I feel like the current implementation is more complex than the UIResponder model from yesteryear. I don’t want to sound like I’m grumbling here, and I feel like the complexity is needed to stay true to the SwiftUI model (composability, single source of truth, etc.) Having said that, I did need to iterate way more than anticipated.

The first piece is the @FocusState descriptor used to describe a variable in a View struct. There appear to be two ways to use FocusState.

  1. a Binding<Bool> type when there is a single View that either has or does not have focus
  2. a Binding<enum?> type where there are multiple views, and maybe one of them has focus, or maybe none of them have focus.

I should point out that the notion of focus here is broader than just text entry views. Any type of View and have focus, but the expected behaviour for focus in non-textEntry views is beyond the scope of this post.

Here is an example of how to use a Binding<Bool> FocusState

struct AddEntryView: View {
    
    @Binding var unsavedData: String
    @FocusState var addEntryHasFocus: Bool

    var body: some View {
        TextEditor(text: $unsavedData)
            .focused($addEntryHasFocus)
            .defaultFocus($addEntryHasFocus, true)
    }
}

Here is an example of how to use a Binding<enum?> FocusState

struct PasswordView: View {
    enum PasswordField {
        case secure
        case regular
    }
    @FocusState private var passwordField: PasswordField?
    @State var passwordText1: String = ""
    @State var passwordText2: String = ""
    let passwordExists: Bool

    var body: some View {
        VStack() {
            SecureField("Enter password", text: $passwordText1)
                .focused($passwordField, equals: .secure)
            TextField("Create a password", text: $passwordText2)
                .focused($passwordField, equals: .regular)
        }
        .defaultFocus($passwordField, .secure)
    }
}

Unfortunately, my use case is sort of in between these two implementation options. I only want one field, but sometimes I want it to be a TextField, and sometimes I want it to be a SecureField.

struct PasswordView: View {
    @FocusState private var passwordField: ????
    @State var passwordText: String = ""
    let passwordExists: Bool

    var body: some View {
        if passwordExists {
            SecureField("Enter password", text: $passwordText)
                .focused(????)
        } else {
            TextField("Create a password", text: $passwordText)
                .focused(????)
        }
    }
}

Since there will only ever be one field displayed, I naively thought I could just use the same Binding<Bool> var for both SecureField and TextField. Sadly those values just get ignored. So I need to use the enum approach, even there will only ever be a single field.

And just to add a bit of insult to injury, I wasn’t able to get .defaultFocus working. According to an Apple Engineer in the dev forums, I may have uncovered a bug. woo! All that to say, my next attempt looked something like this.

struct PasswordView: View {
    enum PasswordField {
        case secure
        case regular
    }
    @FocusState private var passwordField: PasswordField?
    @State var passwordText: String = ""
    let passwordExists: Bool

    var body: some View {
	VStack() {
            if passwordExists {
                SecureField("Enter password", text: $passwordText)
                    .focused($passwordField, equals: .secure)
            } else {
                TextField("Create a password", text: $passwordText)
                    .focused($passwordField, equals: .regular)
            }
        }
        .onAppear {
            passwordField = passwordExists ? .secure : .regular
        }
    }
}

BUT, I encountered a life cycle issue. .onAppear gets called before the document gets loaded. In my case, the document contents determine whether to show the TextField or the SecureField. So I needed to replace .onAppear with onChange. So here’s an approximation of my final code.

struct PasswordView: View {
    enum PasswordField {
        case secure
        case regular
    }
    @FocusState private var passwordField: PasswordField?
    @State var passwordText: String = ""
    @Binding var document: TurtleDocument

    var body: some View {
	VStack() {
            if document.passwordExists {
                SecureField("Enter password", text: $passwordText)
                    .focused($passwordField, equals: .secure)
            } else {
                TextField("Create a password", text: $passwordText)
                    .focused($passwordField, equals: .regular)
            }
        }
        .onChange(of: document.encryptedData) {
            passwordField = document.passwordExists ? .secure : .regular

        }
    }
}
Categories
Software

Investigating SwiftUI Document-based Apps

My Turtle app is a document-based app (iOS and MacOS) was written a long time ago. The iOS version relied heavily on UIDocumentBrowserViewController for managing the documents. After a couple of years of neglect the document management was starting to have problems. The iOS version wasn’t able to create new documents. Also closing and saving a document seemed to fail about half of the time.

Eventually these problems made the app unusable enough that I started weighing my options on how best to get things working again. I first considered doing something incremental, focussing on specific issues with the iOS build and sticking with the UIKit based implementation.

But then I saw a WWDC video demonstrating how to get started building a SwiftUI Document based app. I felt some trepidation that there would be a lot of refactoring from UIDocument to SwiftUI’s Document. The UIDocument subclass included metadata that in hindsight was could/should have been UI state. The original implementation also includes complexity related to supporting one iOS target and one macOS target. Last but not least, Turtle supports two different document formats. (Legacy and Threaded) I’m positive I’ve done a bad job of structuring the code to support these two document formats. Moving to a SwiftUI DocumentGroup would require rebuilding this.

So despite all these reasons to stick with the messy UIKit implementation, I jumped into a SwiftUI implementation. While I still have some functionality gaps, on the whole this conversion has been satisfying and even fun.

Future posts will detail some of the issues I’ve encountered along the way.

Categories
Uncategorized

Today Ollie is initiating launch sequence

We’re helping him get set up in residence at Carleton res. Looks like all systems are go…

Categories
Software

Camera1 + Camera2 = Lua

I recently used two cameras to take photos at a soccer game. One with a moderately zoom-y lens (80-200mm) the other with a more zoom-y lens (200-500mm).

Rovers vs Altitude FC
August 4, 2024 (exact time TBD) Rovers vs Altitude FC, Swangard Stadium, Burnaby

When I was looking through the uploaded photos I noticed the time stamps of the two cameras didn’t line up. I’ve seen this before when I have some photos from my phone (which is very smart with time zones and daylight savings) and other photos from an SLR. (much less smart with time zones and daylight savings) Lightroom is very good about letting you select the photos from one camera and offsetting their capture times by a specific amount.

But this is where I made a strategic blunder! I adjusted camera 1 to match the clock of camera 2. Next I went to Lightroom to adjust the times of the camera1 photos. This would have been easy if I knew the delta between the clocks in the two cameras. Sadly I had just destroyed that evidence (by synching the camera clocks.)

So I tried estimating, which was close, but there were a few places where photos from both cameras now had the same capture time, and I was unable to judge from the content whether I needed to increase or decrease the compensation.

I was fairly confident that if I could present the capture times visually (as two data series in a graph) it would be obvious how much I’d need to shift one series to sync with the other series. If only I could extract all the time stamps from both sets of photos, and present them in a graph.

But how do I get all the time stamps of a set of photos in Lightroom? A bit of research taught me I would need to either buy or create a Lightroom plug-in. Being both stingy and keen to learn stuff like how to write a Lightroom plug in, I chose: create!

Martin Fowler has a very helpful page for people that want to create their first Lightroom plug-in. This page also has a line that rings true for me.

“As a programmer, I’m always looking for ways to spend several hours programming to save an hour’s work.”

Martin Fowler

I feel seen.

So I followed the basic arc of Martin’s Lightroom/Lua journey, and was able to extract the needed time stamp values.

For the curious among you, it looked something like this:

I was then able to clean it up and import it into Numbers. (And struggle to get Numbers to show a scatter plot where each series has its own X values. Not intuitive. Thank you YellowBox) And I created a graph like this:

I was definitely going to need to stretch it out, in order to be try out different offset values. Once I stretched it, I was able to shift the blue dots left so that the green dots all fell in gaps.

Of course, next time I’m in this situation I will remember to sync my cameras before I start. And as a fallback, if I do end up using un-synched cameras, I will be sure to fix the photo metadata before I sync the camera clocks.

And for any Lua fans out there, here’s my plug-in code.

local LrApplication = import 'LrApplication'
local LrLogger = import 'LrLogger'
local LrTasks = import 'LrTasks'

local myLogger = LrLogger( 'lightroomLogger' )
myLogger:enable( "logfile" )

local function log( message )
  myLogger:trace( message )
end

local catalog = LrApplication.activeCatalog()

local function findCandidates()
	return catalog:findPhotos {
	   searchDesc = {
			 criteria = "captureTime",
			 operation = "thisMonth",
			 value = 2024-08-04,
	   }
	} 
 end

local function showDates()
	LrTasks.startAsyncTask(function()
		local photos = findCandidates()
		for i, v in ipairs(photos) do 
			-- local prop = v:getRawMetadata("fileSize")
			local createTime = v:getRawMetadata("dateTime")
			local colourName = v:getRawMetadata("colorNameForLabel")
			local createTimeOriginal = v:getRawMetadata("dateTimeOriginal")
			local fileName = v:getRawMetadata("path")
			local result = "orig, " .. createTimeOriginal .. ", " .. createTime .. ", " .. colourName .. ", " .. fileName
			log(result)
		end
	end )
end

showDates()

Rovers Vs 2009 Boys

Categories
Software

How to listen for changes in an @AppStorage value

For my flash cards app, I’ve added a few user settings that I’ve discussed in other posts. For example, users can choose whether they want to use the same ask language for all cards (e.g. Always first show words in Japanese). They can also choose whether they want to use かな or Romaji for Japanese. They can also choose a subset of the cards they want to use (eg numbers, months/daysOfTheWeek etc.)

Any time one of these settings gets changed by the user, the app’s LanguageManager singleton needs to update the list of available words that match the settings. (for example, if a given word does not have a French translation and the user specifies fr as their only answer language, that card can’t be shown to the user.)

Strictly speaking, available cards could be recalculated on demand. (ie each time a user switches to a new card) But that would be suboptimal, given the available cards only changes when one of the settings changes. But in the spirit of saving cycles, I feel like finding a way to calculate available cards only when the value changes is a worthy cause.

For a starting point, I have the following code where the UI is setting the UserDefault value.

struct CardPickerView: View {
    
    @AppStorage("cardPile") var cardPile: CardPile = .allRandom
    var body: some View {
        VStack {
            Picker("Card Pile", selection: cardPile) {
                ForEach(CardPile.allCases,
                        id: \.self) { 
                    Text($0.title)
                        .tag($0.rawValue)
                }
            }
        }
    }
}

And the following code where LanguageManager updates availableCards

class LanguagesManager {
    static let shared = LanguagesManager()

    // The UserDefaults values that will impact which cards/words can be used
    @AppStorage("scriptPickers") var scriptPickers:  ScriptPickers = ScriptPickers.defaultDictionary
    @AppStorage("languageChoice") var languageChoice: LanguageChoice = .all
    @AppStorage("cardPile") var cardPile: CardPile = .allRandom

    // All the words
    let words: Words

    // The words that match the criteria
    var availableWords: Words

    func updateAvailableWords() {
        var result = words.filtered(by: cardPile.wordList)
        let askLanguages = languageChoice.askLanguages
        let answerLanguages = languageChoice.answerLanguages
        result = result.matching(askIdentifiers: askLanguages, answerIdentifiers: answerLanguages)
        availableWords = result
    }
}

This code mostly works as expected, however when a user updates scriptPickers or languageChoice or cardPile, availableWords doesn’t get updated. <frownyFace!!> My first thought was to use didSet (like this)

    @AppStorage("cardPile") var cardPile: CardPile = .allRandom {
        didSet { updateAvailableWords() }
    }

Sadly didSet on @AppStorage only works in very specific situations for me. Based on what I saw, it works if:

  1. your code is in a View
  2. you explicitly update the value (eg. self.cardPile = .all)

Your mileage may vary. Bottom line: didSet in LanguagesManager was not getting called for me.

It would have been possible to add calls to updateAvailableWords() in the UI code any time one of the related settings changed. But relying on the UI code to keep LanguagesManager fresh. Just. Felt. Wrong.

I also tried to use combine to subscribe to the @AppStorage values, but was not able to get that working. <anotherFrownyFace>

The solution I ended up using was to create a UserDefaults extension that would create a Binding<> for a specific UserDefaults key value. Anytime this value changes, it will fire a notfication.

extension UserDefaults {
    func cardPileBinding(for defaultsKey: String) -> Binding<CardPile> {
        return Binding {
            let rawValue = self.object(forKey: defaultsKey) as? Int ?? 0
            return CardPile(rawValue: rawValue) ?? .allRandom
        } set: { newValue in
            self.setValue(newValue.rawValue, forKey: defaultsKey)
            NotificationCenter.default.post(name: .userDefaultsChanged, object: defaultsKey)
        }
    }
}

extension Notification.Name {
    static let userDefaultsChanged = Notification.Name(rawValue: "user.defaults.changed"
}

Next, I used these bindings in the UI code where I’d previously been using @AppStorage.

struct CardPickerView: View {
    let cardPileBinding: Binding<CardPile>
    init(userDefaults: UserDefaults = .standard) {
        self.cardPileBinding = userDefaults.cardPileBinding(for: "cardPile")
    }
    var body: some View {
        VStack {
            Picker("Card Pile", selection: cardPileBinding) {
                ForEach(CardPile.allCases,
                        id: \.self) { 
                    Text($0.title)
                        .tag($0.rawValue)
                }
            }
        }
    }
}

Then I updated LanguagesManager to listen for this notification.

class LanguagesManager {
    private var subscriptions = Set<AnyCancellable>()
    init() {
        // a bunch of unrelated init stuff....

        NotificationCenter.default.publisher(for: .userDefaultsChanged)
            .sink(receiveValue: { notification in
                self.updateAvailableWords()
            })
            .store(in: &subscriptions)
    }

And this meets my needs, insofar as the UI code doesn’t need to explicitly fiddle with LanguagesManager state any time a user changes their prefs. I don’t like that the UI code needs to use this special binding to do the updating. I also don’t like that the UserDefaults extension needs to define multiple functions to return the different types of Bindings. If you all know of a way to get around this (generics?) I’m all ears!

I also don’t like that the getters and setters in the Binding values need to translate to and from rawValue. @AppSupport magically handled the translations between rawValue and encodedValue.

And one last problem with this approach, for at least one of my settings UI inplementations, calling the setter in the binding didn’t trigger an update to the UI. (it’s on my todo list to investigate this.) I got around this problem by adding a ‘dummy’ @AppStorage var in the View. Adding this dummy value seems to force the view to redraw when the UserDefault value changes.

    @AppStorage("cardPile") var cardPileDummy: CardPile = .allRandom

Hurricanes Vs South Surrey

HurricanesVsSouthBurnaby