Appendix B. Some Useful Utility Functions

As you work with iOS and Swift, you’ll doubtless develop a personal library of frequently used convenience functions. Here are some of mine. Every one of them has come in handy in my own life; I keep them available as user snippets in Xcode so that I can paste them into any project.

Launch Without Main Storyboard

As I explained in Chapter 1, if an app lacks a main storyboard, or if you want to ignore the main storyboard and generate the app’s initial interface yourself, configuring the window and supplying the root view controller is up to you. A minimal app delegate class would look something like this:

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

Core Graphics Initializers

C and Objective-C have the Core Graphics CGRectMake function, which needs no argument labels when you call it. Infuriatingly, Swift 3 cuts off access to this function, leaving only the various CGRect initializers, all of which do need argument labels. I find those labels otiose, clumsy, and verbose; we know what each argument signifies, so why clutter the call with labels? The solution is another CGRect initializer, without labels:

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 three 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)
    }
}

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 it is often the case that the interface must be given a moment to settle down before we proceed to the next command. Calling asyncAfter is not difficult, but we can simplify with a utility function:

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

And call it like this:

delay(0.4) {
    // do something here
}

Dictionary of Views

There are no Swift macros (because there’s no Swift preprocessor), so you can’t write the equivalent of Objective-C’s NSDictionaryOfVariableBindings, which turns a literal list of view names into a dictionary of string names and view references for use in connection with a visual format string in a call to NSLayoutConstraint’s constraints(withVisualFormat:options:metrics:views:). You can, however, generate such 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
}

This 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 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)
            NSLog("

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

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 a 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(
    NSParagraphStyleAttributeName,
    value:para, range:NSMakeRange(0,1))

That has a clunky, procedural feel. 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(NSParagraphStyleAttributeName,
    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))

Drawing Into an Image Context

My original goal was to encapsulate the clunky, boilerplate, imperative-programming dance of beginning an image graphics context, drawing into it, extracting the image, and ending the context. In its place, I wrote a utility function that did everything but the drawing, which would be provided as a function parameter. Then iOS 10 introduced UIGraphicsImageRenderer, which works in exactly that way. But now there’s a new problem: what if you want backward compatibility? So now my utility function combines the old implementation with the new one:

func imageOfSize(_ size:CGSize, opaque:Bool = false,
    closure: () -> ()) -> UIImage {
        if #available(iOS 10.0, *) {
            let f = UIGraphicsImageRendererFormat.default()
            f.opaque = opaque
            let r = UIGraphicsImageRenderer(size: size, format: f)
            return r.image {_ in closure()}
        } else {
            UIGraphicsBeginImageContextWithOptions(size, opaque, 0)
            closure()
            let result = UIGraphicsGetImageFromCurrentImageContext()!
            UIGraphicsEndImageContext()
            return result
        }
}

You call it like this (using my label-free Core Graphics initializers, of course):

let im = imageOfSize(CGSize(100,100)) {
    let con = UIGraphicsGetCurrentContext()!
    con.addEllipse(in: CGRect(0,0,100,100))
    con.setFillColor(UIColor.blue.cgColor)
    con.fillPath()
}

Finite Repetition of an Animation

This is a solution to the problem, posed in Chapter 4, of how to repeat a view animation a fixed number of times without using “begin/commit” syntax. 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: UIViewAnimationOptions,
        animations anim: @escaping () -> Void,
        completion comp: ((Bool) -> Void)?) {
            func helper(_ t:Int,
                _ dur: TimeInterval,
                _ del: TimeInterval,
                _ opt: UIViewAnimationOptions,
                _ anim: @escaping () -> Void,
                _ com: ((Bool) -> Void)?) {
                    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 = UIViewAnimationOptions.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 Array

It is often convenient to collect the indexes of items to be deleted from an array, and then to delete those items. We must be careful to sort the indexes in decreasing numeric 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)
        }
    }
}
..................Content has been hidden....................

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