Appendix B. Some Useful Utility Functions

As you work with iOS and Swift, you’ll develop a personal library of frequently used convenience functions. Here are some of mine. Each of them has come in handy in my own life; I keep them available as snippets in Xcode’s Library, so that I can use them in any project. Many of them have been mentioned earlier in this book.

Launch Without Main Storyboard

If an app lacks a main storyboard, or if you want to ignore the main storyboard and generate the app’s initial interface yourself, it’s up to you to configure the window and supply the root view controller (“How an App Launches”). A minimal app delegate class would look something like this:

@UIApplicationMain
class AppDelegate : UIResponder, UIApplicationDelegate {
    var window : UIWindow?
    func application(_ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions:
        [UIApplication.LaunchOptionsKey : Any]?) -> Bool {
            self.window = self.window ?? UIWindow()
            self.window!.backgroundColor = .white
            self.window!.rootViewController = ViewController()
            self.window!.makeKeyAndVisible()
            return true
    }
}

Core Graphics Initializers

The Core Graphics CGRectMake function needs no argument labels when you call it. But Swift cuts off access to this function, leaving only the various CGRect initializers, all of which do need argument labels. However, we know what each argument signifies, so why clutter the call with labels? The solution is a CGRect initializer without labels (Chapter 1):

extension CGRect {
    init(_ x:CGFloat, _ y:CGFloat, _ w:CGFloat, _ h:CGFloat) {
        self.init(x:x, y:y, width:w, height:h)
    }
}

As long as we’re doing that, we may as well supply label-free initializers for the other common Core Graphics structs:

extension CGSize {
    init(_ width:CGFloat, _ height:CGFloat) {
        self.init(width:width, height:height)
    }
}
extension CGPoint {
    init(_ x:CGFloat, _ y:CGFloat) {
        self.init(x:x, y:y)
    }
}
extension CGVector {
    init (_ dx:CGFloat, _ dy:CGFloat) {
        self.init(dx:dx, dy:dy)
    }
}

The code examples throughout this book, including the rest of this appendix, rely upon the existence of those extensions.

Center of a CGRect

One so frequently wants the center point of a CGRect that even the shorthand CGPoint(rect.midX, rect.midY) becomes tedious. You can extend CGRect to do the work for you:

extension CGRect {
    var center : CGPoint {
        return CGPoint(self.midX, self.midY)
    }
}

Adjust a CGSize

There’s a CGRect method insetBy(dx:dy:), but there’s no comparable method for changing an existing CGSize by a width delta and a height delta. Let’s make one:

extension CGSize {
    func sizeByDelta(dw:CGFloat, dh:CGFloat) -> CGSize {
        return CGSize(self.width + dw, self.height + dh)
    }
}

Delayed Performance

Delayed performance is of paramount importance in iOS programming, where the interface often needs a moment to settle down before we proceed to the next command. Calling asyncAfter is not difficult (Chapter 24), but we can simplify even more with a utility function:

func delay(_ delay:Double, closure: @escaping ()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}

Call it like this:

delay(0.4) {
    // do something here
}

Dictionary of Views

When you generate constraints from a visual format string by calling NSLayoutConstraint’s constraints(withVisualFormat:options:metrics:views:), you need a dictionary of string names and view references as the last argument (Chapter 1). Forming this dictionary is tedious. Let’s make it easier.

There are no Swift macros (because there’s no Swift preprocessor), so you can’t write the equivalent of Objective-C’s NSDictionaryOfVariableBindings, which forms the dictionary from a literal list of view names. You can, however, generate a dictionary with fixed string names, like this:

func dictionaryOfNames(_ arr:UIView...) -> [String:UIView] {
    var d = [String:UIView]()
    for (ix,v) in arr.enumerated() {
        d["v(ix+1)"] = v
    }
    return d
}

That utility function takes a list of views and simply makes up new string names for them, of the form "v1", "v2", and so on, in order. Knowing the rule by which the string names are generated, you then use those string names in your visual format strings.

For example, if you generate the dictionary by calling dictionaryOfNames(mainview, myLabel), then in any visual format string that uses this dictionary as its views: dictionary, you will refer to mainview by the name v1 and to myLabel by the name v2.

Constraint Priority Arithmetic

It is often desired to increment or decrement a constraint’s priority in order to prevent layout ambiguity. That used to be easy, because a priority was just a number; but in iOS 11, a constraint priority became a UILayoutPriority struct (Chapter 1). This extension allows a number to be added to a priority struct:

extension UILayoutPriority {
    static func +(lhs: UILayoutPriority, rhs: Float) -> UILayoutPriority {
        let raw = lhs.rawValue + rhs
        return UILayoutPriority(rawValue:raw)
    }
}

Constraint Issues

These are NSLayoutConstraint class methods aimed at helping to detect and analyze constraint issues (referred to in Chapter 1):

