Jekyll2025-10-11T09:19:07+00:00https://sintraworks.github.io/feed.xmlSintraWorks BlogSome chatter, babble and prattle about things I find interesting.Money Type2023-09-13T00:00:00+00:002023-09-13T00:00:00+00:00https://sintraworks.github.io/swift/2023/09/13/money<![CDATA[

In various places where I’ve worked, even various financially-oriented institutions, I’ve found that the type used for dealing with monetary amounts was some form of floating point number. This is never a good idea. Floating point numbers are a bad tool for dealing with decimal values, due to the way they store fractional numbers, and the imprecision that results from that. Especially when those number are added together, substracted from each other, multiplied or divided, which will almost always compound the imprecision and result in increasingly significant rounding errors. The minimum needed to safely deal with monetary amounts is a decimal type: a type that can represent decimals without loss of precision. In Swift we have the Decimal type to satisfy that need. But the Decimal type in and of itself is not really enough when dealing with monetary amounts, unless, maybe, when your needs are most basic. When dealing with monetary amounts we are really always dealing with an amount in a particular currency. It is convenient therefore to have a type that can represent an amount in a particular currency (whichever currency that may be), and to be able to perform arithmatic with those amounts, in a type-safe manner. By type-safe, I also mean that you cannot, say, add two amounts representing different currencies together. While it makes sense to add 2 euros to 4 euros, the result being 6 euros, it doesn’t make sense to add 2 euros to 4 dollars. What would that even mean? Unless you have built in provisions for currency exchange. But even then, you’d first be converting one amount to a (more or less) equivalent amount in the other currency, before operating on the amounts.

The Money package aims to offer a convenient abstraction for dealing with monetary amounts. It provides a type-safe environment, where you can represent amounts of any currency, and perform arthmetic operations between amounts of the same currency.

Goals

  1. Precision: Allow handling of monetary amounts without loss of precision. This is achieved by using the Decimal type to represent amounts. Decimals do not suffer from the (potentially) imprecise representations that make floating point numbers inappropriate as a carrier for monetary amounts.
  2. Type safety: Disallow arithmetical operations between monetary amounts of different currencies. This is achieved by making the concrete money type (Tender) generic over its currency, enabling compiler level support when dealing with money types.
  3. Yet we must be flexible in allowing dynamic creation of concrete monetary amounts, and allowing collections that contain monetary amounts of varying currencies. This is achieved through the Money protocol.
  4. Convenience: Working with monetary amounts should be as easy as working with built-in number types. This is achieved by adding provisions that facilitate day-to-day needs when dealing with monetary amounts.

Implementation

Currencies are represented by caseless enums that conform to the Currency protocol. We use caseless enums since we want to use currencies as types only. We don’t want instances of currencies, since we don’t need them, and caseless enums cannot be instantiated. Currencies have a code and a minorUnitScale. The minorUnitScale represents the subdivisions of the main (major) currency unit. E.g: Euros and US Dollars are divided into cents, hence they have a minorUnitScale of 2. Each currency also has a name. We derive the (localized) name from the platform, since Apple platforms provide this information.

We provide default implementations for all properties defined in the protocol to avoid code duplication. The only implementation that is expected to occasionally be overridden is that of the minorUnitScale. The vast majority of currencies have a unit scale of 2, but some currencies deviate from that. We provide a default list of known currencies, which, we expect, will seldom need to be updated. The list we provide is based on the common currencies known to the Apple platforms, and therefore is not guaranteed to be quite 100% equal to the ISO 4217 list of currencies, but the discrepancies, at the time of this writing are small enough to be negligeable to most. Yet, your needs may vary, so you may choose to alter the list to your needs.

For the represention of monetary amounts we use two related types: Money, a protocol that represents monetary amounts, and Tender a type that conforms to Money. Tender is generic over the currency it represents, which provides compiler assisted type safety (e.g. disallowing the addition of Tenders of different currencies), while its conformance to the Money protocol provides the needed flexibility to work with collections of money of various currencies, and run-time instantiation of monetary amounts whose currency is not known at compile-time.

Think of Money as the abstract type, providing dynamic flexibility at runtime, and of Tender as the concrete monetary amount that is tendered (offered) for processing.

While you cannot add or subtract Tenders of different currencies (which would make no sense without first converting them to the same currency), or multiply one Tender by another, you can perform arithmetic on Tenders. E.g. you can multiply a Tender by a numeric type: €5.42 * 3,14.

Usage

Currencies are already provided, so you should not need to create currencies. However, if, for whatever reason, you find you need to create custom currencies, you can create new currency types as enums that conform to the Currency protocol. If your custom unit needs a minorUnitScale that is not the standard 2, then you need to provide a custom implementation in the body of the enum:

public enum MyCustomCurrency: Currency { public static var minorUnitScale: Int { 4 } }

Otherwise, you can just create an enum with an empty body:

public enum MyCustomCurrency: Currency {}

Create concrete monetary amounts by creating instances of Tender, providing the currency it is generic over. Preferably create Tenders from Decimals, Ints or Strings, rather than from floating point values, although you can use those if you really need to.

    let amount: Decimal = 3.14 // (or, preferably: Decimal(string: "3.14"))
    let someMoney1 = Tender<EUR>(amount)
    let someMoney2 = Tender<USD> = 5
    let someMoney3 = Tender<JPY> = 3000
    let someMoney4 = Tender<SEK>("42")
    let someMoney5 = Tender<USD>("5,501", locale: Locale(identifier: "nl_NL")
    let someMoney6 = Tender<USD>("5.501", locale: Locale(identifier: "en_US")

    let ratherNot = Tender<EUR>(3.02) // Floating point number are approximations, they cannot be trusted to produce correct results.

You cannot add or subtract Tenders of different currencies. You can however add and subtract Tenders of the same currency:

    let sum = someMoney5 + someMoney6

You can also compare Tenders of the same currency:

    if someMoney5 < someMoney6 {
        …do something…
    }

Take a look at the TenderTests and TenderAlgebraTests files (in the repo) for a good overview of what is possible.

Of course, we will usually need to display our monetary amounts to our users. To this end, the Money type provides a displayable, of type Displayable, that facilitates the translation from the amount to user friendly strings. When you want to diplay an amount to the user, ask the instance for a displayable, and ask the Displayable for the desired string:

        someMoney5.displayable.formatted
        someMoney5.displayable.formattedTruncatingDecimals()
        someMoney5.displayable.formattedTruncatingDecimals(for: Locale(identifier: "en_GB")
        someMoney5.displayable.formattedWithoutCurrencySymbol()
        someMoney5.displayable.formattedWithoutCurrencySymbol(for: Locale(identifier: "pt_PT")

You can also ask a displayable for the currency symbol and decimal separator and currency name.

If you need to store collections of amounts with various currencies, you cannot create a collection of Tender, since Tender is generic over its currency. In this case you should create a collection of Money, since Money, while holding a currency, is not generic over it.

        var variousCurrencies = [any Money]()
        variousCurrencies.append(Tender<EUR>(1))
        variousCurrencies.append(Tender<USD>(0.5))

Additionally, if you need to create monetary amounts at runtime, whose currency you do not know at compile time, use the Money type to model your data. Use the MoneyFactory to obtain a concrete Tender (typed as Money) from an amount and currency code:

        let myRuntTimeAmount = Decimal(42)
        let myRuntimeCurrency = SupportedCurrency.INR
        
        try MoneyFactory.moneyFrom(amount: myRuntTimeAmount, currency: myRuntimeCurrency)

You can find the full source code here.

]]>
<![CDATA[Using a Money Type for Typesafe Currency Handling.]]>
Fractions Revisited2023-09-12T00:00:00+00:002023-09-12T00:00:00+00:00https://sintraworks.github.io/swift/2023/09/12/fractions-revisited<![CDATA[

This post is a follow-up on myprevious post about fractions from some years ago, where I discuss the creation of the Fraction type. (I meant to publish this much sooner, but life got in the way.)

As I started working more with Fraction, I found that I needed to be able to more easily mix Fractions with the base numeric types during calculations, as well as making it easy to instantiate fractions from numeric literals. I also found it inconvenient to only have failable initializers, since often I can guarantee I will only pass in valid values to the initializers. So I added non-failable alternatives that are guaranteed to instantiate (or, crash if you pass in invalid values).

This post picks up from where the previous post on Fractions left of, so I advise you to read that first, if you haven’t already, and then come back here.

Guaranteed Initializers

If you know what you’re doing, i.e. you have taken care to eliminate the possibility of providing invalid input, you can now use guaranteed initializers when instantiating Fractions. This eliminates the frequent need for unwrapping and/or forced-unwrapping when creating fractions.

public init(verifiedNumerator: Int) {
    precondition(verifiedNumerator > Int.min, "Illegal numerator value: Int.min is not allowed")

    self.numerator = verifiedNumerator
    self.denominator = 1
}

public init(verifiedNumerator: Int, verifiedDenominator: Int, wholes: Int = 0) {
    precondition(verifiedNumerator > Int.min, "Illegal numerator value: Int.min is not allowed")
    precondition(verifiedDenominator != 0, "0 is an illegal value for the denominator")
    
    self.numerator = verifiedNumerator + (verifiedDenominator * wholes)
    self.denominator = verifiedDenominator
}

The preconditions ensure you will crash early during development if you pass in an illegal numerator value. Thus reducing the chance of programmer error in production code. In the second initializer I have also added the option to pass in an amount of whole numbers, as a convenience, if you want to turn e.g. 2•2/3 into a Fraction. This wholes option was also added to the existing initializers.

Instantiating Fractions from Literals

While the Fraction type already had initializers, I regularly found it inconvenient to have to instantiate fractions through those wordy initializers:

let f = Fraction(numerator: 3, denominator: 1)

Therefore Fraction now conforms to ExpressibleByIntegerLiteral and ExpressibleByFloatLiteral, allowing the creation of Fraction instances from Ints and the various float types. So you can now write things like:

let wholeFraction: Fraction = 3
let wholeFraction2 = wholeFraction * 2

let fractionalFraction: Fraction = 3.9
let fractionalFraction2 = try? fractionalFraction / 3.3

Fraction now sports an adjustable static var to guide the precision when creating fractions from floating point values, which defaults to a precision of four digits: static var significantFloatingPointDigits = 4. You may adjust this value as needed.

The test suite has been accordingly updated.

You can find the full source code here.

]]>
<![CDATA[Mixing fractions with other numeric types.]]>
SwiftUI: Showing a control and implementing avoidance2022-08-22T00:00:00+00:002022-08-22T00:00:00+00:00https://sintraworks.github.io/swift/swiftui/2022/08/22/bottom-wheel-picker<![CDATA[

In this post we will create a SwiftUI control that can be popped up from the bottom of the screen. We will also make it possible to allow a specific view (usually the view that triggers the control and reflects the chosen value) to be raised, if it would otherwise be obscured by the control when the control pops up. This post assumes you have a reasonable knowledge of SwiftUI and are at least somewhat familiar with more advanced topics such as bindings, geometery readers, preference keys, etc. It won’t go into details as to how they work. I will simply show how to use them to achieve the desired effact.

