Last active
January 1, 2018 19:11
-
-
Save IngmarBoddington/ff4dbfb8260743f6d18299593ebde5f8 to your computer and use it in GitHub Desktop.
Swift notes from iOS dev learning - assumes Java knowledge.
Test apps are here: https://github.com/IngmarBoddington/XcodeProjects
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
General | |
====== | |
- Strongly typed, object oriented | |
- Semi-colons are optional to end statements | |
- // or /* */ for comments | |
- Used across Apple platforms | |
- Execution starts in main.swift | |
- Libraries imported using import statements | |
- Can use any unicode character in names | |
- Short circuits evaluations of conditional statements | |
- Functions and tuples are unnamed compound types (all others are named types) | |
- Any names type can have methods (classes, structs, enums) | |
Variables + Simple Types / Operators | |
===== | |
All variables are value types, except objects which are reference types | |
Standard Types | |
String (Double quoted) | |
Int (Also Int8, Int16... and UInt8, UInt16...) | |
Double (inferred by default over Float) | |
Float | |
Boolean | |
Integer literals | |
Decimal, e.g. 234 | |
Binary, e.g. 0b101 | |
Octal, e.g. 0o76 | |
Hexadecimal, e.g. 0xA3 | |
Floating-point literals | |
Decimal, e.g. 32.4 | |
Exponent, e.g. 1.5e2 | |
Hexadecimal, e.g. 0x1Fp2 | |
Use var for variables or let for constants | |
var <identifier> = <value> | |
let <identifier> = <value> | |
//define explicit type using ':' - This is generally not required for simple types | |
var string: String = "I am a string" | |
//Can mix on same line | |
var varOne = 1, varTwo = 2, varThree: Int | |
Can define without initialising (but must be initialised before use) | |
Variables can generally be cast by using initialisers for the various types: | |
Int(<value>) //Will truncate floating point numbers | |
String(<value>) | |
etc | |
Standard Maths Operators / logic | |
= //Does not return any value on assignment / fail | |
+,-,*,/,% | |
<,>,<=,>=,== | |
||,&&,! | |
===, !== | |
( condition ? A : B ) | |
x...y //Range, Ints only | |
x..<y //Range (not including value of y), Ints only | |
Can use _ to represent a temp variable which is not used (e.g iterator in for loop) | |
Strings | |
===== | |
var str = "Hello"; | |
//Iterate over characters and print | |
for character in newString.characters { | |
print(character, "Can", "add", "more (spaces are added automatically)"); | |
} | |
//Optionally add separator / terminator | |
print("hello", "this is", "a", "test", separator: "||", terminator: "***") | |
//Create a string with manipulation methods | |
var newTypeString = NSString(string: newString); | |
//Substring | |
NSString(string: newTypeString.substring(from: 6)).substring(to: 4); | |
//Substring | |
newTypeString.substring(with: NSRange(location: 6, length: 4)); | |
//Matching | |
if newTypeString.contains("Dave") { | |
print("String contains Dave"); | |
} | |
//Split into array | |
newTypeString.components(separatedBy: " "); | |
//Case | |
newTypeString.uppercased | |
newTypeString.lowercased | |
//Interpolate in Strings, concatenate strings | |
var string = "My age is \(age)" + ", more" | |
Collections | |
===== | |
//Arrays - stupid number of ways to create | |
//Value type (but only copied when needed as an optimisation) | |
var a1:[Int] = [Int]() | |
var a2:[Int] = [Int](repeating: 10, count: 4) | |
var myArray:Array<Int> = Array<Int>() | |
vara3:Array<Int> = Array<Int>(repeating: 0, count: 5) | |
var myArray = [1, 2, 3, 4] | |
let myArray: [Int] = Array(1...50); | |
myArray[1] === 2 | |
myArray[1...3] = [3,4,5,6,7] //Can do daft range expansion | |
array.count | |
array.isEmpty | |
array.removeLast() | |
array.append(<value>) | |
array.remove(at: <index>) | |
//Dictionaries | |
varenglishFrench: Dictionary<String,String> | |
var myDic = ["key" : "value", "key2" : "value"] | |
myDic["newKey"] = <value> | |
myDic.removeValue(forkey: "key") | |
myDic.updateValue(forkey: "key") //Will add and return null if didn't exist | |
myDic.keys | |
myDic.values | |
//Sets | |
//A set can only store types that conform to the Hashableprotocol and an implementation of ==(the “is equal to” operator) (Most types are hashableby default) | |
var myCustomers= Set<String>() | |
var myFriends: Set<String> = Set<String>() | |
myFriends.insert("John"); myFriends.insert("Duncan"); myFriends.insert("Pam") | |
var yourFriends: Set = ["Pam", "Paul", "Keith"] | |
var numbers: Set<Int> = [10,20,30] | |
numbers = [] // numbers is now an empty set | |
let setA: Set = [1,2,3,4] | |
let setB: Set = [3,4,5,6] | |
var res = setA.union(setB) // [1,2,3,4,5,6] | |
res = setA.subtracting(setB) // [1,2] | |
res = setA.intersection(setB) // [3,4] | |
res = setA.symmetricDifference(setB) // [1,2,5,6] | |
//All collections must have types, var / let determine if mutable | |
var myArray : [<type>] | |
var myDic : [<keyType> : <valueType>] | |
QOWDJIOQWJDOIW | |
Control Structures | |
================== | |
Use break to exit a loop | |
Use continue to skip to next loop | |
//If | |
if <condition> { | |
//If true | |
} | |
//If / else | |
if <condition> { | |
//If true | |
} else { | |
//if False | |
} | |
//If / else-if | |
if <condition> { | |
//If true | |
} else if <condition> { | |
//if true | |
} else { | |
//if False | |
} | |
//While loop | |
while <condition> { | |
//Stuff | |
} | |
//Do-While loop | |
do { | |
//Stuff | |
} while <condition> | |
//Iterate over array or a range | |
for <identifier> in <arrayIdentifier|range> { | |
//Stuff with <identifier> | |
} | |
//Iterate over dictionary tuples | |
for (<indexIdentifier>, <valueIdentifier>) in <arrayIdentifier>.enumerate() { | |
//Stuff with <indexIdentifier> / <valueIdentifier> | |
} | |
//Switch statement - must be exhaustive (e.g. Int switch will have to have a default) | |
//Skips rest of case statements on match | |
switch variable { | |
case value1: | |
//Stuff | |
case value2: | |
//Stuff | |
case x...y: //Can use ranges | |
//Stuff | |
case x, y, z: | |
//Stuff | |
case var m where m < 0 || m > 100: //Can use a where clause with temp variable | |
//Stuff | |
default: | |
//Stuff | |
} | |
//Loops can be labelled to allow for use of break at parent levels | |
outerLoop: for i in 1...5 { | |
for j in 1...5 { | |
if i == 3 && j == 2 { | |
break outerLoop | |
} else { | |
print("i is \(i) and j is \(j)") | |
} | |
} | |
} | |
Functions | |
===== | |
Return definition required if the function has a return statement | |
//Func with return and two params (one with a defined external naming which overrides the internal name when invoking function) | |
//Can use _ as external name which means that no parameter naming is required when invoking function | |
func funcName (varInternalName: Int, varExternalName varInternalNameTwo: String) -> resultType { | |
statements | |
return value:resultType | |
} | |
//With a default value | |
func funcName (varInternalName: Int = 8) -> resultType { | |
statements | |
return value:resultType | |
} | |
//Variadic parameters | |
func myFunc(something: Int...) { | |
//Stuff | |
} | |
myFunc(1,2,3,4) | |
//Pass by reference | |
func swapNums(num1:inout Int, num2:inout Int) { | |
let temp = num1 | |
num1 = num2 | |
num2 = temp | |
} | |
swap(&x, &y) | |
//Type description | |
(Type, Type, Etc) () | |
(Type, Type, Etc) -> Type | |
(Type, Type, Etc) -> (Type, Etc) | |
//Can set to var | |
var sillyFunc = { (a: Int, b: Int) -> Bool in a > b) } | |
//SHhrtest closure form example - params are zero index, types and return have been inferred | |
sortedNumList= numList.sorted(by: { $1 < $0 }) | |
sortedNumList= numList.sorted(){ $1 < $0 } | |
Optionals | |
===== | |
- Values which can be nil / not set | |
- To force unwrap use exclamation mark after variable - reverts to type from optional | |
//Define: | |
var variable: Type? | |
let variable: Type? | |
//Can test with standard nil checks or: | |
if let variable = optional { | |
/... | |
} else { | |
/... | |
} | |
Tuples | |
====== | |
Simple unnamed strongly typed data structure | |
Can be nested | |
e.g. var myTuple = ("I Am A Tuple", (10, 5)) | |
Access using zero index | |
myTuple.0 //I Am A Tuple | |
Can use in switch statement | |
switch myTuple { | |
case ("Meh", (10, 5): | |
//No match | |
case (_, let(first, second)): | |
//Hit and disregards first value in tuple and inits first and second vars | |
case (_, let(first, second)) where <logic> | |
//etc | |
default <etc> | |
//etc | |
} | |
Access Modifiers | |
===== | |
Access (to variables and types) can be controlled with the keywords open, public, private, fileprivate, and internal | |
Default is internal | |
Open access and public access enable entities to be used within any source file from their defining module, and also in a source file from another module that imports the defining module. You typically use open or public access when specifying the public interface to a framework. | |
Internal access enables entities to be used within any source file from their defining module, but not in any source file outside of that module. You typically use internal access when defining an app’s or a framework’s internal structure. | |
File-private access restricts the use of an entity to its own defining source file. Use file-private access to hide the implementation details of a specific piece of functionality when those details are used within an entire file. | |
Private access restricts the use of an entity to the enclosing declaration, and to extensions of that declaration that are in the same file. Use private access to hide the implementation details of a specific piece of functionality when those details are used only within a single declaration. | |
Classes | |
==================== | |
All classes must have a designated initialiser (a constructor) | |
Any other (optional) initialisers must be marked as "convenience" and call the designated initialiser | |
One is provided if no parameters or all parameters have default | |
All parameters must either have a default or be set by the designated initialiser | |
Can declare properties or method as static | |
class ClassName { | |
var property | |
//"Designated" initialiser | |
init(property: Int){ | |
// | |
} | |
//Optional "Convenience" initiliser | |
convenience init() | |
self.init(property: 5) | |
} | |
func method(vars) { | |
//.... | |
} | |
} | |
var class = ClassName(5) | |
class.method(vars) | |
Methods can have same name, different parameter types / names / defaults | |
Classes can have a deinit destructor method with no args too | |
Properties can be marked as lazy (only calculated when needed) | |
lazy var value = Expensive() | |
Properties can be normal "stored" properties or "computed" properties | |
//Computed property (get / set) | |
var value: Int { | |
get { | |
// | |
} | |
set (valueName) { | |
//will get newValue if no valueName given | |
} | |
// | |
//Get only, do not need to include block: | |
var value: Int { | |
// | |
} | |
Can "adopt" protocols or inherit from parent classes | |
Methods which override an existing parent method must start with 'override' keyword | |
class myClass: parentClassOrProtocol { | |
init() { | |
self.stuff() | |
super.stuff() | |
} | |
} | |
Protocols | |
===== | |
Interfaces, basically. Classes can adopt as many as they like | |
protocol { | |
func funcName() -> Type | |
var variable: Int | |
} | |
Extensions | |
===== | |
Add to source without altering original code | |
Cannot use stored properties | |
Can adopt a protocol to make a class adopt it | |
extension classBeingExtended { | |
//Code | |
} | |
Structures | |
====== | |
Extremely similar to classes in definition (includes all of the above syntax) | |
Value type, not reference like object | |
Cannot use inheritance | |
A default initializer and a memberwiseinitializer are automatically generated (if none defined) | |
Memberwise is simply ordered constructor with all params of the struct | |
Methods which change state must be marked as mutating (as value types should be immutable) | |
mutating method(vars) { | |
//can replace entire instance | |
self = ckass(changesVars) | |
} | |
Can aggregate further structs (can access using chained dot operators) | |
parent.child.parameter | |
Enumerations | |
===== | |
Extremely similar to classes in definition (includes all of the above syntax) - but defines a set | |
//Example with days of week and 1 -> 7 as rawValues (access Key) | |
enum WeekDay:Int { | |
case Monday=1, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday | |
} | |
var day = enum.Monday | |
let todayNum= day.rawValue | |
varday4 = WeekDay(rawValue: 4) //Returns an optional | |
//Can shortcut when used in switch statements: | |
switch day { | |
case .Monday: | |
//Stuff | |
case .Tuesday: | |
//Stuff | |
... | |
default: | |
//Stuff | |
} | |
//Can also have different states as tuples | |
enum State { | |
case Start(String) | |
case DoWork(Int, Int, Int) | |
case End(String, Int) | |
} | |
vartheState: State = State.Start("about to start the work") | |
theState = State.DoWork(3,4,5) | |
theState = State.End("Finished", result) | |
Error Handling | |
===== | |
Enums which implement "Error" can be thrown and caught | |
Code must state (using try) when calling a method which may throw | |
enum myError: Error { | |
case IllegalArgs(args: [String]) | |
case ... | |
} | |
func funcWhichCanThrow() { | |
throw myError.IllegalArgs(["Meh"]) | |
} | |
do { | |
try funcWhichCanThrow | |
} catch myError.IllegalArgs { | |
// | |
} | |
//Will always be executed even if error in surrounding block | |
defer { | |
// | |
}} | |
//Converts result to null on exception throw | |
let var = try? func() | |
//Error suppression! :( | |
try! func() | |
//Guard statements (inverse if for error catching generally) | |
guard <condition> else { | |
//code, throw maybe | |
} | |
Generics | |
===== | |
Generally use T symbol but can use anything | |
class sillyClass<T> { | |
init() { | |
//Do stuff with type T | |
} | |
... | |
} | |
//Forcing adherence to type | |
class sillyClass<T: Type> { | |
init() { | |
//Do stuff with type T | |
} | |
... | |
} | |
func genericSwap<T>(_ item1 : inoutT, _ item2 : inoutT) { | |
let temp = item1 | |
item1 = item2 | |
item2 = temp | |
} | |
//Adding further constraints | |
func myFunc<T1: Sequence where T1.Generator.Element : Person>(theList: T1) -> T1.Generator.Element? { | |
//Stuff | |
} | |
Other Stuff | |
----- | |
arc4random_uniform(<limit>) //Random nums | |
-------------------------------------------------------------- | |
iOS APIs / XCode | |
View Controllers | |
===== | |
Common methods: | |
viewDidLoad - Initial load delegate method | |
viewDidAppear - User navigated to view delegate method | |
didReceiveMemoryWarning - | |
Actions | |
===== | |
A method which is invoked by an event (managed / brough in through the storyboard | |
@IBAction <definition> | |
Outlets | |
======= | |
A property representing a view item which can be manipulated | |
@IBOutlet <definition> | |
Table Views | |
===== | |
Need to extend UITableViewDelegate, UITableViewDataSource and implement: | |
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int | |
-> Return number of rows in table view | |
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell | |
-> Return content of a cell | |
Use <tableView>.reloadData(); to update the table with any changes | |
An example delete swipe: | |
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { | |
if editingStyle == UITableViewCellEditingStyle.delete { | |
items.remove(at: indexPath.row); | |
UserDefaults.standard.set(items, forKey: "list") | |
self.tableList.reloadData(); | |
} | |
} | |
Web View | |
===== | |
Example load URL in webview: | |
let url = URL(string: "http://www.stackoverflow.com")!; | |
<webviewOutlet>.loadRequest(URLRequest(url: url)); | |
Example load HTML: | |
<webviewOutlet>.loadHTMLString("<h1>Hello World!</h1>", baseURL: nil); | |
Note (if insecure content error, can add exception): | |
https://stackoverflow.com/questions/31254725/transport-security-has-blocked-a-cleartext-http | |
Example scrape (asynchonous): | |
if let url = URL(string: "http://www.stackoverflow.com") { | |
let request = URLRequest(url: url); | |
let task = URLSession.shared.dataTask(with: request) { | |
data, response, error in | |
if error != nil { | |
print(error!); | |
} else { | |
if let unwrappedData = data { | |
let dataString = NSString(data: unwrappedData, encoding: String.Encoding.utf8.rawValue); | |
print(dataString!); | |
} | |
} | |
} | |
task.resume(); | |
} | |
Persistant Storage | |
===== | |
UserDefaults | |
UserDefaults.standard.set(<value>, forKey: <key>); | |
- Save a value | |
let <object> = UserDefaults.standard.object(forKey: <key>); | |
- Fetch a value object | |
Use a let check to ensure a value is set before use when fetching from store | |
Keyboard Control | |
===== | |
To close keyboard when user taps off the keyboard: | |
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { | |
self.view.endEditing(true); | |
} | |
To close keyboard on return tap, need to extend UITextFieldDelegate and | |
func textFieldShouldReturn(_ textField: UITextField) -> Bool { | |
textField.resignFirstResponder(); | |
return true; | |
} | |
Timers | |
====== | |
Set a timer using normally | |
Timer.scheduledTimer(timeInterval: <#T##TimeInterval#>, target: <#T##Any#>, selector: <#T##Selector#>, userInfo: <#T##Any?#>, repeats: <#T##Bool#>) | |
Stop a timer using: | |
<Timer>.invalidate(); | |
e.g. | |
if (self.started == false) { | |
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.tickerFunction), userInfo: nil, repeats: true) | |
self.buttonText.setTitle("Stop!", for: .normal); | |
} else { | |
self.timer.invalidate(); | |
self.buttonText.setTitle("Start!", for: .normal); | |
} | |
Animation | |
===== | |
(Use above timer methods) | |
Set a new image for an UIImageView: | |
self.image.image = UIImage(named: <string>); | |
Simple fade in example: | |
@IBAction func fadeIn(_ sender: Any) { | |
<image>.alpha = 0; | |
UIView.animate(withDuration: <time>, animations: { | |
<image>.alpha = 1; | |
}) | |
} | |
Simple slide in example (from left): | |
@IBAction func slideIn(_ sender: Any) { | |
<image>.center = CGPoint(x: image.center.x - <offscreenValue>, y: image.center.y); | |
UIView.animate(withDuration: <time>, animations: { | |
<image>.center = CGPoint(x: self.image.center.x + <offscreenValue>, y: self.image.center.y) | |
}) | |
} | |
Simple grow example (from top left) | |
@IBAction func grow(_ sender: Any) { | |
<image>.frame = CGRect(x: 0, y: 0, width: 0, height: 0); | |
UIView.animate(withDuration: 5, animations: { | |
<image>.frame = CGRect(x: 0, y: 0, width: 200, height: 200); | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment