Swift

Eine Snippet-Sammlung für Swift.

Links:

Links UIKit:

Bibliotheken:

Basis-Typen

let x: Any = ...
let x: Void = ()
let x: Bool = true  // or: false
let x: Character = "a"
let x: Int     = 0   // 4 Bytes auf 32-Bit-Plattform, 8 Bytes auf 64-Bit-Plattform
let x: Int8    = 0   // 1 Byte
let x: UInt8   = 0   // 1 Byte, unsigned
let x: Int32   = 0   // 4 Bytes
let x: UInt32  = 0   // 4 Bytes, unsigned
let x: Int64   = 0   // 8 Bytes
let x: UInt64  = 0   // 8 Bytes, unsigned
let x: Float   = 0.0 // 4 Bytes
let x: Double  = 0.0 // 8 Bytes
let x: CGFloat = 0.0 // 8 Bytes, alias zu Double

Strings

Siehe:

Strings definieren:

let simpleString = "Hello"

// Swift 4
let multiline = """
  This is a multiline string.
  No need to "escape" single quotes.
  You could use other variables: \(simpleString)
"""

Häufige String-Manipulationen:

string.hasPrefix("start")
string.hasSuffix("end")

// Letztes Zeichen entfernen (Swift 3)
var truncated = name.substringToIndex(name.endIndex.predecessor())
// Alternativ in-place
name.removeAtIndex(name.endIndex.predecessor())

// Letztes Zeichen entfernen (Swift 4)
name.dropLast()

Regex-Replacement:

let input = "ABC bla-123 DEF bla-42 GHI"

let regex = try! NSRegularExpression(pattern: "bla-(\\d+)", options: [])
let output = regex.stringByReplacingMatchesInString(
    input, options: [], range: NSMakeRange(0, input.characters.count), withTemplate: "blubb-$1")

// output = "ABC blubb-123 DEF blubb-42 GHI"

Formatierung (siehe Doku):

// Zahl mit zwei Dezimalstellen
let str = String(format: "%.2f", 10.426123)  // 10.43

// Zahl ohne Dezimalstellen
let str = String(format: "%d", 10.426123)    // 10

Collections

Siehe Apple-Doku CollectionTypes

Arrays:

var myArray = [Int]()     // Erzeugt leeres [Int]
var myArray: [Int] = []   // Erzeugt leeres [Int] (Alternative)
var myArray: Array(repeating: 0, count: 3)  // Erzeugt [0, 0, 0]

myArray = [0, 1] + [2, 3]  // [0, 1, 2, 3]
myArray += [4]             // [0, 1, 2, 3, 4]
myArray[0...2] = [42]      // [42, 3, 4]

myArray.append(3)
print("myArray has \(myArray.count) items.")

if myArray.isEmpty {
   ...
}

for item in myArray {
    ...
}

for (index, value) in myArray.enumerated() {
    ...
}

Dictionaries:

var myDict = [String: Int]()       // Erzeugt leeres Dictionary
var myDict: [String: Int] = [:]    // Erzeugt leeres Dictionary (Alternative)
var myDict: [ "a": 15, "b": 42 ]

myDict["c"] = 28
myDict["b"] = nil  // Entfernt "b" aus Dictionary

print("myDict has \(myDict.count) items.")

if myDict.isEmpty {
    ...
}

if let name = myDict["d"] {
    print("name: \(name).")
} else {
    print("myDict has no 'd'.")
}

for (key, value) in myDict {
    ...
}

for key in myDict.keys {
    ...
}

for value in myDict.values {
    ...
}

Enums, Klassen, Strukturen, Protokolle

enum State {
    case idle, busy, failed
}

class MyClass {

    static let myStaticConstant = 42

    var myVar = 1
    let myConstant = "Hi"

    func myMethod(param: String) -> String {
        return param
    }

}

struct MyStruct {
}

protocol MyProtocol {
    var myConstant: Int { get }
    func myMethod(param: String) -> String
}

Singleton

class MyClass {

    static let shared = MyClass()

    private init() {
    }

}

Properties

Details siehe Doku

class MyClass {
    var myVar = 1
    let myConstant = "Hi"

    lazy var importer = DataImporter()

    public private(set) var myReadonlyVar = 42