We’ll use a wheel picker to demonstrate the technique, but of course it could be used for other content too.

Wheel Picker

To start with, let’s create a view that shows a wheel picker and a dismissal button, formatted to look somewhat like a system control.

struct WheelPicker: View {
    @Binding var selection: String
    @Binding private(set) var show: Bool
    var values: [String]
    var dismissButtonTitle: String

    var body: some View {
        VStack {
            VStack {
                Divider()
                Spacer()
                Button(action: {
                    withAnimation {
                        self.show = false
                    }
                }) {
                    HStack {
                        Spacer()
                        Text(dismissButtonTitle)
                            .padding(.horizontal, 16)
                    }
                }
                Divider()
            }
            .background(Color.init(uiColor: .secondarySystemGroupedBackground.withAlphaComponent(0.5)))
            Picker(selection: $selection, label: Text("")) {
                ForEach(values, id: \.self) {
                    Text("\($0)")
                }
            }
            .pickerStyle(.wheel)
        }
    }
}

The picker needs some values to show. To keep things simple, we only allow strings. We also allow setting the title of the button that dismisses the view when tapped. Finally, there are two bindings so that we can communicate with the client about when to hide the control, and about the selection. The show binding is readonly, since we only use it to communicate back up the chain that the dismissal button was pressed. The selection binding is two way: we simply pass it on to the picker. This should result in an initial selection, and when the user changes the selection the new value will be passed back up the chain. We use the withAnimation modifier to ensure the shifts are nicely animated.

Bottom Wheel Picker

Next, we need to implement a mechanism that can show the picker at the bottom of the screen when it needs to be visible, and that hides it just below the screen when it needs to be hidden.

struct BottomWheelPicker: View {
    /// A binding that controls showing or hiding the picker
    @Binding var show : Bool
    /// A binding that manages the selected option from the collection of values
    @Binding var selection: String
    /// The collection of values available to the picker
    var values: [String]

    var body: some View {
        WheelPicker(selection: self.$selection,
                    show: self.$show,
                    values: values,
                    dismissButtonTitle: "Done")
        .fixedSize() // Ensures the WheelPicker is exactly as large as it needs to be, and no larger
        .background(
            Color.init(uiColor: .systemGray6)
        )
        // When not shown, hide us below the bottom of the screen.
        .offset(y: self.show ? 0 : UIScreen.main.bounds.height)
    }
}

show, selection and values are simply passed directly to the picker.

In the body we provide the WheelPicker, and use fixedSize to ensure our wheel picker view is only as large as it needs to be. We set a background color in the background modifier, and use the offset modifier so that the picker is hidden below the screen when not shown.

Showing and Hiding the Picker in a Container View

Now we need a view that makes use of the wheel picker to allow the selection of a value from a list of predefined values.

struct ContentView: View {
    @State var showPicker = false
    @State var selection: String = "€1.500"
    @State var pickerHeight: CGFloat = 0
    var values = ["€1.500", "€3.000", "€6.000", "€9.000", "€10.000", "€11.000", "€12.000", "€13.000", "€14.000", "€15.000", "€16.000", "€17.000", "€18.000"]
    @State var showMoreText = true

    var body: some View {
        VStack {
            Group {
                Spacer()
                Text("Let's pick an amount")
                    .padding()
                Button(action: {
                    withAnimation {
                        self.showPicker.toggle()
                    }
                }) {
                    Text(selection)
                        .padding()
                }
            }
            Group {
                Text("The chosen amount")
                Text("is…")
                Text(selection)
                Text("Be kind")
                Text("…and friendly,")
                Text("really.")
            }
            if showMoreText {
                Spacer()
                Text("It works!")
            }
        }
        .frame(minWidth: 300, minHeight: 600)
        .overlay(BottomWheelPicker(show: $showPicker, selection: $selection, values: values), alignment: .bottom)
    }
}

The code above creates a view that has a button that reflects a monetary amount. The button acts as a toggle: Tapping it will show the picker, if it is hidden, and hide the picker, if it is on-screen.

The showPicker state variable is used to control the visibility of the picker. It is tied to the button, naturally, so that we trigger view updates when the button is tapped.

The selection state variable is tied to the button title. It will be updated when the user selects a value from the picker.

Below we have some filler text, mostly for demonstration purposes.

We use the frame modifier so that we have a reasonably sized view in a playground. If the content is hosted in a real app, you should remove this modifier.

Finally, but rather importantly, we use the overlay modifier to host the BottomWheelPicker, passing along the relevant state variables and picker values. When the picker is triggered it will be animated in, on top of the content view.

We now have all the pieces in place to show and hide the wheel picker on command. You could use the above code in a playground to see it in action.

There is a variable showMoreText, that defaults to true. It ensures the button is quite high up in the content view, so that it is not obscured by the picker, when the picker is shown. If you set that variable to false before running the playground, you’ll see that the button is considerably lower on the screen, and that it gets obscured by the picker when the picker is visible.

We need to provide a mechanism that allows us to raise the button when showing the picker, but only if the button would be obscured by the picker:

Wheel Picker Avoidance

To enable view avoidance, we need to do a fair bit of bookkeeping. We need to know the height of the picker, and we need to know the y-coordinate of the bottom of the view that wants to avoid the picker (in our case, the button). Then we can use that data to determine if, and if so, how much, the avoiding view needs to shift up, not to be obscured by the picker.

We also need a way to be notified when the picker is shown, and when it is hidden so that we can shift the view in the appropriate direction when we receive such a notification.

Let’s start by enabling access to the picker’s frame.

We create a preference key to hold the frame information. It is quite basic, as it doesn’t need to accumulate any values.

struct FramePreferenceKey: PreferenceKey {
    static var defaultValue: CGRect = .zero
    static func reduce(value: inout CGRect, nextValue: () -> CGRect) {}
}

Then we add a geometry reader to the background modifier, to provide the frame of the wheel picker. We put the reader around the color, and then provide the preference key hanging off the color with the frame of the picker in global coordinates.

        .background(
            // Use the geometry reader to extract the frame of the WheelPicker
            GeometryReader { geometry in
                Color.init(uiColor: .systemGray6)
                    .preference(key: FramePreferenceKey.self, value: geometry.frame(in: .global))
        })

We also add notifications to be sent when showing or hiding the picker.

At the top of the BottomWheelPicker:

struct BottomWheelPicker: View {
    static let willShowNotification: NSNotification.Name = NSNotification.Name(rawValue: "PickerWillShow")
    static let willHideNotification: NSNotification.Name = NSNotification.Name(rawValue: "PickerWillHide")

Between the background and offset modifiers add this modifier:

.onPreferenceChange(FramePreferenceKey.self) { newFrame in
// Post a notification that we are showing or hiding, passing along the resulting frame info
let notificationName = show ? Self.willShowNotification : Self.willHideNotification
NotificationCenter.default.post(name: notificationName, object: self, userInfo: ["frame": newFrame])
}

The following extension to Notification provides convenient access to frame info in its user dictionary:

extension Notification {
    /// Extends `Notificaton` with a shortcut to acces a frame from its user info dict.
    var frame: CGRect {
        userInfo?["frame"] as? CGRect ?? .zero
    }
}

We now have access to the frame of the picker, as it changes, and post a notification whenever the picker is shown or hidden. What we still lack is a mechanism to observe these changes and react to them.

To enable any view to avoid the picker, we are going to create a view modifier. This modifier can be attached to any view that needs to avoid the picker. Depending on the buildup of the encompassing view, more than one view might have this view modifier attached, but most likely you are only going to want to attach it to a single view in the scene, since usually all the views above it will also be shifted up (e.g. because you are in a vertical stack view).

The view modifier will provide a customisable padding to allow control over how much space to leave between the picker and the avoiding view when shifting it out of the way.

struct BottomWheelPickerAdaptive: ViewModifier {
    /// The current frame of the picker. This will be either zero (when the picker is hidden), or the actual frame (when the picker is shown)
    @State private var pickerFrame: CGRect = .zero
    /// The offset to be applied to the adaptee's bottom.
    @State private var offset: CGFloat = 0
    /// The current frame of the adaptee.
    @State private var adapteeFrame: CGRect = .zero
    /// A padding to be apply to the offset to ensure the adaptee does not rest flush on the picker.
    public var padding: CGFloat = 12

    func body(content: Content) -> some View {
        content
        // padd the bottom by offset. This will effectively raise the adapting view when offset > 0.
            .padding(.bottom, offset)
        // subscribe to wheel picker frame changes
            .onReceive(Publishers.wheelPickerFrame) { pickerFrame in
                withAnimation {
                    self.pickerFrame = pickerFrame

                    guard pickerFrame.height > 0 else {
                        offset = 0
                        return
                    }

                    // The padding ensures the picker will not sit too closely below the adaptee, even if it wouldn't obscure any part of the adaptee.
                    //
                    // Additionally, only set an offset if the adaptee's bottom (extended by the amount of padding) will be obscured by the picker,
                    // i.e. only when the offset required for avoidance is greater than zero, because negative values mean the adaptee will remain sufficiently clear from the picker, without raising it.
                    offset = max(0, (adapteeFrame.maxY + padding) - pickerFrame.minY)
                }
            }
            .background(
                GeometryReader { geometry in
                    // Use a neutral view to allow us to obtain the frame of the content (= adaptee)
                    Color.clear
                        .preference(key: FramePreferenceKey.self, value: geometry.frame(in: .global))
                }
            )
        // Here we subscribe to changes to the frame of the adaptee.
            .onPreferenceChange(FramePreferenceKey.self) { newFrame in
                adapteeFrame = newFrame
            }
    }
}

The crux of the view modifier, is that it shows its content with a bottom offset applied to it (.padding(.bottom, offset)). It accesses the content frame info in the background modifier, through a GeometryReader, and subscribes to frame changes of the adaptee (the avoiding view) in the onPreferenceChange modifier.

The subscription to changes to the picker wheel frame is established in the onReceive modifier, and the offset is calculated and set within its closure.

Finally, we want to make access to this view modifier convenient, which is what the following extension on View does:

extension View {
    /// Provides bottom wheel picker avoidance behavior
    func bottomWheelPickerAdaptive(padding: CGFloat = 12) -> some View {
        ModifiedContent(content: self, modifier: BottomWheelPickerAdaptive(padding: padding))
    }
}

Now, all that is left to do, is to add the bottomWheelPickerAdaptive modifier to the main view. We want the button showing the current amount to always remain visible, so that’s where we add the modifier. The Group holding the button now becomes:

Group {
                Spacer()
                Text("Let's pick an amount")
                    .padding()
                Button(action: {
                    withAnimation {
                        self.showPicker.toggle()
                    }
                }) {
                    Text(selection)
                        .bottomWheelPickerAdaptive()
                        .padding()
                }
            }

I hope this compact post was clear enough to let you use this technique in any project that might benefit from it.

You can find the full source code, ready for use in an Xcode Playground, in a Github gist here.

]]>
<![CDATA[Showing a view containing a control from the bottom, and implementing view avoidance so that the related view is not obscured.]]>
An Adaptable Segmented Control2020-07-07T00:00:00+00:002020-07-07T00:00:00+00:00https://sintraworks.github.io/swift/swiftui/2020/07/07/segmentedpicker<![CDATA[

