Skip to content

Hi! 👋 I'm Joseph Duffy. I enjoy building software. This website contains information about my commercial software, open-source projects, and blog posts. Welcome to my corner of the internet!

★ My Favourites

Nevis

Nevis Icon

Find and install applications found in DMGs and ZIPs.

Configurable to watch directories, ask before performing actions, moving used files to trash, and is completely free to try.

Download NevisVisit Nevis Website

Gathered

Gathered Icon

Gathered is an app that facilitates the viewing, recording, and sharing of over 25 sources of data available on iOS and iPadOS devices, from the GPS and accelerometer to the advertising identifier and the WiFi. Gathered is the perfect app to enable the recording of all the data required to calulcate position, acceleration, and ambient conditions of the device.

Download Gathered on the App Store

HashableByKeyPath framework release 1.0.0


Today I have released the 1.0.0 version of a Swift package that aids with adding Equatable and Hashable conformance by using KeyPaths.

The package is available on GitHub.

I created the Swift Playground that sparked this concept in December 2018, so this concept has been rattling around in my brain for a couple of years. The API has changed a lot since the original concept, but the core has stayed the same: a protocol that requires a single function to be implemented that uses KeyPaths to synthesise Equatable and/or Hashable conformance.

Keep Reading

iOS Share Sheets the Proper Way - Locations


Sharing a location on iOS is something that not a lot of apps need, but after requiring it for my latest app, Scanula, I found that there isn't a good resource explaining how to do it properly. This is the first post in a series of planned posts going over a few of the tips, tricks, and common pitfalls I have found while working with iOS Share Sheets.

Keep Reading

Recent Entries

Pull request Cache `SingleElementSections.numberOfElements` on composed-swift/Composed


Overall this is pretty minor but when dealing with a lot of SingleElementSections that are infrequently updated is does start to slow down.

I thought a faster approach would be to use generics on replace(element:) to only check for nil when the value is Optional, but generics don't allow for this kind of overload.

Also added some basic tests. The performance tests show a ~9% performance increase.

Pull request Cache nib registrations to improve performance on composed-swift/Composed


@bill201207 found that UINib(nibName:bundle:) is a big part of the poor performance when our app launches.

Ultimately batching updates will help with a lot of things like this, but really that'll just be masking some of the performance issues so it's useful to find issues like this now.

Mapping Optional Binding to Bool


When displaying an alert in SwiftUI, if the value used to calculate whether the alert is presented is both Optional and does not conform to Identifiable1 it is often recommended to use a separate flag, similar to:

struct ContentView: View {
    @State private var alertText: String?
    @State private var isPresentingAlert = false

    var body: some View {
        Button("Show Alert") {
            self.alertText = "Alert Text"
            self.isPresentingAlert = true
        }
        .alert(isPresented: $isPresentingAlert) {
            Alert(title: Text(alertText!))
        }
    }
}

There are 2 main downsides to this:

  1. alertText is not set back to nil, which may cause bugs and will increase memory usage (even if only a little in this case)
  2. The isPresentingAlert flag needs to be managed

To work around these issues I create a small extension to Binding the allows this same code to be updated to:

struct ContentView: View {
    @State private var alertText: String?

    var body: some View {
        Button("Show Alert") {
            self.alertText = "Alert Text"
        }
        .alert(isPresented: $alertText.mappedToBool()) {
            Alert(title: Text(alertText!))
        }
    }
}

The extension is fairly small and simple:

import os.log
import SwiftUI

extension Binding where Value == Bool {
    /// Creates a binding by mapping an optional value to a `Bool` that is
    /// `true` when the value is non-`nil` and `false` when the value is `nil`.
    ///
    /// When the value of the produced binding is set to `false` the value
    /// of `bindingToOptional`'s `wrappedValue` is set to `nil`.
    ///
    /// Setting the value of the produce binding to `true` does nothing and
    /// will log an error.
    ///
    /// - parameter bindingToOptional: A `Binding` to an optional value, used to calculate the `wrappedValue`.
    public init<Wrapped>(mappedTo bindingToOptional: Binding<Wrapped?>) {
        self.init(
            get: { bindingToOptional.wrappedValue != nil },
            set: { newValue in
                if !newValue {
                    bindingToOptional.wrappedValue = nil
                } else {
                    os_log(
                        .error,
                        "Optional binding mapped to optional has been set to `true`, which will have no effect. Current value: %@",
                        String(describing: bindingToOptional.wrappedValue)
                    )
                }
            }
        )
    }
}

extension Binding {
    /// Returns a binding by mapping this binding's value to a `Bool` that is
    /// `true` when the value is non-`nil` and `false` when the value is `nil`.
    ///
    /// When the value of the produced binding is set to `false` this binding's value
    /// is set to `nil`.
    public func mappedToBool<Wrapped>() -> Binding<Bool> where Value == Wrapped? {
        return Binding<Bool>(mappedTo: self)
    }
}

The extension isn't tied directly to showing an alert or a sheet and can be used in any context, but this is one of the better examples of its usage.

This extension is available on GitHub under the MIT license.

1 If it does conform to Identifiable use alert(item:content:)

Keep Reading

Pull request Merge ComposedLayout and ComposedUI packages on composed-swift/Composed


This adds the ComposedLayout and ComposedUI packages, which will close #16.

The base for this is 2.0-beta, which we can use for testing new changes, which can also break API.

If you go to the repo settings you can (temporarily) set the default branch to 2.0-beta and we can add a note to the README stating this is the beta release and the stable release is available in the master branch.

Once merged and these changes are made the READMEs for ComposedLayout and ComposedUI repos can be updated to point to this repo and their repos archived.

Pull request Add `FlatSection` on composed-swift/Composed


This is a new type of section that is similar to ComposedSectionProvider, but rather than flattening each of the children in to a single SectionProvider it flattens them in to a single Section.

The ComposedUI side of this has been updated to support multiple cells per section, with a convenience for FlatSection that delegates the calls to each of the flattened sections.

This is a breaking change since the protocol requirements have changed. I have set the base of the PR to merge-all-libraries since that's also a breaking change and this PR relies on those changes.

edit: We've moved to our own fork to allow for more rapid development. The latest changes are in https://github.com/opennetltd/Composed/tree/feature/FlatSection, which will eventually be merged back in to this repo once some of the other PRs/issues have been resolved.

Pull request Use default Xcode version on composed-swift/Composed


In my open source frameworks I usually use the last major Xcode (currently 11.7), latest (currently 12.3) and latest beta (soon to be 12.4), but this project has only been setup for the latest version of Xcode.

If we feel it's good to run the tests across multiple Xcode versions I have created https://github.com/JosephDuffy/update-xcode-version-action, which we can use to keep the versions in-sync with the versions available as part of GitHub actions.

For now I've removed all use a explicit Xcode versions.