extension NSLayoutConstraint {
    class func reportAmbiguity (_ v:UIView?) {
        var v = v
        if v == nil {
            v = UIApplication.shared.keyWindow
        }
        for vv in v!.subviews {
            print("(vv) (vv.hasAmbiguousLayout)")
            if vv.subviews.count > 0 {
                self.reportAmbiguity(vv)
            }
        }
    }
    class func listConstraints (_ v:UIView?) {
        var v = v
        if v == nil {
            v = UIApplication.shared.keyWindow
        }
        for vv in v!.subviews {
            let arr1 = vv.constraintsAffectingLayout(for:.horizontal)
            let arr2 = vv.constraintsAffectingLayout(for:.vertical)
            let s = String(format: "

%@
H: %@
V:%@", vv, arr1, arr2)
            print(s)
            if vv.subviews.count > 0 {
                self.listConstraints(vv)
            }
        }
    }
}

You can call those methods in your code, though you should remove them before shipping the app. Another possibility is to call them while paused in the debugger, like this:

(lldb) expr NSLayoutConstraint.reportAmbiguity(nil)

Configure a Value Class at Point of Use

A recurring pattern in Cocoa is that a value class instance is created and configured beforehand for one-time use. Here’s a case in point:

let para = NSMutableParagraphStyle()
para.headIndent = 10
para.firstLineHeadIndent = 10
para.tailIndent = -10
para.lineBreakMode = .byWordWrapping
para.alignment = .center
para.paragraphSpacing = 15
content.addAttribute(
    .paragraphStyle,
    value:para,
    range:NSMakeRange(0,1))

That feels clunky, procedural, and wasteful. First we create the NSMutableParagraphStyle; then we set its properties; then we use it once; then we throw it away.

It would be clearer and more functional, as well as reflecting the natural order of thought, if the creation and configuration of para could happen just at the actual moment when we need this object, namely when we supply the value: argument. Here’s a generic function that permits us to do that:

func lend<T> (_ closure: (T)->()) -> T where T:NSObject {
    let orig = T()
    closure(orig)
    return orig
}

Now we can express ourselves like this:

content.addAttribute(
    .paragraphStyle,
    value:lend { (para:NSMutableParagraphStyle) in
        para.headIndent = 10
        para.firstLineHeadIndent = 10
        para.tailIndent = -10
        para.lineBreakMode = .byWordWrapping
        para.alignment = .center
        para.paragraphSpacing = 15
    },
    range:NSMakeRange(0,1))

Finite Repetition of an Animation

This is a solution to the problem of how to repeat a view animation a fixed number of times without using begin-and-commit syntax (Chapter 4). My approach is to employ tail recursion and a counter to chain the individual animations. The delay call unwinds the call stack and works around possible drawing glitches:

extension UIView {
    class func animate(times:Int,
        duration dur: TimeInterval,
        delay del: TimeInterval,
        options opts: UIView.AnimationOptions,
        animations anim: @escaping () -> (),
        completion comp: ((Bool) -> ())?) {
            func helper(_ t:Int,
                _ dur: TimeInterval,
                _ del: TimeInterval,
                _ opt: UIView.AnimationOptions,
                _ anim: @escaping () -> (),
                _ com: ((Bool) -> ())?) {
                    UIView.animate(withDuration: dur,
                        delay: del, options: opt,
                        animations: anim, completion: { done in
                            if com != nil {
                                com!(done)
                            }
                            if t > 0 {
                                delay(0) {
                                    helper(t-1, dur, del, opt, anim, com)
                                }
                            }
            })
        }
        helper(times-1, dur, del, opts, anim, comp)
    }
}

The calling syntax is exactly like ordering a UIView animation in its full form, except that there’s an initial times parameter:

let opts = UIView.AnimationOptions.autoreverse
let xorig = self.v.center.x
UIView.animate(times:3, duration:1, delay:0, options:opts, animations:{
    self.v.center.x += 100
    }, completion:{ _ in
        self.v.center.x = xorig
})

Remove Multiple Indexes from an Array

It is often convenient to collect the indexes of items to be deleted from an array, and then to delete those items. We can do this by repeatedly calling the Array method remove(at:), but then we must be careful to sort the index numbers in descending order first, because array indexes will be off by one after an item at a lower index is removed:

extension Array {
    mutating func remove(at ixs:Set<Int>) -> () {
        for i in Array<Int>(ixs).sorted(by:>) {
            self.remove(at:i)
        }
    }
}

Unfortunately, that code’s performance is poor for a large array, because every call to self.remove(at:i) requires that all higher indexes be decremented before we can proceed. Swift 4.2 introduces removeAll(where:), which allows us to be much more efficient. This implementation is about 100 times faster:

extension Array {
    mutating func remove(at ixs:Set<Int>) {
        var arr = Swift.Array(self.enumerated())
        arr.removeAll{ixs.contains($0.offset)}
        self = arr.map{$0.element}
    }
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.245.196