SegmentedPicker Demo Clip

On iOS, especially on iPads, the dimensions of the size of the views we work with have, over the years, become increasingly flexible and varied. Additionally, accessibility features, like dynamic, user controllable font size have been with us for a while. This means that our user interface items need to be increasingly flexible so that they can adapt to their sometimes dynamically changing environment.

Not so long ago, I found myself faced with a small challenge: I had created a segmented control in SwiftUI, that supports a visual style particular to one of the apps I work on. When I started testing how the control behaved, especially when combining dynamic font size changes with multi-tasking split screens on iPads, I found an issue: Even though my control only had three segments with short titles, at the larger accessibility font sizes, especially if the screen was split due to multitasking mode, the segments would not fit. After thinking about the various ways in which this issue could be solved, I quickly settled on allowing the control to dynamically change to a popup-menu-style control when space gets tight. Now, in UIKit, I knew exactly how to do that. But trying to do this in SwiftUI, which I’m fairly new to, and which uses a completely different approach to building layouts, this was a bit more challenging. Yet, a fun project to dive into.

The solution is made possible through the use of GeometryReaders, preferences and anchors. This post will assume you are already familiar with those, and will simply explain how to arrive at a solution using these and other relevant techniques, like @Bindings. If you are unfamiliar with GeometryReaders, PreferenceKeys, etc, I suggest you read up on those elsewhere. A website that I found particulalry helpful when figuring all this out is The SwiftUI Lab. Also very informative is an excellent book by objc.io called Thinking in SwiftUI.

Let’s get to it…

We start by creating the basic segmented control. Each segment will consist of a button that performs a very specific action when tapped: it sets the active segment. We visually indicate the active segment by underlining it with a thin yellow beam. We’ll call this control a ‘SegmentedPicker’.

Before we can build the segmented control, however, we need to implement a view type to provide the segments. We’ll call that a PickerButton, and here is a first draft of its implementation:

struct PickerButton: View {
    var index: Int
    let title: String
   @Binding var selectedButtonIndex: Int
    
    var body: some View {
        Button(action: {
            selectedButtonIndex = self.index
        }) {
            Text(title).fixedSize()
        }
        .padding(10)
    }
}

The index holds the index of the segment in the parent view (the segmented control), and the title is used for displaying the title of the segment. The view’s body is very simple: a single button that displays the title and sets the selectedButtonIndex when tapped. The selectedButtonIndex is a binding so that we can communicate the segment selection back to the control.

Now we are ready to start implementing the segmented control. We’ll start by implementing the basic control, without any fancy, flexibility oriented features. We’ll need to hold an array of titles to use for the button labels, and we’ll need to know which button represents the selected segment. The selectedSegment is a @Binding so that we can communicate, not only between the buttons and the picker, but also between the parent view that probably has a @State var to listen to selection changes in the picker.

struct SegmentedPicker: View {
    @Binding var selectedSegment: Int
    var labels: [String]

    var body: some View {
        HStack {
            ForEach(0 ..< labels.count, id: \.self) {
                PickerButton(index: $0, title: labels[$0], selectedButtonIndex: $selectedSegment)
            }
        }
    }
}

Since we want to lay out our content horizontally, we start out with an HStack, and within it, we loop through the labels, and create a PickerButton for each, providing it its title and index, and also passing in the binding to the active index.

If you create this picker in a project or playground, you’ll end up with the three buttons arranged horizontally on a single line. Here’s a sample implementation of the contentview in such a project, with some padding and spacers to make it look nice:

struct ContentView: View {
    @State private var selectedSegment: Int = 1

    var body: some View {
        VStack {
            Spacer()
            SegmentedPicker(selectedSegment: $selectedSegment, labels: ["Option 1", "Option 2", "Option 3"], markerHeight: 10)
                .padding()
            Spacer()
            Text("Current Option: \(selectedSegment + 1)")
            Spacer()
        }
    }
}

Feel free to try this out.

Tapping a button will set the picker’s selectedSegment to its index, but the picker won’t yet show the selected segment, so that’s the first addition we’ll make to the code above.

The SegmentedPicker will be responsible for drawing the beam underneath the selected segment. For this it needs to know the bounds of each segment. In SwiftUI we can use an anchorPreference on the PickerButton to communicate these bounds back up the view hierarchy. (We’ll also use these bounds to figure out the total width of the segments, and then compare that total to the width of the container view, but more on that later.)

Before we can add an anchorPreference to the PickerButton, we need to define a PreferenceKey to hold the bounds information.

struct ButtonPreferenceData {
    let buttonIndex: Int
    var bounds: Anchor<CGRect>? = nil
}

struct ButtonPreferenceKey: PreferenceKey {
    static var defaultValue: [ButtonPreferenceData] = []

    static func reduce(value: inout [ButtonPreferenceData], nextValue: () -> [ButtonPreferenceData]) {
        value.append(contentsOf: nextValue())
    }
}

The ButtonPreferenceData struct will hold the info we need: the index of the button (segment), and its bounds. The reduce function in the ButtonPreferenceKey ensures we collect this data from all segments.

To actually collect this data we need to add an anchorPreference to PickerButton. Making the whole (and final) version of PickerButton look like this:

struct PickerButton: View {
    var index: Int
    let title: String
    @Binding var selectedButtonIndex: Int

    var body: some View {
        Button(action: {
            selectedButtonIndex = index
        }) {
            Text(title).fixedSize()
        }
        .padding(10)
        .anchorPreference(key: ButtonPreferenceKey.self, value: .bounds, transform: {
            [ButtonPreferenceData(buttonIndex: index, bounds: $0)]
        })
    }
}

In the anchorPreference we create an instance of ButtonPreferenceData and fill it with the index and bounds of the segment it represents. Finally, we return it wrapped in an array, since that is what our implementation of the reduce function expects.

If you did create a project and ran the code earlier, as we encouraged you to do, and saw the three segments, then, if you run the project again now, you will find nothing has changed visually. All we did was add in the plumbing that will enable us to draw the selection indicator, and figure out whether to display a compact or a regular view.

Let’s start with implementing the selection indicator. For that we need to get access to the data we collected in the anchorPreference on the PickerButton. We do that by adding a backgroundPreferenceValue view after the frame modifier in the body of SegmentedPicker, where we use a GeometryReader to obtain the bounds of the picker’s container. Once we have that, we pass it, along with the collected preference data, to a function that will either return an indicator view or an empty view if it can’t find a selection. We also need a height for the indicator. For that we’ll add a user accessible instance variable, with a default value. If you run the project with the incarnation of SegmentedPicker shown below, you’ll now see a selection indicator. If you tap another segment, the indicator animates into place to indicate the changed selection.

struct SegmentedPicker: View {
    @Binding var selectedSegment: Int
    var labels: [String]
    var markerHeight: CGFloat = 6

    var body: some View {
        return HStack {
            ForEach(0 ..< labels.count, id: \.self) {
                PickerButton(index: $0, title: labels[$0], selectedButtonIndex: $selectedSegment)
            }
        }
        .backgroundPreferenceValue(ButtonPreferenceKey.self) { preferences in
            GeometryReader { containerGeometry in
                showSelectionIndicator(containerGeometry: containerGeometry, preferences: preferences)
            }
        }
    }

    private func showSelectionIndicator(containerGeometry: GeometryProxy, preferences: [ButtonPreferenceData]) -> some View {
        if let preference = preferences.first(where: { $0.buttonIndex == self.selectedSegment }) {
            let anchorBounds = preference.bounds
            let bounds = containerGeometry[anchorBounds]
            return AnyView(RoundedRectangle(cornerRadius: markerHeight / 2)
                            .fill()
                            .foregroundColor(Color.yellow)
                            .frame(width: bounds.width, height: markerHeight)
                            .offset(x: bounds.minX, y: bounds.maxY)
                            .animation(.easeInOut(duration: 0.33)))
        } else {
            return AnyView(EmptyView())
        }
    }
}

To know where to place the selection indicator, we first extract the selected segment from the preferences we received. Then we access its bounds, and resolve the value of the anchor to the container view. Now that we have the bounds in the correct coordinate space, building the indicator is fairly straightforward.

With the code above you now have a fully functioning custom segmented picker, which supports dark mode and dynamic font sizes out of the box. However, on smaller screens, and even on iPads when multitasking with a split screen, and, of course, depending on the number of segments and the length of their titles, it may we happen that the outer segments get clipped if the available screen real estate is not wide enough. When this happens we want the picker to morph into a more compact, popup style control, which only shows the selected option, and allows the user to choose another option by tapping and holding the button, and then selecting from the menu that pops up.

To know when to show the popup style instead of the regular style we need to figure out the total width of all the segments (including their padding), and compare that to the width available to the picker. Fortunately, we already have the bounds of each segment, which we needed earlier for displaying the selection indicator. So there’s no need to add any code for that. Yay! But we do need to gain access to the picker’s surrounding geometry so that we can decide on which view to show. We will change the body of the picker view to gather geometry info, and will pass that along to helper functions so that they can make informed decisions.

In SegementedPicker replace the definition of the body with the following

    var body: some View {
        GeometryReader { stackProxy in
            appropriateView(stackProxy: stackProxy)
        }
    }

We now return a GeometryReader, which gives us access to data like the bounds available to the picker. We will modularize the code into small helper functions that return some appropriate view. This modularization not only helps in keeping the code clear and manageable, it also helps a very picky compiler, prone to complain about returning views of different types.

When deciding which view to show—the regular one, or the compact one—we immediately run into a challenge: we can’t measure the required width before building the view, and then just show the appropriate one, as we could have done in UIKit. We have to actually start building the regular view tree, and then switch to the compact view if we find the regular view won’t fit. But when we re-render the tree for the compact view, we cannot know the required width, so really we need to first build the regular view every time. But then, if we switch to compact again, we end up with an infinite loop. We need to break this cycle by only showing the compact view if we just re-entered from a regular pass through the layout process, and otherwise build the regular view. This ensures that, if we are showing the compact view, we will always re-evaluate by trying to build the regular view.

To keep track of whether we just switched to the compact view in the previous layout pass, we add an instance variable to the picker. We use this variable to decide which view to return:

    private func appropriateView(stackProxy: GeometryProxy) -> some View {
        if justSwitchedToCompactView {
            justSwitchedToCompactView = false
            return AnyView(compactView(stackProxy: stackProxy))
        } else {
            return AnyView(regularView(stackProxy: stackProxy))
        }
    }

