Swift
Eine Snippet-Sammlung für Swift.
Links:
- Swift-Tour
- Swift-API
- Push Notifications Tutorial
- pushy - A java lib for sending push notifications on server-side
- Error handling
- UnsafePointer-Geraffel
- Character-Geraffel
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:
- Apple Doku Table View Styles and Accessory Views
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)
- Java:
myString.trim()
Swift:myString.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
- Java:
myString.replaceAll("a", "b")
Swift:myString.stringByReplacingOccurrencesOfString("a", withString: "b")
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}" }
}