    var myVar: Int = 0 {
        willSet(newValue) {
            ...
        }
        didSet {
            print("Changed from \(oldValue) to \(myVar)")
        }

    // Computed
    var center: Point {
        get {
            return Point(x: ..., y: ...)
        }
        set(newCenter) {
            ...
        }
    }
}

Guards

func foo() {
    guard
        let foo = bar,
        let qux = taco
        else { return }
}

Sichtbarkeit (Access Control)

Details siehe Apple-Doku zu Access Control.

open var myVariable = 1;         // Überall sichtbar und erweiterbar, auch von anderen Modulen
public var myVariable = 1;       // Überall sichtbar, auch von anderen Modulen
internal var myVariable = 1;     // Innerhalb des Moduls sichtbar
fileprivate var myVariable = 1;  // Innerhalb der Quellcode-Datei sichtbar
private var myVariable = 1;      // Sichtbar in der umgebenden Deklaration und
                                 // deren Erweiterungen in der selben Quellcode-Datei

var myVariable = 1;              // Implizit internal

Fehlerbehandlung

Fehler werfen:

enum ThrowableError : Error { case badError }

func doSomething() throws -> String {
    if everythingIsFine {
        return "Everything is ok"
    } else {
        throw ThrowableError.BadError
    }
}

try mit do-catch-Block:

do {
    let result = try doSomething()
} catch ThrowableError.BadError {
    // Handle specific error
    print("Bad error")
} catch let error as NSError {
    // Handle NSError
    print(error.localizedDescription)
} catch {
    // Handle unspecific error
    print("Some other error: \(error)")
    // Or re-throw
    throw error
}

try mit throws:

func doSomeOtherThing() throws -> Void {
    // Any errors will be re-thrown to callers.
    let result = try doSomething()
}

try!:

// An error will cause a CRASH!
let result = try! doSomething()

try? mit if-let:

if let result = try? doSomething() {
    // doSomething succeeded, and result is unwrapped.
} else {
    // Ouch, doSomething() threw an error.
}

try? mit guard:

guard let result = try? doSomething() else {
    // Ouch, doSomething() threw an error.
}
// doSomething succeeded, and result is unwrapped.

Delegates

Definition:

protocol MyClassDelegate: class {
    func myClass(_: MyClass, onEventWithParam: ParamType)
    func myClassOnEventWithoutParam(_: MyClass)
}

extension MyClassDelegate {
    // Makes implementation optional
    func myClassOnEventWithoutParam(_: MyClass) {}
}

class MyClass {

    weak var delegate: MyClassDelegate?

    ...
}

Benutzung:

class MyUsingClass {
    ...
}

// MARK: - MyClassDelegate

extension MyUsingClass: MyClassDelegate {

    func myClass(_: MyClass, onEventWithParam: ParamType) {
        ...
    }

}

NSData

NSData -> String:

var string = String(data: nsData, encoding: .utf8)

String -> NSData:

var nsData = "my string".dataUsingEncoding(NSUTF8StringEncoding)!

JSON

JSON encoding:

// Mit JSONEncoder (Mit JSONEncoder lassen sich auch Structs usw. direkt encoden)
let dict = [ "my": "data" ]
let jsonEncoder = JSONEncoder() // One currently available encoder
let jsonData = try jsonEncoder.encode(dict)

// Mit JSONSerialization
let jsonData = try JSONSerialization.data(withJSONObject: value, options: [])

JSON decoding:

// Mit JSONDecoder
let jsonDecoder = JSONDecoder()
let dict = try jsonDecoder.decode(NSDictionary.self, from: nsData)

// Mit JSONSerialization
let dict = try? JSONSerialization.jsonObject(with: nsData, options: []) as? [String: Any]

UIImageView

let imageView = UIImageView(image: UIImage(named: "my_resource_name"))
imageView.contentMode = .Center           // Center image, don't scale
imageView.contentMode = .ScaleAspectFill  // Fill size of view
imageView.clipsToBounds = true            // Clip at view bounds

UIStackView

let stackview = UIStackView()
stackview.axis = .Vertical  // Default: .Horizontal

// Along stack-axis. Default: .Fill (using hugging priority)
stackview.distribution = .FillEqually
stackview.distribution = .FillProportionally // (using intrinsic size)
stackview.distribution = .EqualSpacing
stackview.distribution = .EqualCentering

// Along opposite axis. Default: .Fill (full width / height)
stackview.alignment = .Leading   // or .Top
stackview.alignment = .Center
stackview.alignment = .Trailing  // or .Bottom

// Min. spacing between views. Default: 0
stackview.spacing = 0

UITableViewController

Links:

Code-Vorlage (Swift 3):

import UIKit

class MyTableController: UITableViewController {

    var items: [MyItemType]

    // MARK: - Life cycle

    init() {
        super.init(style: .plain)  // Or .grouped

        tableView.register(Cell.self, forCellReuseIdentifier: "cell")
        tableView.separatorStyle = .none

        // Add spacing (or something else) above or below the table
        // No auto-layout allowed here!
        tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 16))
        tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 16))
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        tableView.backgroundView = nil
        tableView.backgroundColor = UIColor.clear
    }

    private func updateContents() {
        // Update items

        self.tableView.reloadData()
    }

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! Cell
        cell.update(item: items[indexPath.row])

        return cell
    }

    // MARK: - Table view delegate

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 44 // Adjust row height
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let item = items[indexPath.row]

        // Do something with the selected item

        tableView.deselectRow(at: indexPath, animated: true)
    }

    class Cell: UITableViewCell {

        override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)

            // Define own selection color
            self.backgroundColor = UIColor.clear
            self.backgroundView = nil

            let selectedBgView = UIView()
            selectedBgView.backgroundColor = UIColor.red
            self.selectedBackgroundView = selectedBgView

            // Or make the cell unselecable
            selectionStyle = .none

            // Show right arrow
            accessoryType = .disclosureIndicator

            // Fill contentView
            let contentView = self.contentView
            contentView.addSubview(...)
        }

        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }

        func update(item: MyItemType) {
            // Update cell
        }

    }

}

Zelle mit Details erzeugen:

let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
cell.textLabel!.text = "My main text"
cell.detailTextLabel!.textColor = UIColor(white: 0.5, alpha: 1)
cell.detailTextLabel!.adjustsFontSizeToFitWidth = true
cell.detailTextLabel!.text = "My subtitle"
cell.accessoryType = .disclosureIndicator

Zelle mit Switch erzeugen:

func createSwitchCell() -> UITableViewCell {
    let mySwitch = UISwitch()
    mySwitch.addTarget(self, action: #selector(onSwitchChange), for: .valueChanged)

    let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
    cell.textLabel!.text = Util.msg("settings_showCheckoutAlerts")
    cell.accessoryView = mySwitch
    cell.selectionStyle = .none

    return cell
}

func onSwitchChange(control: UISwitch) {
    print("## switch state: \(control.isOn)")
}

Zelle disablen:

cell.isUserInteractionEnabled = false
cell.textLabel?.isEnabled = false
cell.detailTextLabel?.isEnabled = false

Spinner zeigen:

// Show spinner
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .gray)
//spinner.center = view.center // Doesn't work properly
spinner.center = CGPoint(x: view.bounds.width / 2, y: 80)
spinner.startAnimating()
view.addSubview(spinner)

// Hide spinner
spinner.removeFromSuperview()

Code im Hintergrund oder Main-Thread ausführen

Normalerweise befindet man sich immer im Main-Thread. Manchmal wird Code jedoch in einem Hintergrundthread ausgeführt - z.B. im completionHandler eines NSURLRequest. Das macht in diesem Fall ja auch Sinn, dann kann man die Server-Antwort parsen ohne den Main-Thread zu blockieren. Oder aber man möchte selbst etwas im Hintergrund ausführen.

Code im Hintergrund und im Main-Thread ausführen (ab Swift 3):

DispatchQueue.global(qos: .background).async {
    // Code for background thread

    DispatchQueue.main.async {
        // Code for main thread
    }
}

Code mit Delay im Main-Thread ausführen (ab Swift 3):

DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { // after one second
    // Code for main thread
}

App Transport Security policy umgehen

Apple erlaubt mit seiner "App Transport Security policy" keine Kommunikation ohne SSL mehr. Man kann allerdings in der Info.plist Ausnahmen definieren. Das ist OK für die Entwicklung, dürfte jedoch ein Apple Review zum Scheitern bringen.

Info.plist erweitern:

    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>

Man kann auch genauere Regeln für bestimmte Domains angeben. Weitere Details siehe Apple-Doku.

Code-Documentation

Siehe:

Code wird in einem Markdown-Dialekt dokumentiert. Die Doku wird direkt in XCode gezeigt (per Alt+Klick auf einem Identifier).

Typische Beispiele:

/**
    Repeats a string `times` times.

    - Parameter str:   The string to repeat.
    - Parameter times: The number of times to repeat `str`.

    - Throws: `MyError.InvalidTimes` if the `times` parameter 
        is less than zero.

    - Returns: A new string with `str` repeated `times` times.
*/
func repeatString(str: String, times: Int) throws -> String { ... }

/// Returns the magnitude of a vector in three dimensions
/// from the given components.
///
/// - Parameters:
///     - x: The *x* component of the vector.
///     - y: The *y* component of the vector.
///     - z: The *z* component of the vector.
func magnitude3D(x: Double, y: Double, z: Double) -> Double { ... }

/**
    Mechanism for converting pedal power into motion.

    - Fixed: A single, fixed gear.
    - Freewheel: A variable-speed, disengageable gear.
*/
enum Gearing {
    case Fixed
    case Freewheel(speeds: Int)
}