Unfortunately, as this is a struct, we can’t easily modify this variable, as the compiler will complain that self is not modifiable. We could turn it into a @State variable, but then changing it would always trigger a new layout pass, which wouldn’t work either. Yet we need to ensure the variable becomes modifiable from within the SegmentedPicker. Custom property wrappers to the rescue:

@propertyWrapper class Modifiable<Value> {
    private(set) var value: Value
    
    init(wrappedValue: Value) {
        self.value = wrappedValue
    }
    
    var wrappedValue: Value {
        get { value }
        set { value = newValue }
    }
}

The above property wrapper allows us to easily add a modifiable instance variable to a struct:

struct SegmentedPicker: View {
    @Modifiable private var justSwitchedToCompactView: Bool = false

}

Now the compiler is happy, and so are we. :-)

Next, let’s first get the implementation of the compact view out of the way, since it is more straightforward than the regular view’s implementation.

    private func compactView(stackProxy: GeometryProxy) -> some View {
        let validMenuItems = labels.filter {
            $0 != labels[selectedSegment]
        }

        return HStack {
            Text(labels[selectedSegment]).foregroundColor(Color(UIColor.label))
            Image(systemName: "arrowtriangle.down.circle").foregroundColor(Color(UIColor.label))
        }
        .padding()
        .background(Color(UIColor.systemBackground))
        .cornerRadius(12)
        .contextMenu {
            ForEach(validMenuItems, id: \.self) { menuItem in
                Button(menuItem) {
                    selectedSegment = labels.firstIndex(of: menuItem)!
                }
            }
        }
        .frame(width: stackProxy.size.width, height: stackProxy.size.height, alignment: .center)
    }

We do not want to show the currently selected option in the menu, so we first create an array with the selected segment filtered out. We show the selected segment along with a little icon that suggests you can tap it, through a Text and an Image wrapped in an HStack. We provide some formatting modifiers so that it looks right, especially when the popup menu is shown (and make sure it plays nicely with both dark and light mode by using the systemBackground color). Finally we add the contextMenu modifier, where we build the menu options by cycling through the valid menu items.

With the compact view covered, we are ready to focus on the regular view. Most of the code here you’ve seen before:

    private func regularView(stackProxy: GeometryProxy) -> some View {
        return HStack {
            ForEach(0 ..< labels.count, id: \.self) {
                PickerButton(index: $0, title: labels[$0], selectedButtonIndex: $selectedSegment)
            }
        }
        .frame(width: stackProxy.size.width, height: stackProxy.size.height, alignment: .center)
        .backgroundPreferenceValue(ButtonPreferenceKey.self) { preferences in
            processPreferenceValue(containerGeometry: stackProxy, preferences: preferences)
        }
    }

The regularView function builds the segments using an HStack, looping through all labels to create a PickerButton for each. Each button is provided with its title, its index and a binding to the selectedSegment, so that it can communicate a new selection back up when it is tapped. We make sure the HStack is centered within its parent view by setting its frame size to equal the parent’s size.

Now it’s time to figure out if the regular view fits its parent, and take action accordingly. Triggering a new layout pass if it doesn’t fit, or showing a selection indicator beneath the selected segment if it does.

This happens within the backgroundPreferenceValue modifier, where we have access to the collected geometry data for each segment. The code is extracted into the processPreferenceValue function to keep our code nicely modularized, as well as to help the compiler figure out the view types.

    private func processPreferenceValue(containerGeometry: GeometryProxy, preferences: [ButtonPreferenceData]) -> some View {
        let fits = fitsContainer(containerGeometry: containerGeometry, preferences: preferences)
        switchViewIfNeeded(fits: fits)

        return Group {
            if  fits {
                showSelectionIndicator(containerGeometry: containerGeometry, preferences: preferences)
                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
            } else {
                EmptyView()
            }
        }
    }

processPreferenceValue first checks if the regular view fits its container. Then it calls a function that may trigger a new layout pass, depending on its current state and the state it needs to be in. Finally, if the regular view doesn’t fit, it will return an empty view, since we don’t need to show anything when in compact mode, and, if the regular view does fit, it will return a selection indicator. We need to envelop the returned views in a Group to keep the compiler happy. (The alternative being to return the views wrapped in an AnyView, but, for performance reasons, using a group is preferable where possible.)

The implementation of fitsContainer collects the combined width of each segment in the reduce function, and returns a bool indicating whether that width fits within the container:

    private func fitsContainer(containerGeometry: GeometryProxy, preferences: [ButtonPreferenceData]) -> Bool {
        let requiredWidth = preferences.reduce(0) { (result, pref) -> CGFloat in
            let anchorBounds = pref.bounds
            let bounds = containerGeometry[anchorBounds]
            return result + bounds.width
        }

        return requiredWidth <= containerGeometry.size.width
    }

On to the implementation of switchViewIfNeeded:

    private func switchViewIfNeeded(fits: Bool) {
        if fits {
            justSwitchedToCompactView = false
            if !allFit {
                DispatchQueue.main.async {
                    allFit = true
                }
            }
        } else {
            if allFit {
                justSwitchedToCompactView = true
                DispatchQueue.main.async {
                    allFit = false
                }
            }  else {
                justSwitchedToCompactView = false
                DispatchQueue.main.async {
                    allFit = true
                }
            }
        }
    }

If the regular view fits, we check our allFit state variable, and set it to true if needed, which will cause the view tree to be re-rendered. We set justSwitchedToCompactView to false, since we are not rendering the compact view. We cannot interrupt the layout process at this point without SwiftUI landing us in undefined behaviour territory, so we need to trigger the new layout on the next iteration through the run-loop, by wrapping the code in an async call on the main queue.

If the regular view doesn’t fit, we check whether we were already showing the compact view. If we were not, we are switching to the compact view, so we set justSwitchedToCompactView to true, and set allFit to false, to trigger a redraw.

If at this point the compact view was already being shown, the required width has not been calculated, and we need to trigger a new layout pass, that will first try to render the regular view. So we, unintuitively, have to set allFit to trueto trigger this new pass. (And we ensure justSwitchedToCompactView is set to false, since we were already showing the compact view, and, more importantly, otherwise the path to try to render the regular view, in the appropriateView function, will not be taken.)

With all this in place, we now have a fully functioning segmented control that adapts its display mode to the space it’s given, depending on the space its regular view needs.

You can find the full source code here.

]]>
<![CDATA[A segmented control, built in SwiftUI, that doubles as a popup menu when space is tight.]]>
Fractions2020-03-18T00:00:00+00:002020-03-18T00:00:00+00:00https://sintraworks.github.io/fractions/math/swift/2020/03/18/fractions<![CDATA[

Sometimes, using floating point arithmetic (e.g using float or double types) just doesn’t cut it. Floating point values cannot represent all values accurately, and if you start adding/subtracting/multiplying/dividing such values it is very likely the inacurracies quickly exacerbate into an unworkable mess. Depending on the domain you’re working on, different solutions can be appropriate. E.g, if you’re working with currency, you might need a type representing decimal numbers, or, if you’re working with musical timelines or scores, especially where tuplets (e.g. triplets) come into the mix, a type accurately representing any fraction may be called for. Here we will look into the latter: a type where each instance represents a fraction. We want to be able to perform basic arthmetic calculations on those numbers.

Since we’re being Swifty, but more importantly, since we’re going to represent fairly basic values, and care about (im)mutability, we’ll use a value type to represent our fractions: a struct.

Fractions have just two properties: a denominator –representing the value’s base unit– and a numerator –expressing how many units the value considers–.

The result:

struct Fraction {
    var numerator: Int
    var denominator: Int
}

What’s next? We want to be able to instantiate instances of fractions. Since we are using Swift structs, we do not need to write an initializer, since one will be automatically synthesized for us. But we do want to do perform some basic input checking in our initializer, to ensure we end up with a reasonably safe instance. For starters, division by zero is illegal. We therefore need to check that the denominator is not zero. Secondly, since we will implement an essential function that flips negative values to their positive counterpart, we cannot allow the use of Int.min, and have to check that parameters > Int.min. We could create a throwing initializer, and throw an error when invalid parameter values are provided, but I think that is overkill for the task at hand, and might make the initializer less convenient at the point of use. Instead, we’ll make the initializer failable:

init?(numerator: Int, denominator: Int) {
    guard denominator != 0 else { return nil }
    guard numerator > Int.min, denominator > Int.min else { return nil }

    self.numerator = numerator
    self.denominator = denominator
}

Now, before we start implementing arithmetic operations on the type, lets get a useful utility out of the way. It is likely we will encounter occasions where we need to convert our fraction into a floating point representation:

var doubleValue: Double {
    Double(numerator) / Double(denominator)
}

var floatValue: Float {
    Float(doubleValue)
}

(Remember: in Swift 5.1 and later we do not have to write return when the function body consists of a single expression.)

We also want a compact way to print our fractions:

var description: String {
    "\(numerator)/\(denominator)"
}

With that out of the way, it’s time to implement our first operation: addition. We want both mutating and nonmutating variants.

mutating func add(_ other: Fraction) {
    if (denominator == other.denominator) {
        numerator += other.numerator
    } else {
        numerator = numerator * other.denominator + other.numerator * denominator
        denominator = denominator * other.denominator
    }
}

func adding(_ other: Fraction) -> Fraction {
    var copy = self
    copy.add(other, reducing: reducing)
    return copy
}

Now we can create two fractions and add them together:

var f2_4 = Fraction(numerator: 2, denominator: 4)!
let f1_2 = Fraction(numerator: 1, denominator: 2)!
f2_4.add(f1_2)
f2_4.description // 8/8

Nice! The result is correct. But it is probably not the value we were looking for. It is desirable to be able to reduce fractions to their GCD (Greatest Common Denominator/Divisor). To enable us to do that, we implement a function we’ll, unsurprisingly, call reduce:

mutating func reduce() {
    var u = numerator
    var v = denominator

    // Euclid's solution to finding the Greatest Common Denominator
    while (v != 0) {
        (v, u) = (u % v, v)
    }

    numerator /= u
    denominator /= u
}


func reduced() -> Fraction {
    var copy = self
    copy.reduce()
    return copy
}

We change the add functions to take a parameter indicating whether we want to reduce the result of the operation before returning it. We’ll assume we usually want to do that, so we default to true.

mutating func add(_ other: Fraction, reducing: Bool = true) {
    if (denominator == other.denominator) {
        numerator += other.numerator
    } else {
        numerator = numerator * other.denominator + other.numerator * denominator
        denominator = denominator * other.denominator
    }
    if ( reducing ) {
        self.reduce()
    }
}

Now, lets run the sample addition above again:

var f2_4 = Fraction(numerator: 2, denominator: 4)!
let f1_2 = Fraction(numerator: 1, denominator: 2)!
f2_4.add(f1_2)
f2_4.description // 1/1

Great! And if we want to prevent the reduction:

f2_4.add(f1_2, reducing: false)
f2_4.description // 8/8

