Skip to content

Swift and iOS

alex [dot] kramer [at] g_m_a_i_l [dot] com edited this page Apr 15, 2019 · 22 revisions

Conditional assignment operator

infix operator ??=
func ??= <T>(left: inout T?, right: T) {
    left = left ?? right
}

Safe Array Subscript Operator

extension Array {
    subscript (safe index: Int) -> Element? {
        return indices ~= index ? self[index] : nil
    }
}

Object names

For any:

String(describing: type(of: anyObject))

Extension for all:

extension NSObject {
    var className: String {
        return String(describing: type(of: self))
    }

    class var className: String {
        return String(describing: self)
    }
}

Array remove first by value

extension Array where Element: Equatable {
    mutating func remove(_ object: Element) {
        if let index = index(of: object) {
            remove(at: index)
        }
    }
}

Make entire view resign when tapped

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    self.endEditing(true)
}

Add insets to borderless UITextField

var padding: UIEdgeInsets?

override func textRect(forBounds bounds: CGRect) -> CGRect {
    if let padding = self.padding {
        return UIEdgeInsetsInsetRect(bounds, padding)
    } else {
        return super.textRect(forBounds: bounds)
    }
}

override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
    if let padding = self.padding {
        return UIEdgeInsetsInsetRect(bounds, padding)
    } else {
        return super.placeholderRect(forBounds: bounds)
    }
}

override func editingRect(forBounds bounds: CGRect) -> CGRect {
    if let padding = self.padding {
        return UIEdgeInsetsInsetRect(bounds, padding)
    } else {
        return super.editingRect(forBounds: bounds)
    }
}

Activity Indicator

var activityIndicator = UIActivityIndicatorView()

...

func initializeActivityIndicator() {
    activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.WhiteLarge
    activityIndicator.color = UIColor.darkGrayColor()
    activityIndicator.center = self.view.center
    // activityIndicator.center = CGPointMake(view.center.x, recentSearchesTableView.center.y)
    activityIndicator.hidesWhenStopped = true
    view.addSubview(activityIndicator)
}

Alert

self.alert("Oops, we need to go back!") {
    self.navigationController?.popViewControllerAnimated(true)
    return
}

...

private func alert(message: String, completion: () -> Void = {}) {
    let alertController = UIAlertController(title: "Error", message: message, preferredStyle: UIAlertControllerStyle.Alert)

    let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) { (result : UIAlertAction) -> Void in
        completion()
    }

    alertController.addAction(okAction)
    self.presentViewController(alertController, animated: true, completion: nil)
}

Use dynamic cell row heights from storyboard

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    struct StaticVars {
        static var rowHeights = [String:CGFloat]()
    }

    let cellReuseId = // get using indexPath

    guard let height = StaticVars.rowHeights[cellReuseId] else {
        let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId)
        let height = cell?.bounds.size.height ?? tableView.rowHeight
        StaticVars.rowHeights[cellReuseId] = height
        return height
    }

    return height
}

Run in background

DispatchQueue.global(qos: .background).async {
    print("=====> zzzzzz...")
    sleep(5)
    // Do stuff
}

Run on UI thread

E.g. when doing UI stuff from an async callback

DispatchQueue.main.async {
    // Hide spinner or whatever
}

NSDecimal values

let nums = ["", ".", "00", "01", ".10", ".1", "1.", "1..", "1...", "1..1", ".0", "0.", "0.1", "1.0", "1.2.3", "1e5", "1x5", "1e", "e5", "0e5", "1.2e3.4e5x", " 1 2 ", "x1", "NaN"]

for num in nums {
    let numericValue = NSDecimalNumber.init(string: num)
    print("[\(num)] -> [\(numericValue)] \(numericValue == NSDecimalNumber.notANumber)")
}

Output:

[] -> [NaN] true
[.] -> [0] false
[00] -> [0] false
[01] -> [1] false
[.10] -> [0.1] false
[.1] -> [0.1] false
[1.] -> [1] false
[1..] -> [1] false
[1...] -> [1] false
[1..1] -> [1] false
[.0] -> [0] false
[0.] -> [0] false
[0.1] -> [0.1] false
[1.0] -> [1] false
[1.2.3] -> [1.2] false
[1e5] -> [100000] false
[1x5] -> [1] false
[1e] -> [1] false
[e5] -> [0] false
[0e5] -> [0] false
[1.2e3.4e5x] -> [1200] false
[ 1 2 ] -> [1] false
[x1] -> [NaN] true
[NaN] -> [NaN] true

UITest examples

Async assertions using NSPredicate

let firstCell = app.cells.elementBoundByIndex(0)

let predicateExists = NSPredicate(format: "exists == 1")
expectationForPredicate(predicateExists, evaluatedWithObject: firstCell, handler: nil)
waitForExpectationsWithTimeout(5, handler: nil)

let cells = app.cells
let predicateGreaterThanZero = NSPredicate(format: "count > 0")
expectationForPredicate(predicateGreaterThanZero, evaluatedWithObject: cells, handler: nil)
waitForExpectationsWithTimeout(5, handler: nil)

URLComponents query string helpers

extension URLComponents {
    public func queryStringValue(forKey key: String) -> String? {
        if let queryItems = self.queryItems,
            let value = queryItems.filter({ $0.name == key }).first?.value {
            return value
        } else {
            return nil
        }
    }

    public mutating func addOrUpdateQueryStringValue(forKey key: String, value: String?) {
        var queryItems = self.queryItems ?? [URLQueryItem]()

        for (index, queryItem) in queryItems.enumerated() {
            if queryItem.name == key {
                if let value = value {
                    queryItems[index] = URLQueryItem(name: key, value: value)
                } else {
                    queryItems.remove(at: index)
                }

                self.queryItems = queryItems.count > 0 ? queryItems : nil

                return
            }
        }

        // Key doesn't exist if reaches here
        if let value = value {
            queryItems.append(URLQueryItem(name: key, value: value))
            self.queryItems = queryItems
        }
    }
}

Use local CocoaPods pod

pod 'MyPod', :path => '/Path/To/Pod'

Update CocoaPods and Carthage

After updating the version in the podspec:

git fetch -p --tags
git tag [new tag]
git push --tags
carthage update
carthage build --no-skip-current --platform iOS
pod repo add [GIT ACCOUNT] https://github.com/[GIT ACCOUNT]/SpecRepo # Only run this if you've never done so before
pod repo push [GIT ACCOUNT] *.podspec --verbose --allow-warnings