Observing Low Data Mode and other expensive network paths

In iOS 13, Apple introduced Low Data Mode, an option users can enable on a per network basis to nicely ask the system and developers to consume as little data as possible.

It’s then the developer responsibility to conform to this preference. This option is easy to implement when using URLSession, but there is also a way to observe the status of the current network path, and the changes that may occur while your application or your screen is active using the Network framework:

import Network

let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
    if path.isConstrained {
        // Current network is constrained by user preference, aka iOS 13+ Low Data Mode
    } else if path.isExpensive {
        // Current network is considered expensive (eg: cellular, hotspot)
    } else {
        // Current network hasn't anything special, most likely is WiFi
    }
}
monitor.start(queue: DispatchQueue.global(qos: .background))

(Swift 5.1 / iOS 13.1)

SwiftUI Ambiguous reference to member ‘buildBlock()’

So this work:

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Text 1")
            Text("Text 2")
            Text("Text 3")
            Text("Text 4")
            Text("Text 5")
            Text("Text 6")
            Text("Text 7")
            Text("Text 8)
            Text("Text 9")
            Text("Text 10")
        }
    }
}

But this doesn’t:

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Text 1")
            Text("Text 2")
            Text("Text 3")
            Text("Text 4")
            Text("Text 5")
            Text("Text 6")
            Text("Text 7")
            Text("Text 8)
            Text("Text 9")
            Text("Text 10")
            Text("Text 11")
        }
    }
}

The compiler refuses to compile and prompts the following error: Ambiguous reference to member 'buildBlock()'.

This is due to the view building system in SwiftUI internally having code to let you add 1 view, 2 views, 3 views… up to 10 views, but nothing for 11 and beyond.

The solution is to wrap up to 10 elements in a Group, it allows you to break your components structure, for example:

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Group {
                Text("Text 1")
                Text("Text 2")
                Text("Text 3")
                Text("Text 4")
                Text("Text 5")
                Text("Text 6")
                Text("Text 7")
                Text("Text 8)
                Text("Text 9")
                Text("Text 10")
            }
            Text("Text 11")
        }
    }
}

Sort by Name

Simple trick in Xcode, but important nevertheless since keeping things organized is crucial for you and your colleagues.

For instance, you can transform the order of these files:

into this:

by right-clicking on the Views folder and select Sort by Name:

Preserving a minimum tappable area for UIButton

One of the Apple Human Interface Guideline says:

Provide ample touch targets for interactive elements. Try to maintain a minimum tappable area of 44pt x 44pt for all controls.

(https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/adaptivity-and-layout/)

But sometimes, your graphic designer only gives you a tiny image and:

  1. you don’t want to deal with the button insets in your layout
  2. you still want your users to have a reasonable hit zone

The solution is to override the func hitTest(_:, with:) -> UIView? method of the UIButton, so that it returns said button even if the tap if slightly out of bounds (as long as this matches the 44×44 points rule):

import UIKit
class MinimumTouchAreaButton: UIButton {
override func hitTest(_ point: CGPoint, with _: UIEvent?) -> UIView? {
guard !isHidden, isUserInteractionEnabled, alpha > 0 else {
return nil
}
let expandedBounds = bounds.insetBy(dx: min(bounds.width 44.0, 0), dy: min(bounds.height 44.0, 0))
return expandedBounds.contains(point) ? self : nil
}
}

Only assign new values when the right operand is not nil

It’s sometimes useful to create custom operators to avoid boilerplate code. For example in the following code, we check if a variable is not nil before assigning it to our object:

if let basedOn = basedOn {
    story.basedOn = basedOn
}

It would be much better if we could simplify and have this instead:

story.basedOn ?= basedOn

To do that in Swift, we can create an “Optional Assignment” custom operator, as short as ?= here, with a few lines of code:

import Foundation
precedencegroup OptionalAssignment {
associativity: right
}
infix operator ?=: OptionalAssignment
public func ?= <T>(variable: inout T, value: T?) {
if let unwrapped = unwrap(any: value) ?? nil {
variable = unwrapped
}
}
public func unwrap<T: Any>(any: T) -> T? {
let mirror = Mirror(reflecting: any)
guard mirror.displayStyle == .optional else { return any }
guard let child = mirror.children.first else { return nil }
return unwrap(any: child.value) as? T
}