There’s a problem lurking in our reduce implementation though. You’ll discover that, if you write unit tests that exercise that function with fractions that contain negative values:

var f1 = Fraction(numerator: -3, denominator: 15)!
f1.reduce()
f1.description // 1/-5

The correct answer should be -1/5! Looks like we need to refine our reduce function.

mutating func reduce() {
    let (absNumerator, numeratorSign) = numerator < 0 ? (-numerator, -1) : (numerator, 1)
    let (absDenominator, denominatorSign) = denominator < 0 ? (-denominator, -1) : (denominator, 1)

    var u = absNumerator
    var v = absDenominator

    // Euclid's solution to finding the Greatest Common Denominator
    while (v != 0) {
        (v, u) = (u % v, v)
    }

    numerator = absNumerator / u * numeratorSign
    denominator = absDenominator / u * denominatorSign
}

We take the absolute values of the numerator and denominator and perform the reduction on these. When done we re-establish the signedness of each, thus ensuring a correct result:

var f1 = Fraction(numerator: -3, denominator: 15)!
f1.reduce()
f1.description // -1/5

The implementations for subtraction, multiplication and division follow the same pattern as the implementation for addition. You can check out the actual implementations in the project’s source. We won’t discuss them here.

So, as we saw above, now we can write

var f2_4 = Fraction(numerator: 2, denominator: 4)!
let f1_2 = Fraction(numerator: 1, denominator: 2)!
f2_4.add(f1_2) // or: let result = f2_4.adding(1_2)

but wouldn’t it be nicer if we could write

let result = f2_4 + f1_2

Yes? Then let’s make that possible by overloading the operators. I think this is a domain where doing that makes sense. And, since the implementations are very compact, I’ll list them all here:

func + (lhs: Fraction, rhs: Fraction) -> Fraction {
    lhs.adding(rhs)
}

func - (lhs: Fraction, rhs: Fraction) -> Fraction {
    lhs.subtracting(rhs)
}

func * (lhs: Fraction, rhs: Fraction) -> Fraction {
    lhs.multiplying(by: rhs)
}

func / (lhs: Fraction, rhs: Fraction) -> Fraction {
    lhs.dividing(by: rhs)
}

This allows us to write:

var f2_4 = Fraction(numerator: 2, denominator: 4)!
let f1_2 = Fraction(numerator: 1, denominator: 2)!
let result = f2_4 + 1_2 // 1/1

Still missing is the ability to compare fractions, so let’s turn our attention to that: To enable comparisons we must conform our Fraction type to the Comparable protocol. This involves implementing two functions: == and <. The implementations are fairly standard and simple, but there is one gotcha: Fractions with minus signs in different positions may still be equal. For instance, 1/-4 represents the same value as -1/4; and -1/-4 is the same as 1/4. To account for that we need to ‘normalize’ these fractions to facilitate the implementation of the comparison functions. The rules for normalization are provided as documentation to the normalization methods below:

extension Fraction: Comparable {
    /// Fractions with two negative signs are normalized to two positive signs.
    /// Fractions with negative denominator are normalized to positive denominator and negative numerator.
    mutating func normalize() {
        if numerator >= 0 && denominator >= 0 { return }
        if (numerator < 0 && denominator < 0) || (denominator < 0) {
            numerator *= -1
            denominator *= -1
        }
    }

    /// Fractions with two negative signs are normalized to two positive signs.
    /// Fractions with negative denominator are normalized to positive denominator and negative numerator.
    func normalized() -> Fraction {
        var copy = self
        copy.normalize()
        return copy
    }

With these functions in place, we can safely implement the comparison functions.

static func == (lhs: Fraction, rhs: Fraction) -> Bool {
    let lhsRed = lhs.reduced().normalized()
    let rhsRed = rhs.reduced().normalized()

    return lhsRed.numerator == rhsRed.numerator && lhsRed.denominator == rhsRed.denominator
}

static func < (lhs: Fraction, rhs: Fraction) -> Bool {
    let lhsRed = lhs.reduced().normalized()
    let rhsRed = rhs.reduced().normalized()

    let lhsNominatorProduct = lhsRed.numerator * rhsRed.denominator
    let rhsNominatorProduct = rhsRed.numerator * lhsRed.denominator

    return lhsNominatorProduct < rhsNominatorProduct
}

There’s only one issue left now. If we operate on fractions with a negative denominator, the results may surprise us. Which is a euphemistic way of saying that the results may well be incorrect. As the code currently stand, the following unit test will fail:

var f1_m4 = Fraction(numerator: 1, denominator: -4)!
let f2_4 = Fraction(numerator: 2, denominator: 4)!
f1_m4.add(f2_4)
XCTAssertTrue(f1_m4.numerator == 1 && f1_m4.denominator == 4, "Incorrect result adding \(f2_4.description) to 1/-4. Expected 1/4, got \(f1_m4.description)")

The solution to getting the test to pass is to normalize the Fractions before operating on them, as we did in the implementation providing the Comparable protocol conformance. So now, the implementation of add becomes:

    mutating func add(_ other: Fraction, reducing: Bool = true) {
        self.normalize()
        let normalizedOther = other.normalized()

        if (denominator == normalizedOther.denominator) {
            numerator += normalizedOther.numerator
        } else {
            numerator = numerator * normalizedOther.denominator + normalizedOther.numerator * denominator
            denominator = denominator * normalizedOther.denominator
        }
        if ( reducing ) {
            self.reduce()
        }
    }

And that’s it. We now have a basic implementation of a type that allows us to easily perform calculations on fractions, without loss of precision.

You can find the full source code, including implementations for the remainder of the arithmetic operators, and unit tests, here.

]]>
<![CDATA[A Swift type to operate on Fractions without loss of precision.]]>
Observers as an alternative to delegates2019-07-07T00:00:00+00:002019-07-07T00:00:00+00:00https://sintraworks.github.io/swift/2019/07/07/observables-1<![CDATA[

Recently I started studying Ray Wenderlich’s tutorial book RxSwift. Early on, the book suggests using Rx as an alternative to delegates and their associated protocols. I though, great, but, for the task at hand, that’s a rather heavy handed solution. (Yes, I know it is just an example designed to teach me the basics.) So I tried to imagine how I could achieve something similar to the functionality they built for their initial examples, without the overhead of importing a big library like RxSwift.

It quickly occured to me that the concept of a light-weight observable was a good avenue to explore. So I jotted down the following:

protocol Observable {
    associatedtype Observation

    var observer: Observer<Observation>? { get set }
}

Objects that want to be observable should conform to the Observable protocol. The thing they offer for observation is defined by typealiasing its associated type Observation. They must also implement a property holding an observer which will be triggered whenever the observable wants to convey an update.

Ideally, I would want an observer to be any object conforming to an Observer protocol. Like this:

protocol Observer {
    associatedtype Observation

    func observe(_ thing: Observation) {
        // …handle thing…
    }
}

Just like in the delegate pattern, the observer—the object interested in observing an Observable—would pass itself as the observer, defining a function named observe, to be called by the Observer, and which processes the thing it receives.

But you can’t do that in Swift. Protocols with associated types can only be used as generic constraints, not as first class types. If you try to implement this, the compiler will very kindly present you with an error:

class MyClass: Observer {
    typealias Observation = Thing

    func observe(_ thing: Observation) {
        // …handle thing…
    }
}

// error: Protocol 'Observer' can only be used as a generic constraint
// because it has Self or associated type requirements.

So, instead, we use an intermediate object with a generic type:

final class Observer<Observation> {
    let handler: (Observation) -> Void

    func observe(_ thing: Observation) {
        handler(thing)
    }
}

The Observable object defines an observer property, to be called into action when something is to be oserved. The observing object creates anObserver and passes it to the Observable. The Observer holds a handler, defined by the object that is requesting the observation. To be able to write the handler we need to know the type of Observation, the actual thing we are observing. To that end we typealias Observation in the Observable object to whatever we offer for observation:

class ObservableViewController: UIViewController, Observable {
    typealias Observation = Int

    var observer: Observer<Observation>?
	
}

In this case the observing object has to provide an observer whose generic type is an Int. You could also typealias Observation = Void, and provide an observer whose handler doesn’t take any arguments.

But here is a very cool thing you can do: rather than define some delegate protocol, which, in (pure) Swift, can’t have optional functions, we can use an enum that contains all the actions we want to offer for observation, and, since it is an enum, on the observing side, we do not need to handle all cases. Only the ones we are interested in. Here’s a trivial example:

class ObservableViewController: UIViewController, Observable {
    enum Action {
        case button1Tapped
        case button2Tapped(message: String)
    }

    typealias Observation = Action

    var observer: Observer<Observation>?
	
}

This has a downside, of course, since the observable might want to require certain actions to be implemented. In that case, we could choose to fall back to using a delegate protocol. But this construct can be a handy tool in our toolbox, for when we want to define a set of optional functions. What I also like is that it concentrates the API in one clearly defined place, both in the Observer and the Observee. And, compared to just using closures for each option (another alternative to using the delegate pattern with a delegate protocol), there’s the advantage of labeled parameters, which the closures don’t allow. (The full source code, linked to below, includes examples of both types of implementation so that you can easily compare the resulting code.)

When the observable has something to convey to the observer, it will call its handler: observer?.observe(.button1Tapped), or observer?.observe(.button2Tapped(message: "Remote action on button 2")).

How does that look on the observing side?

class ObservingViewController: UIViewController {
    var history = [String]()
    
    @IBAction func showObservableViewController() {
        guard let vc = setupObservableViewController() else { return }
        self.navigationController?.pushViewController(vc, animated: true)
    }

    private func setupObservableViewController() -> UIViewController? {
        guard let vc = self.storyboard?.instantiateViewController(withIdentifier: "ObservableViewController") as? ObservableViewController else { return nil }

        vc.observer = Observer<ObservableViewController.Action> { [weak self] (action) in
            guard let self = self else { return }

            switch action {
            case .button1Tapped:
                self.history.append("\(self.history.count + 1) Remote action on button 1")
            case .button2Tapped(let message):
                self.history.append("\(self.history.count + 1) " + message)
            }
        }

        return vc
    }
}

When we set up the observable view controller, we provide it an Observer. The Observer class has an initializer that allows us to include a handler that receives the Observation (action). Our handler switches over it, handling only the cases it is interested in. Finally, we push the observable view controller onto the navigation stack, and, whenever it calls our handler, our code processes the changes.

This pattern can also be used to turn UIControls into lightweight reactive objects. You can read all about that in the next post.

You can find the full source code here (including a MultiObservable protocol, for observables that can service multiple observers, and a couple of observable UIControl derivatives).

]]>
<![CDATA[How to replace delegates with light-weight observables.]]>
Observable controls2019-07-07T00:00:00+00:002019-07-07T00:00:00+00:00https://sintraworks.github.io/swift/2019/07/07/observables-2<![CDATA[