/// The style of the bicycle.
let style: Style

Weitere Keywords:

/**
    - Attention: Watch out for this!
    - Author: Tim Cook
    - Authors:
      John Doe
      Mary Jones
    - Bug: This may not work
    - Complexity: O(log n) probably
    - Copyright: 2016 Acme
    - Date: Jan 1, 2016
    - Experiment: give it a try
    - Important: know this
    - Invariant: something
    - Note: Blah blah blah
    - Precondition: alpha should not be nil
    - Postcondition: happy
    - Remark: something else
    - Requires: iOS 9
    - Seealso: something else
    - Since: iOS 9
    - Todo: make it faster
    - Version: 1.0.0
    - Warning: Don't do it
*/

HTML API-Doku generieren

Mit dem Tool jazzy, kann man eine HTML-API-Doku generieren.

sudo gem install jazzy
cd myproject
jazzy

Eine README.md im Projektverzeichnis ablegen um eine Startseite zu erhalten.

Mit Hilfe einer .jazzy.yaml im Projektverzeichnis kann die Doku noch genauer konfiguriert werden (Details siehe: jazzy --help config):

#module_name: My module
author_name: Junghans und Schneider
author_url: http://junghans-schneider.de/
copyright: © 2016 Junghans und Schneider. All rights reserved.
#hide_documentation_coverage: true

custom_categories:

  - name: Logic
    children:
    - MyLogicClass

  - name: Util
    children:
    - myStaticFunction()
    - MyUtilClass

Was an Swift nervt

Es gibt keine Namespaces

Frameworks verwenden Klassennamenpräfixe. Weitere Unterteilung nur mit virtuellen Gruppen, die für den Nutzer eines Frameworks nicht sichtbar sind.

String-API sehr gesprächig

(Stand Swift 2)

Man kann sich für einen Enum-Wert keinen String geben lassen

Offizielle Empfehlung:

enum Foo {
    case Bing;
    case Bang;
    case Boom;

    func description() -> String {
        switch self {
        case .Bing: return "Bing";
        case .Bang: return "Bang";
        case .Boom: return "Boom";
        }
    }
}

Statische Klassenvariablen müssen von Instanzen mit vollem Namen verwendet werden

class MyClass {
    static let MY_CONST = 5

    func foo() {
        let a = MY_CONST         // Geht nicht
        let a = MyClass.MY_CONST // Geht
    }
}

Funktions-Pointern erzeugen Speicher-Lecks

Man kann mit Funktions-Pointern sehr leicht Speicher-Lecks durch zyklische Referenzen bauen.

Beispiel: Man muss für Delegates Protokolle statt Structs mit Funktions-Pointern verwenden.

Structs mit Funktions-Pointern:

class MySubViewController: UIViewController {

    struct Delegate {
        var onBack: () -> Void
    }

    var controlDelegate: Delegate?

    ...

    func bla() {
        controlDelegate?.onBack()
    }

}
class MyMainViewController: UIViewController {

    ...

    private lazy var mySubView: MySubViewController = {
        let controller = MySubViewController()
        controller.controlDelegate = MySubViewController.Delegate(
            onBack: self.showOverview
        )
        return controller
    }()

}

Mit Protokollen:

protocol MySubViewControllerDelegate {
    func mySubViewOnBack(_: MySubViewController)
}

class MySubViewController: UIViewController {

    var controlDelegate: MySubViewControllerDelegate?

    ...

    func bla() {
        controlDelegate?.mySubViewOnBack(self)
    }    

}
class MyMainViewController: UIViewController {

    ...

    private lazy var mySubView: MySubViewController = {
        let controller = MySubViewController()
        controller.controlDelegate = self
        return controller
    }()

    ...

}

// MARK: - MySubViewControllerDelegate

extension MyMainViewController: MySubViewControllerDelegate {

    func mySubViewOnBack(_: MySubViewController) {
        self.showOverview()
    }

}

Protokolle können keine Konstanten enthalten

Stand: Swift 4.1

Geht nicht:

protocol MyProtocol {
    static let done: Character = "\u{FFFF}"  // Geht nicht
    func getNextChar() -> Character
}

Statt dessen mit Struct:

struct MyProtocolConstants {
    static let done: Character = "\u{FFFF}"
}

protocol MyProtocol {
    func getNextChar() -> Character
}

Statt dessen mit Extension:

protocol MyProtocol {
    var done: Character { get }
    func getNextChar() -> Character
}

extension MyProtocol {
    var done: Character { return "\u{FFFF}" }
}