Continuing on our previous post, let’s create some observable controls, and a view controller that observes another view controller.

Let’s say we want a button that we can observe. Rather than by setting a target and a selector and a control event, as we do traditionally, like this:

control.addTarget(bridge, action: selector, for: .touchUpInside)

…where we have to define a method whose selector we pass to the control, we want to be able to add observers like this:

button1.addObserver(for: .touchUpInside, handler: { [weak self] in
            self.button1Pressed()
        })

We pass only the control event(s) we are interested in and the corresponding handler to be called when an event occurs.

How do we go about designing such a control?

Well, there is an issue that we need to get out of the way first: Since we want to observe a UIControl, and eventually have to add a target and provide a selector, we need to have functions that can serve as selectors. Meaning, they have to be visible from Objective-C. Since objects sporting generics aren’t visible from ObjC, we need a bridge that provides the crossing from Swift to ObjC. This bridge will provide for all the selectors that are meaningful to a UIControl, and it will hold a reference to the actual control so that it can pass actions through to it.

@objc
final private class ObjCSelectorBridge: NSObject {
    private let control: UIControl

    private lazy var eventSelectors: [UIControl.Event.RawValue: Selector] = [
        UIControl.Event.touchDown.rawValue: #selector(touchDown),
        UIControl.Event.touchDownRepeat.rawValue: #selector(touchDownRepeat),
        UIControl.Event.touchDragInside.rawValue: #selector(touchDragInside),
        UIControl.Event.touchDragOutside.rawValue: #selector(touchDragOutside),
        UIControl.Event.touchDragEnter.rawValue: #selector(touchDragEnter),
        UIControl.Event.touchDragExit.rawValue: #selector(touchDragExit),
        UIControl.Event.touchUpInside.rawValue: #selector(touchUpInside),
        UIControl.Event.touchUpOutside.rawValue: #selector(touchUpOutside),
        UIControl.Event.touchCancel.rawValue: #selector(touchCancel),
        UIControl.Event.valueChanged.rawValue: #selector(valueChanged),
        UIControl.Event.primaryActionTriggered.rawValue: #selector(primaryActionTriggered),
        UIControl.Event.editingDidBegin.rawValue: #selector(editingDidBegin),
        UIControl.Event.editingChanged.rawValue: #selector(editingChanged),
        UIControl.Event.editingDidEnd.rawValue: #selector(editingDidEnd),
        UIControl.Event.editingDidEndOnExit.rawValue: #selector(editingDidEndOnExit)
    ]

    required init(with control: UIControl) {
        self.control = control
    }

    func selectors(for controlEvents: UIControl.Event) -> [(UIControl.Event, Selector)] {
        var selectors = [(UIControl.Event, Selector)]()
        for event in controlEvents {
            if let selector = eventSelectors[event.rawValue] {
                selectors.append((event, selector))
            }
        }

        return selectors
    }
}

We initialize the bridge passing in the relevant UIControl instance, and the bridge knows how to map between control events and selectors. “But, where are the selectors?” you say? Well, we put them in an extension on the bridge:

extension ObjCSelectorBridge {
    @objc fileprivate func touchDown() {
        control.sendActions(for: .touchDown)
    }

    @objc fileprivate func touchDownRepeat() {
        control.sendActions(for: .touchDownRepeat)
    }

    etc

    @objc fileprivate func editingDidEndOnExit() {
        control.sendActions(for: .editingDidEndOnExit)
    }
}

These are the reason we need the bridge. They have to be visible from Objective-C, so that the UIControl can call them.

You may have noticed that we are actually iterating over controlEvents. This is an Optionset (of type UIControl.Event). How’s that possible? Optionsets are not Collections. Well, there’s a trick for that, but its implementation is not relevant to the subject at hand. You can check it out in the full implementation, available in the repo.

Now that we have the bridge in place, we can define a class that will observe a UIControl:

final internal class ControlObserver<Observation>: NSObject {
    internal class EventObserver {
        let observer: Observer<Observation>
        var controlEvents: UIControl.Event

        init(observer: Observer<Observation>, controlEvents: UIControl.Event) {
            self.observer = observer
            self.controlEvents = controlEvents
        }
    }

    private var bridge: ObjCSelectorBridge
    private weak var control: UIControl?
    internal var eventObservers = [EventObserver]()

    required init(with control: UIControl) {
        self.control = control
        self.bridge = ObjCSelectorBridge(with: control)
    }
}

ControlObservers have an associated generic type, so that we can decide what they actually observe. We use a nested helper class that holds information about the observer and the events it responds to. It sets up an empty array of EventObservers, and creates and holds on to a bridge object upon initialization, where it also stores the control it will be observing.

In an extension on ControlObserver we put the utility functions for managing observers. We’ll list the two most important ones:

When adding an observer we request the relevant selectors for the passed in control events. Then we loop through those events and for each add an action (selector) to the control. Finally we create an EventObserver that stores the handler and the applicable control events:

internal func addObserver(for controlEvents: UIControl.Event, handler: @escaping (Observation) -> Void) {
        guard let control = control else { return }
        let eventsAndSelectors = bridge.selectors(for: controlEvents)

        for (controlEvent, selector) in eventsAndSelectors {
            control.addTarget(bridge, action: selector, for: controlEvent)
        }

        let eventObserver = EventObserver(observer: Observer<Observation>(handler: handler), controlEvents: controlEvents)
        eventObservers.append(eventObserver)
    }

We allow removing all observers for a specific control event, or set of control events:

internal func removeObservers(for controlEvents: UIControl.Event) {
        if controlEvents == .allEvents {
            eventObservers.removeAll()
            return
        }

        for event in controlEvents {
            // Remove all observers whose controlEvents match exactly
            eventObservers.removeAll(where: { $0.controlEvents.rawValue == event.rawValue })
            // For those that have more events, and can't be deleted yet, we remove the specific event.
            for eventObserver in eventObservers {
                if eventObserver.controlEvents.contains(event) {
                    eventObserver.controlEvents.remove(event)
                }
            }
        }

        // Finally, we remove the action for the passed in set of control events.
        control?.removeTarget(bridge, action: nil, for: controlEvents)
    }

We also allow retrieving all observers, querying for observers for specific control events, or querying the registered actions. The implementations of these can be found in the full source code.

So, to take stock: we now have a bridge to the Objective-C world, and we have an object that can observe a control. What we need next is a control that can be observed. For that, we create a protocol called—what else?—ObservableControl:

protocol ObservableControl {
    associatedtype Observation

    var controlObserver: ControlObserver<Observation> { get }

    func addObserver(for controlEvents: UIControl.Event, handler: @escaping (Observation) -> Void)
    func removeObservers(for controlEvents: UIControl.Event)

    func sendActions(for controlEvents: UIControl.Event)

    func actions(for controlEvents: UIControl.Event) -> [String]?
    func observers(for controlEvents: UIControl.Event) -> [Observer<Observation>]
    var observers: [Observer<Observation>] { get }
}

We need a ControlObserver, of course, to do the actual observing, and we define an API for adding, removing and monitoring observers, and also for the actual interactions.

We provide default implementations for the bulk of the requirements, to avoid having to duplicate code all over the place:

extension ObservableControl {
    func addObserver(for controlEvents: UIControl.Event, handler: @escaping (Observation) -> Void) {
        controlObserver.addObserver(for: controlEvents, handler: handler)
    }

    func removeObservers(for controlEvents: UIControl.Event) {
        controlObserver.removeObservers(for: controlEvents)
    }

    func observers(for controlEvents: UIControl.Event) -> [Observer<Observation>] {
        return controlObserver.observers(for: controlEvents)
    }

    var observers: [Observer<Observation>] {
        return controlObserver.observers
    }

    func actions(for controlEvent: UIControl.Event) -> [String]? {
        return controlObserver.actions(for: controlEvent)
    }
}

These simply provide a pass-through to the equivalent interface calls of the ControlObserver.

Now we are finally ready to to implement a concrete observable control. Here’s a button:

final class ObservableButton: UIButton, ObservableControl {
    typealias Observation = Void

    lazy internal var controlObserver = ControlObserver<Observation>(with: self)

    override func sendActions(for controlEvents: UIControl.Event) {
        for event in controlEvents {
            for eventObserver in controlObserver.eventObservers {
                guard eventObserver.controlEvents.contains(event) else { continue }
                eventObserver.observer.observe(())
            }
        }
    }
}

So, it’s a proper UIButton, but one that conforms to ObservableControl. As you can see, there’s not much left to do here:

  • We define the type of action to observe: in this case Void since all we are interested in is that the observer is called (for the specific control event it was registered with). There is no value that we are interested in.
  • We create and hold on to a control observer, generic on the type we just defined.
  • We pass triggered actions through to any relevant observers that are registered with us.

How about a slider?

final class ObservableSlider: UISlider, ObservableControl {
    enum Action {
        case isTrackingTouch(_ flag: Bool)
        case valueChanged(_ value: Float)
    }

    typealias Observation = Action

    lazy var controlObserver = ControlObserver<Observation>(with: self)

    override func sendActions(for controlEvents: UIControl.Event) {
        for event in controlEvents {
            for eventObserver in controlObserver.eventObservers {
                guard eventObserver.controlEvents.contains(event) else { return }
                switch controlEvents {
                case .touchDown:
                    eventObserver.observer.observe(.isTrackingTouch(true))
                case .valueChanged:
                    eventObserver.observer.observe(.valueChanged(value))
                case .touchCancel, .touchUpInside, .touchUpOutside, .touchDragOutside, .touchDragExit:
                    eventObserver.observer.observe(.isTrackingTouch(false))
                default:
                    break
                }
            }
        }
    }
}

This one is slightly more interesting, because now we are interested in a value: we want to know when the value has changed, and, in this particular implementation, we also want to know when the slider is tracking a touch. For this we create an enum that provides the state changes we are interested in: isTrackingTouch and valueChanged. These cases each have an associated value: isTrackingTouch has a flag indicating whether tracking has started or ended, and valueChanged has the current value of the slider.

So, now that we have all the pieces in place, how does this look in actual use? Well, to observe a button:

class MyViewController {
    let button = ObservableButton(type: .custom)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupButtonObservers()
    }

    private func setupButtonObservers() {
        button.addObserver(for: .touchDown, handler: { [weak self] in
            self?.enterPanicMode()
        })

        button.addObserver(for: .touchUpInside, handler: { [weak self] in
            self?.exitPanicMode()
        })
    }
}

To observe a slider:

class MyViewController, MultiObservable {
    enum Action {
        
        case sliderValueChanged(newValue: Float)
        case sliderIsTrackingTouch(_ flag: Bool)
        
    }

    private var progressSlider = ObservableSlider()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupSliderObserver()
    }

    private func setupSliderObserver() {
        progressSlider.addObserver(for: [
            .valueChanged, .touchDown, .touchUpInside,
            .touchUpOutside, .touchCancel, .touchDragExit
        ]) { [weak self] (action) in
            guard let self = self else { return }

            switch action {
            case .valueChanged(let newValue):
                for observer in self.observers {
                    observer.observe(.sliderValueChanged(newValue: newValue))
                }
            case .isTrackingTouch(let isTracking):
                for observer in self.observers {
                    observer.observe(.interactiveStateChanged(isActive: isTracking))
                    observer.observe(.sliderIsTrackingTouch(isTracking))
                }
            }
        }
    }
}

Notice, in this last example, that the view controller itself is also observable. In fact, it conforms to MultiObservable, meaning it can have multiple observers. When a slider is interacted with, the controller informs all of its observers of the slider’s new value as its thumb is dragged, and also conveys some other state changes.

I started developing this technique some time before WWDC 2019. Meanwhile, WWDC came and passed and SwiftUI will soon be upon us, with its own variation of attaching handlers to controls. But the technique shown here is available right now, and compatible with iOS 11 and iOS 12, whereas SwiftUI is available only from iOS 13 onwards.

You can find the full source code here.

]]>
<![CDATA[How to turn UIControls into light-weight reactive objects.]]>
A Scrolling Stackview2018-12-24T00:00:00+00:002018-12-24T00:00:00+00:00https://sintraworks.github.io/swift/uikit/2018/12/24/scrolling-stackview<![CDATA[

Some months ago I read this article by Agnes Vasarhelyi. It’s about–guess what–scrollable UIStackViews. More precisely, it’s about how to correctly set up a UIStackView within a UIScrollView, using autolayout. Not long after that, I needed extactly that: a scrolling stack view for a screen I was developing at work. I decided to create something simple, yet convenient and reusable. I didn’t want to create a fancy view controller with all manner of bells and whistles. Just a simple view, that acts as scrolling stack view. Also, I did not want to have to write something like scrollView.stackView.axis = .vertical, but rather stackView.axis = .vertical.

So, we start with a scroll view. Upon initialization we embed the stack view within it. There are a couple of initializers we must implement. Additionally, we want to be able to set the scrolling axis during initialization, so we add an initializer for that. We also add a convenience initializer to facilitate creating the stack view with an array of arranged subviews:

class ScrollingStackView: UIScrollView {
    private var stackView = UIStackView()

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit(axis: .vertical)
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit(axis: .vertical)
    }

    init(axis: UILayoutConstraintAxis = .vertical) {
        super.init(frame: CGRect.zero)
        commonInit(axis: axis)
    }

    convenience init(arrangedSubviews: [UIView], axis: UILayoutConstraintAxis = .vertical) {
        self.init(axis: axis)
        for subview in arrangedSubviews {
            stackView.addArrangedSubview(subview)
        }
    }

    private func commonInit(axis: UILayoutConstraintAxis) {
        embed(view: stackView) // See bottom of article for the embed extension to UIView. 
        self.axis = axis
    }
}

You may have raised an eyebrow at the implementation of the commonInit function. We set an axis property directly on our scroll view subclass. But “a UIScrollView doesn’t have an axis property” I hear you say. That’s right. We define a computed property on the scroll view, that manages the axis property of the stack view:

    var axis: UILayoutConstraintAxis {
        get { return stackView.axis }
        set {
            axisConstraint?.isActive = false // deactivate existing constraint, if any
            switch newValue as UILayoutConstraintAxis {
                case .vertical:
                    axisConstraint = stackView.widthAnchor.constraint(equalTo: self.widthAnchor)
                case .horizontal:
                    axisConstraint = stackView.heightAnchor.constraint(equalTo: self.heightAnchor)
            }
            axisConstraint?.isActive = true // activate new constraint

            stackView.axis = newValue
        }
    }

When querying the axis property, we simply and conveniently return the stack view’s axis property. But when setting the property, we ensure the stack view is set up correctly. If you took the time to read Agnes Vasarhelyi’s article, you’ll know we need to set either a width or a height constraint on the stack view, to make the scroll view happy. We need a property to manage that constraint, since we need to disable the old constraint when switching axes. Therefore, we added a property to ScrollingStackView:

class ScrollingStackView: UIScrollView {
    private var stackView = UIStackView()
    private var axisConstraint: NSLayoutConstraint?
	
}

When changing axis, we deactivate the existing axis constraint and create and activate the new one, and hold on to it for future reference. This is really all we need to correctly manage a stack view inside a scroll view.

But, we also want to make it convenient to work with it. We don’t want to be exposed to the fact that the stack view is encapsulated inside a scroll view (unless when there’s a need for it). That’s easily achieved by vending the stack view’s properties straight from our encapsulating view:

// MARK: - Pass-throughs to UIStackView

// MARK: Managing arranged subviews
extension ScrollingStackView {
    func addArrangedSubview(_ view: UIView) {
        stackView.addArrangedSubview(view)
    }

    var arrangedSubviews: [UIView] {
        return stackView.arrangedSubviews
    }

    func insertArrangedSubview(_ view: UIView, at stackIndex: Int) {
        stackView.insertArrangedSubview(view, at: stackIndex)
    }

    func removeArrangedSubview(_ view: UIView) {
        stackView.removeArrangedSubview(view)
    }
}

// MARK: Configuring the layout
extension ScrollingStackView {
    var alignment: UIStackViewAlignment {
        get { return stackView.alignment }
        set { stackView.alignment = newValue }
    }

    var axis: UILayoutConstraintAxis {
        get { return stackView.axis }
        set {
            axisConstraint?.isActive = false // deactivate existing constraint, if any
            switch newValue as UILayoutConstraintAxis {
                case .vertical:
                    axisConstraint = stackView.widthAnchor.constraint(equalTo: self.widthAnchor)
                case .horizontal:
                    axisConstraint = stackView.heightAnchor.constraint(equalTo: self.heightAnchor)
            }
            axisConstraint?.isActive = true // activate new constraint

            stackView.axis = newValue
        }
    }

    var isBaselineRelativeArrangement: Bool {
        get { return stackView.isBaselineRelativeArrangement }
        set { stackView.isBaselineRelativeArrangement = newValue }
    }

    var distribution: UIStackViewDistribution {
        get { return stackView.distribution }
        set { stackView.distribution = newValue }
    }

    var isLayoutMarginsRelativeArrangement: Bool {
        get { return stackView.isLayoutMarginsRelativeArrangement }
        set { stackView.isLayoutMarginsRelativeArrangement = newValue }
    }

    var spacing: CGFloat {
        get { return stackView.spacing }
        set { stackView.spacing = newValue }
    }
}

// MARK: Adding space between items
@available(iOS 11.0, *)
extension ScrollingStackView {
        func customSpacing(after arrangedSubview: UIView) -> CGFloat {
        return stackView.customSpacing(after: arrangedSubview)
    }

    func setCustomSpacing(_ spacing: CGFloat, after arrangedSubview: UIView) {
        stackView.setCustomSpacing(spacing, after: arrangedSubview)
    }

    class var spacingUseDefault: CGFloat {
        return UIStackView.spacingUseDefault
    }

    class var spacingUseSystem: CGFloat {
        return UIStackView.spacingUseSystem
    }
}

// MARK: - UIView overrides
extension ScrollingStackView {
    override var layoutMargins: UIEdgeInsets {
        get { return stackView.layoutMargins }
        set { stackView.layoutMargins = newValue }
    }
}

This passing thorugh of UIStackView’s properties is what makes our view feel like a UIStackView, even if it isn’t really. Now, all we need to do is set up a ScrollingStackview, and add a stack of subviews to it:

class ViewController: UIViewController {

let stackView = ScrollingStackView()

override func viewDidLoad() {
super.viewDidLoad()

view.embed(stackView)
setupStackView()
}

func setupStackView() {
    stackView.spacing = 10.0

    for i in 1..<30 {
        let label = UILabel()
        label.text = "Hello, this is label nº \(i)"
        stackView.addArrangedSubview(label)
    }
}

And that’s it. Our ScrollingStackView will automagically scroll as needed. If you want a horizontally scrolling stackview, initialize it like this: let stackView = ScrollingStackView(axis: .horizontal).

Full source code in this gist.

Lastly, here is the convenience function we used above, to embed a view within another view, with the subview completely filling the parent view:

extension UIView {
    func embed(_ child: UIView) {
        child.translatesAutoresizingMaskIntoConstraints = false
        addSubview(child)
        let constraints = [
            child.leftAnchor.constraint(equalTo: self.leftAnchor),
            child.rightAnchor.constraint(equalTo: self.rightAnchor),
            child.topAnchor.constraint(equalTo: self.topAnchor),
            child.bottomAnchor.constraint(equalTo: self.bottomAnchor),
            ]
        NSLayoutConstraint.activate(constraints)
    }
}
]]>
<![CDATA[A drop-in UIStackView replacement that can actually scroll.]]>
PleasantNavigationController2018-11-01T00:00:00+00:002018-11-01T00:00:00+00:00https://sintraworks.github.io/swift/uikit/2018/11/01/a-better-navigation-controller<![CDATA[

So you’re building your app, and have a navigation controller, and sometimes you need to know when the controller is about to navigate, or has just finished navigating. Well, there’s a great built in API for that, right?

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated;

Very nice indeed. Now, your view controller is about to be shown, and you need to know whether the user got there by navigating back, e.g. by tapping the back button, or by swiping right to exit the current top controller in the stack. Ouch. You don’t know. And UINavigationcontroller won’t tell you. All it will tell you is “when [it] shows a new top view controller via a push, pop or setting of the view controller stack”. You never know the direction of the move. There are hacks to detect a back button tap, or a right-swipe and pass that along. These I find unsatisfactory. It would be better if UINavigationController told its delegate more about its actions. And, as it turns out, it is not difficult to extend UINavigationController to do exactly that.

Enter PleasantNavigationController and its PleasantNavigationControllerDelegate protocol.

We start by defining the API we want to subscribe to. We want to be told when a navigation event is about to happen, and when it has just finished, and we want to know the direction of the move:

@objc protocol PleasantNavigationControllerDelegate: AnyObject {
    @objc optional func willPushViewController(_ viewController: UIViewController, animated: Bool)
    @objc optional func didPushViewController(_ viewController: UIViewController, animated: Bool)

    @objc optional func willPopViewController(_ viewController: UIViewController?, animated: Bool)
    @objc optional func didPopViewController(_ viewController: UIViewController?, animated: Bool)

    @objc optional func willPopToViewController(_ viewController: UIViewController, animated: Bool)
    @objc optional func didPopToViewController(_ viewController: UIViewController, animated: Bool)

    @objc optional func willPopToRootViewController(animated: Bool)
    @objc optional func didPopToRootViewController(animated: Bool)

    @objc optional func willSetViewControllers(_ viewControllers: [UIViewController], animated: Bool)
    @objc optional func didSetViewControllers(_ viewControllers: [UIViewController], animated: Bool)
}

This provides us with all the relevant events, so that we can react to them as needed. Now we need a version of the navigation controller that implements the protocol:

class PleasantNavigationController: UINavigationController {
	// First, some obligatory overrides
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override init(rootViewController: UIViewController) {
        super.init(rootViewController: rootViewController)
    }
	
	// Sometimes we need to be able to access the delegate with the more refined type.
    var pleasantDelegate: PleasantNavigationControllerDelegate? {
        return delegate as? PleasantNavigationControllerDelegate
    }

	// And, finally, the functions that implement our protocol
    override func pushViewController(_ viewController: UIViewController, animated: Bool) {
        pleasantDelegate?.willPushViewController?(viewController, animated: animated)
        super.pushViewController(viewController, animated: animated)
        pleasantDelegate?.didPushViewController?(viewController, animated: animated)
    }

    override func popViewController(animated: Bool) -> UIViewController? {
        let poppedViewController = topViewController
        pleasantDelegate?.willPopViewController?(poppedViewController, animated: animated)
        defer { pleasantDelegate?.didPopViewController?(poppedViewController, animated: animated) }
        return super.popViewController(animated: animated)
    }

    override func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? {
        pleasantDelegate?.willPopToViewController?(viewController, animated: animated)
        defer { pleasantDelegate?.didPopToViewController?(viewController, animated: animated) }
        return super.popToViewController(viewController, animated: animated)
    }

    override func popToRootViewController(animated: Bool) -> [UIViewController]? {
        pleasantDelegate?.willPopToRootViewController?(animated: animated)
        defer { pleasantDelegate?.didPopToRootViewController?(animated: animated) }
        return super.popToRootViewController(animated: animated)
    }

    override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) {
        pleasantDelegate?.willSetViewControllers?(viewControllers, animated: animated)
        super.setViewControllers(viewControllers, animated: animated)
        pleasantDelegate?.didSetViewControllers?(viewControllers, animated: animated)
    }
}

Note the defer in the functions that return a value. Swift makes it so easy to ensure an action is taken even after returning from a function. Or rather, after executing the call in the return statement, but before returning control to the calling code, the deferred code will be executed, which is just what we need, and allows us to keep our code concise and to the point.

With this in place, you make your observers conform to the PleasantNavigationControllerDelegate protocol, assign them as the navigation controller delegate at the appropriate time, and become master of navigation in your app.

You can find the full source code in this gist, and the full repository, including a sample app on GitHub

[Added link to gist on 12-01-2019; updated and added link to full repo on 14-09-2022]

]]>
<![CDATA[A navigation controller that does a better job at communicating navigation events.]]>
Mini Auto Layout DSL Revisited2018-01-06T00:00:00+00:002018-01-06T00:00:00+00:00https://sintraworks.github.io/swift/autolayout/2018/01/06/autolayout-dsl-v2<![CDATA[

After the previous post, and despite the positive feedback, I wasn’t quite satisfied yet with the result and felt the code could do with some improvement. As I looked again at the NSLayoutAnchor header, I realised I had left out a bit of functionality. I also wanted to see if I could manage one more simplification. The end result is that, after some refactoring, the API surface has been slightly reduced, and, while the code has gained a few lines, it is more correct and more flexible.

I wanted to post about this refactoring sooner, but having sustained two broken ribs about a week ago slowed me down a bit… But anyway, here it is.

If you haven’t read the original post be sure to do so. It will make it easier to follow along here.

The first thing I wanted to do, which is something Chris also suggested, was to remove the distinction between PairedConstraint and UnpairedConstraint:

typealias Constraint = (_ view: UIView, _ otherView: UIView?) -> NSLayoutConstraint

This leaves us with a single type: Constraint, which takes two UIViews and returns a layout constraint. The second view (otherView) was made optional, because some constraints are not relative to other views. This compensates for the removal of the UnpairedConstraint type.

The definition of the constraint function for axis-relative constraints remains largely the same:

func constraint<Anchor, AnchorType>(_ keyPath: KeyPath<UIView, Anchor>,
                                    _ otherKeyPath: KeyPath<UIView, Anchor>? = nil,
                                    constraintRelation: ConstraintRelation = .equal,
                                    multiplier: CGFloat? = nil,
                                    constant: CGFloat = 0,
                                    priority: UILayoutPriority? = nil) -> Constraint where Anchor: NSLayoutAnchor<AnchorType> {
    return { view, otherView in
        guard let otherView = otherView else { fatalError("Pairing view missing")}
        var partialConstraint: NSLayoutConstraint
        let otherKeyPath = otherKeyPath ?? keyPath
        
        switch constraintRelation {
        case .equal:
            partialConstraint = view[keyPath: keyPath].constraint(equalTo: otherView[keyPath: otherKeyPath], constant: constant)
        case .greaterThanOrEqual:
            partialConstraint = view[keyPath: keyPath].constraint(greaterThanOrEqualTo: otherView[keyPath: otherKeyPath], constant: constant)
        case .lessThanOrEqual:
            partialConstraint = view[keyPath: keyPath].constraint(lessThanOrEqualTo: otherView[keyPath: otherKeyPath], constant: constant)
        }
        
        return fullConstraint(from: partialConstraint, withMultiplier:multiplier, priority: priority)
    }
}

It gained only a single line: since the closure’s second view parameter is now optional, we need to unwrap it. If unwrapping fails, we cop out, because not supplying a second view for this type of constraint is a programmer error.

The second constraint function, which creates constraints for NSLayoutDimension based anchors gets more heavily refactored, since this is where we add the missing functionality. In its previous incarnation the function could create constraints directly on constants, but I had left out the possibility of creating constraints based on another anchor and a constant. So lets fix that:

The function will need to either create a constraint of the equalToConstant: type (as it already did), or of the equalTo:otherAnchor, constant: type. To avoid a fair amount of duplication and typing, we define a nested function that will do the work of actually creating the constraint:

        func constraint(otherView: UIView,
                        otherKeyPath: KeyPath<UIView, Anchor>?) -> NSLayoutConstraint {
            if let otherKeyPath = otherKeyPath {
                switch constraintRelation {
                case .equal:
                    return view[keyPath: keyPath].constraint(equalTo:otherView[keyPath: otherKeyPath], constant: constant)
                case .greaterThanOrEqual:
                    return view[keyPath: keyPath].constraint(greaterThanOrEqualTo:otherView[keyPath: otherKeyPath], constant: constant)
                case .lessThanOrEqual:
                    return view[keyPath: keyPath].constraint(lessThanOrEqualTo:otherView[keyPath: otherKeyPath], constant: constant)
                }
            } else {
                switch constraintRelation {
                case .equal:
                    return view[keyPath: keyPath].constraint(equalToConstant: constant)
                case .greaterThanOrEqual:
                    return view[keyPath: keyPath].constraint(greaterThanOrEqualToConstant: constant)
                case .lessThanOrEqual:
                    return view[keyPath: keyPath].constraint(lessThanOrEqualToConstant: constant)
                }
            }
        }

It takes a view, which is the related view, and an optional keyPath to the related anchor. The other parameters needed within the function body are inherited from the surrounding context. This saves quite a bit of hassle. The function first checks for the existence of a related key path. If there is none, we create the constant based constraint, as in the previous version of the code, ignoring the otherView, which is not needed here. If we did receive an otherKeyPath, we unwrap it and use it to create a constraint related to that key path and a constant, relative to the view we received in otherView.

What is left, is to call the internal function, which can be done quite succinctly, passing in correct values for otherView and otherKeyPath:

        constraint(otherView: otherView ?? view,
                   otherKeyPath: otherView == nil ? otherKeyPath : otherKeyPath ?? keyPath)

If the outer function was called with an otherView we pass it along to the inner function. If the otherView is nil, we pass along the base view.

For otherKeyPath, if the outer function did not receive an otherView we pass in the otherKeyPath, which may or may not be nil. If we did receive another view, we are definitely creating a constraint based on a related anchor. Therefore we must pass in another key path. If one is upplied, we pass it along, if none is supplied, we use the same key path as that for the base view. This last trick allows us to create constraints without having to retype the anchor key path, thus simplifying the call site. I wrote it using a combination of the ternary and the nil coalescing operators. While I’m usually wary of doing that, I felt that, in this instance, the result is compact and quite readable.

Putting the pieces together, the whole function becomes:

func constraint<Anchor>(_ keyPath: KeyPath<UIView, Anchor>,
                        _ otherKeyPath: KeyPath<UIView, Anchor>? = nil,
                        constraintRelation: ConstraintRelation = .equal,
                        multiplier: CGFloat? = nil,
                        constant: CGFloat = 0,
                        priority: UILayoutPriority? = nil) -> Constraint where Anchor: NSLayoutDimension {
    return { view, otherView in
        func constraint(otherView: UIView,
                        otherKeyPath: KeyPath<UIView, Anchor>?) -> NSLayoutConstraint {
            if let otherKeyPath = otherKeyPath {
                switch constraintRelation {
                case .equal:
                    return view[keyPath: keyPath].constraint(equalTo:otherView[keyPath: otherKeyPath], constant: constant)
                case .greaterThanOrEqual:
                    return view[keyPath: keyPath].constraint(greaterThanOrEqualTo:otherView[keyPath: otherKeyPath], constant: constant)
                case .lessThanOrEqual:
                    return view[keyPath: keyPath].constraint(lessThanOrEqualTo:otherView[keyPath: otherKeyPath], constant: constant)
                }
            } else {
                switch constraintRelation {
                case .equal:
                    return view[keyPath: keyPath].constraint(equalToConstant: constant)
                case .greaterThanOrEqual:
                    return view[keyPath: keyPath].constraint(greaterThanOrEqualToConstant: constant)
                case .lessThanOrEqual:
                    return view[keyPath: keyPath].constraint(lessThanOrEqualToConstant: constant)
                }
            }
        }

        return fullConstraint(from: constraint(otherView: otherView ?? view,
                                               otherKeyPath: otherView == nil ? otherKeyPath : otherKeyPath ?? keyPath),
                              withMultiplier:multiplier,
                              priority: priority)
    }
}

The constraint creation helper function, which allows us to apply a multiplier and a priority, remains the same but was renamed to fullConstraint for clarity.

The extension on UIView remains largely the same. Only the body of the last function was refactored, to account for the removal of UnpairedConstraint. We now must pass in a nil second parameter:

extension UIView {
    func addSubview(_ child: UIView, pairingTo pairingView: UIView? = nil, constraints: [Constraint]) {}
    func constrainToView(_ pairingView: UIView, constraints: [Constraint]) {}
    
    func constrain(to constraints: [Constraint]) {
        NSLayoutConstraint.activate(constraints.map { $0(self, nil) })
    }
}

We can now create additional constraint types, in a way that we couldn’t in the previous version of the code. For instance:

purpleView.constrain(to: [
    constraint(\.widthAnchor, \.heightAnchor)
    ])
	

You can find the full source code for this refactoring, with additional constraint examples, in a gist here.

]]>
<![CDATA[Running further with Chris Eidhof's Micro Auto Layout DSL.]]>