Section 1: Getting Started with iOS 13 and Swift 5.1

1. Useful resources

https://www.appbrewery.co/p/ios-course-resources/

13. Getting set up with Xcode

Check you have enough space on disk

Make sure MacOS is 10.15+

Section 2: Xcode Storyboards and Interface Builder

17. Let’s Create a Brand New Xcode Project

Create a new Project

Choose a template: Application - Single View App. Because other views can be added later on.

Options: Organization identifier - reverse domain name (e.g. com.yuexit). Language - Swift. User interface - Storyboards (to begin with). Leave checkboxes unchecked.

19. Let’s Design the User Interface!

LaunchScreen.storyboard: screen that shows up on the first launch of app.

Design on the Main.storyboard.

View attributes: background (useful resource: ).

Adding components: “+ (library)” on right top. Drag them to the Main.storyboard.

Layers: first in list = bottom, last = top

20. Let’s Incorporate Some Image Assets

Drag custom images to Assets.xcassets. Image view attributes: image - your image.

Image size and names: image.png, image@2x.png, image@3x.png (online tool: )

21. How to Design and Add an App Icon

Design resource:

Icon image autogeneration:

23. Run Your App on Your iPhone or Simulator

iPhone: Xcode preferences - Accounts - add Apple ID. Make sure iPhone iOS version matches Xcode version (iOS 13.x to Xcode 11.x). Connect iPhone via USB or same WiFi.

Simulator: Choose device/model on left top. Play button on left top.

Section 4: Swift Programming Basics - Collections, Constants & Variables

36. Cloning from GitHub and How to Download the L.A.B. Project Stubs

To clone from GitHub: Xcode menu - Source Control - clone - enter git address.

37. How to Design Your App

To copy a component, hold “option” and drag it.

Open editor assistant: right top “Adjust Editor Options” - Assistant.

Hold “control” and drag from component to inside “class ViewController: UIViewController”.

Do not rename IBOutlet names directly, or the connection will be broken. To fix it, right click the component, delete the outlet, and reconnect them by dragging.

To correctly rename IBOutlet, right click the IBOutlet name, refactor, rename.

To set view image: outletXXX.image = imageliteral. Then double click imageliteral to set image.

39. Responding to User Interactions with IBActions

Open editor assistant: right top “Adjust Editor Options” - Assistant.

Hold “control” and drag from component to inside “class ViewController: UIViewController”.

42. Storing Data using Variables and Arrays

Array: [ element1, element2, … ]

Print: print(“some text (some variable)”)

46. How to Randomise the Dice Images

Random number: Int.random(in: 0..5)

Random element of an array: someArray.randomElement()

Constant variable: let someVar = someValue

Variable variable: var someVar = someValue

Section 6: Auto Layout and Responsive UIs

65. Setting Constraints and working with the Safe Area

Adding constraints: bottom right - Add constraints.

Super view, safe area and margin.

67. Working with Containers and Subviews

To embed components into a view:

  1. Create a view and drag components under it.

  2. Choose components - menu - editor - embed in - view.

  3. Choose components - bottom right - embed in - view.

To rename a view: right - identity - label.

68. Stack Views

Put related components into a stack view. They can be arranged automatically.

Section 7: Using and Understanding Apple Documentations

76. The 5 Step Approach to Solve Any Programming Problem

  1. Google

  2. Stack Overflow

  3. Implements

  4. Docs (hold “option” and click a word to open Apple Developer Documentations)

  5. Customise

79. Linking Multiple Buttons to the Same IBAction

Drag from the dot at the left of IBAction to all related buttons.

80. Functions with Inputs and Type Inference

@IBAction func keyPressed(_ sender: UIButton) {
    playSound(soundName: sender.currentTitle!)
}

func playSound(soundName: String) {
    let url = Bundle.main.url(forResource: soundName, withExtension: "wav")
    player = try! AVAudioPlayer(contentsOf: url!)
    player.play()    
}
func divide(n1: Int, n2: Int){
  let n3 = Double(n1)/Double(n2)
  print(n3)
}

Section 8: Intermediate Swift Programming - Control Flow and Optionals

87. Setting up the Egg Timer Project and Linking the Storyboard and ViewController

To solve label text overflow problem: change “Lines” to 0 (unlimited lines), make “Autoshrink - Minimum Font Size” smaller (so font automatically downsizes when overflow).

90. Conditional Statements Challenge Solution

if else:

if hardness == 1 {
  statement1
} else if hardness == 2 {
  statement2
} else {
  statement3
}

switch case:

switch hardness {
  case 1:
    statement1
  case 2:
    statement2
  default:
    statement3
}

dictionary:

let eggTimes = ["Soft": 5, "Medium": 7, "Hard": 12]

98. Using the 5 Step Approach to Debug our App

  1. What do you expect your code to do?

  2. What happened instead?

  3. What does your expectation depend upon?

  4. How can we test the things our expectations depend on?

  5. Fix our code to make reality match expectations

Section 9: iOS App Design Patterns and Code Structuring

109. Implementing MVC and Understanding Parameter Names

MVC:

  • Model: Question.swift (data)

  • View: Main.storyboard

  • Controller: ViewController.swift

In model,

import Foundation

struct Question {
    let text: String
    let answer: String

    init(q: String, a: String) {
        text = q
        answer = a
    }
}

//To initiate a Question structure:
//let quiz = Question(q: "A slug's blood is green.", a: "True")

Functions with external and internal parameters:

func someFunction(parameter: Int){
  statement
}

func someFunction(par parameter: Int){
  statement
}

func someFunction(_ parameter: Int){
  statement
}

someFunction(parameter: myParameter)
someFunction(par: myParameter)
someFunction(myParameter)

Functions with an output:

func getMilk(money: Int) -> Int{
  let change = money - 2
  return change
}

Inside the struct, if a property is changed, the func should be mutating

mutating func checkAnswer(userAnswer: String) -> Bool {
    if userAnswer == quiz[questionNumber].answer {
        score += 1
        return true
    } else {
        return false
    }
}

Section 11: Advanced Swift Programming - Classes, Inheritance & Advanced Optionals

132. Structs vs Classes

struct MyStruct {}

  • immutable

  • passed by value

class MyClass: SuperClass {}

  • passed by reference

  • inheritance

134. How to Create a UI Programatically and Pass Data between ViewControllers

Create a UI:

let label = UILabel()
label.text = "..."
label.frame = CGRect(...)
view.addSubview(label)

Present and pass data between view controller:

let secondVC = SecondViewController()
secondVC.bmiValue = ...
self.present(secondVC, animated: true, completion: nil)

135. Segues and Navigation for Multi-Screen Apps

Create new view controller: File - New file - Cocoa Touch Class (contains UIKit etc.)

Link swift file to view controller: choose the screen relate to new view controller - right “identity” - Class - your new view controller.

Create a segue: hold “control”, drag from first ViewController to the second one.

performSegue(withIdentifier: "goToResult", sender: self)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "goToResult" {
        let destinationVC = segue.destination as! ResultViewController
        destinationVC.bmiValue = calculatorBrain.getBMIValue()
        destinationVC.advice = calculatorBrain.getAdvice()
        destinationVC.color = calculatorBrain.getColor()
    }
}

Go back to previous view: dismiss()

137. [Swift Deep Dive] Optional Binding, Chaining, and the Nil Coalescing Operator

String and String? (optional) are totally different data types.

  1. Force Unwrap: optional!. Must make sure the optional will never be nil when it’s used.

  2. Check for nil value: if optional == nil {} else {}

  3. Optional Binding: if let safeOptional = myOptional { use safeOptional }

  4. Nil Coalescing Operator: optional ?? defaultValue

  5. Optional Chaining: optional?.property, optional?.method()

Section 13: Networking, JSON Parsing, APIs and Core Location

154. Dark Mode and Working with Vector Assets

SF symbols: free, by Apple.

If choose colors provided by system, it will adapt to dark mode automatically.

Custom color that adapt to dark mode: Assets.xcassets - add new color sets - set colors - give it a name and use it.

PDF can be used as image. Image set - drag the pdf to 1x - Resizing: preserve vector data - Scales: single scale

155. Learn to use the UITextField

//UITextFieldDelegate is a protocol, not a class
extension WeatherViewController: UITextFieldDelegate {

    //if search button is pressed
    @IBAction func searchPressed(_ sender: UIButton) {
        searchTextField.endEditing(true)
    }

    //if keyboard enter is pressed
    //somewhere: @IBOutlet weak var searchTextField: UITextField!
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        searchTextField.endEditing(true)
        return true
    }

    //if any of the text field end editing
    func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
        if textField.text != "" {
            return true
        } else {
            textField.placeholder = "Type something"
            return false
        }
    }

    func textFieldDidEndEditing(_ textField: UITextField) {

        if let city = searchTextField.text {
            weatherManager.fetchWeather(cityName: city)
        }

        searchTextField.text = ""

    }
}

158. An Example of Protocols and Delegates in Practice

Use Delegate Design Pattern to maximize code re-usage.

//file: SignalProcessor.swift
//can be used by any view controller
protocol SignalProcessorDelegate {
    //the VC uses this file must implements these functions:
    func output()
}

class SignalProcessor {
    //who takes over afterward:
    var delegate: SignalProcessorDelegate?
    var outputString: String?

    //this is where VC functions are called:
    func doesSomething(){  
        print("Signal received.")
        outputString = "Some results"
        delegate?.output()
    }
}

//file: ViewController.swift
class ViewController {
    var signalProcessor = SignalProcessor()

    override func viewDidLoad(){
        //access to the processor
        signalProcessor.delegate = self
    }

    //other regular things
}

//implement required functions related to SignalProcessor
extension ViewController: SignalProcessorDelegate {
    func output(){
        //utilize properties of SignalProcessor
        print(signalProcessor.outputString)
    }
}

160. Use the URLSession for Networking

func didUpdateWeather(_ weatherManager: WeatherManager, weather: WeatherModel) {
    //while weather data is retrieving in background,
    //update the user interface in foreground so that app won't freeze
    DispatchQueue.main.async {
        self.temperatureLabel.text = weather.temperatureString
        self.conditionImageView.image = UIImage(systemName: weather.conditionName)
        self.cityLabel.text = weather.cityName
    }
}
func performRequest(with urlString: String) {
    if let url = URL(string: urlString) {
        let session = URLSession(configuration: .default)
        //task starts but immediately suspends...
        let task = session.dataTask(with: url) { (data, response, error) in
            if error != nil {
                self.delegate?.didFailWithError(error: error!)
                return
            }
            if let safeData = data {
                if let weather = self.parseJSON(safeData) {
                    self.delegate?.didUpdateWeather(self, weather: weather)
                }
            }
        }
        //real start of task
        task.resume()
    }
}

161. [Swift Deep Dive] Closures

let array = [1, 2, 3, 4, 5]

func addOne(n1: Int) -> Int {
  return n1 + 1
}

array.map(addOne)  //function as parameter

array.map((n1: Int) -> Int {  //anonymous function
  return n1 + 1
})

//use closure to be one-line function
array.map({(n1: Int) -> Int in return n1 + 1 })  

array.map({(n1) in n1 + 1 })  //type Inference

array.map({$0 + 1})  //$0 para_1, $1 para_2...

array.map(){$0 + 1}
array.map{$0 + 1}  //if function is last para, trailing

162. JSON Decoding

//file: WeatherData.Swift
import Foundation

struct WeatherData: Codable {
    let name: String
    let main: Main
    let weather: [Weather]
}

struct Main: Codable {
    let temp: Double
}

struct Weather: Codable {
    let description: String
    let id: Int
}

//decode JSON
func parseJSON(_ weatherData: Data) -> WeatherModel? {
    let decoder = JSONDecoder()
    do {
        let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
        let id = decodedData.weather[0].id
        let temp = decodedData.main.temp
        let name = decodedData.name

        let weather = WeatherModel(conditionId: id, cityName: name, temperature: temp)
        return weather

    } catch {
        delegate?.didFailWithError(error: error)
        return nil
    }
}

163. Create a WeatherModel and Understand Computed Properties

Computed properties:

struct WeatherModel {
    let temperature: Double
    //computed property based on other properties
    var temperatureString: String {
        return String(format: "%.1f", temperature)
    }

164. Typealiases and a Protocols and Delegate Challenge

Typealiases

struct WeatherData: Decodable, Encodable {
  ...
}

//also
struct WeatherData: Codable {
  ...
}

169. Using Extensions to Refactor the ViewController

Auto completion of frequently used code: Select the code - right click - Create code snippet - Completion: YourChoice. Use <#something#> for placeholder.

170. Using CoreLocation to get Location Data

import UIKit
import CoreLocation

class WeatherViewController: UIViewController {

    var weatherManager = WeatherManager()
    let locationManager = CLLocationManager()

    override func viewDidLoad() {
        super.viewDidLoad()

        locationManager.delegate = self
        //modify plist
        locationManager.requestWhenInUseAuthorization()
        locationManager.requestLocation()

        weatherManager.delegate = self
    }

}


extension WeatherViewController: CLLocationManagerDelegate {

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let location = locations.last {
            locationManager.stopUpdatingLocation()
            let lat = location.coordinate.latitude
            let lon = location.coordinate.longitude
            weatherManager.fetchWeather(latitude: lat, longitude: lon)
        }
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error)
    }
}

Section 15: Firebase Cloud Firestore, TableViews and Cocoapod Dependencies

187. Navigation Controller Stacks and Segues

Add navigation bar on top of screen: Select root ViewController - top menu Editor - Embed in - Navigation Controller

188. Typing Animations, Timers and For Loops

Letters in a String can be accessed by in:

let astring = "sentence"
for letter in astring {
  print(letter)
}

192. Cocoapods Installation Instructions

sudo gem install cocoapods
pod setup --verbose
pod --version

193. How to Install a Pod to your Project

cd YOUR_PROJECT_DIR
pod init

Open the newly created file named Podfile with Xcode. Edit it according to library installation manual.

pod install

To remove a Pod, delete the line in Podfile, pod install again.

200. Logging Out Users

Drag Bar Button Items to the navigation bar.

To get straight back to root ViewController: navigationController?.popToRootViewController(animated: true)

To hide ‘Back’ button: navigationItem.hidesBackButton = true

201. Using a Constants File and Understanding the static Keyword

Minimize String typing by using a Constants file:

struct K {    //or class
    let instanceProperty = "Must create an instance before use"
    static let typeProperty = "Can be used as 'K.typeProperty' straight away"

    struct BrandColors {
        static let purple = "BrandPurple"
    }

    struct FStore {
        static let collectionName = "messages"
    }
}

202. How to use a UITableView and Create a Message Model

To use a Table View (in which items arranged as a list), drag one Table View and ONE Table View Cell to the screen. Adjust the size of Table View.


class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
    }
}

extension ChatViewController: UITableViewDataSource {

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
      let message = messages[indexPath.row]

      let cell = tableView.dequeueReusableCell(withIdentifier: K.cellIdentifier, for: indexPath) as! MessageCell
      cell.label.text = message.body

      //This is a message from the current user.
      if message.sender == Auth.auth().currentUser?.email {
        cell.leftImageView.isHidden = true
        cell.rightImageView.isHidden = false
        cell.messageBubble.backgroundColor = UIColor(named: K.BrandColors.lightPurple)
        cell.label.textColor = UIColor(named: K.BrandColors.purple)
      }
      //This is a message from another sender.
      else {
        cell.leftImageView.isHidden = false
        cell.rightImageView.isHidden = true
        cell.messageBubble.backgroundColor = UIColor(named: K.BrandColors.purple)
        cell.label.textColor = UIColor(named: K.BrandColors.lightPurple)
      }

      return cell
    }
}

203. Customising Cells in a TableView using a .xib File

Create a Cocoa Touch Class file - Subclass of UITableViewCell, also create xib file - Design the cell in xib file.

Make sure there is no prototype cell left in the table view. Match cell identifier and file name. Register table view with the designed cell:

class ChatViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(UINib(nibName: K.cellNibName, bundle: nil), forCellReuseIdentifier: K.cellIdentifier)


    }

In the cellNibName.swift file:

import UIKit

class MessageCell: UITableViewCell {

    @IBOutlet weak var messageBubble: UIView!
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var rightImageView: UIImageView!
    @IBOutlet weak var leftImageView: UIImageView!

    override func awakeFromNib() {
        super.awakeFromNib()
        messageBubble.layer.cornerRadius = messageBubble.frame.size.height / 5
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}

Set cell label lines to 0, so that label adapts to unlimited number of lines.

204. [Swift Deep Dive] Casting as? as! as is and understanding Any

is

if var1 is SomeType {
  print("var1 is SomeType")
}

as!: Forced downcast

let var1 = varOfClassA as! SubclassOfClassA

as?: Downcast to Optionals

if let var1 = varOfClassA as? SubclassOfClassA {
    ...
}

as: Upcast

let var1 = varOfSubclass as! SuperClass

Any: All objects, which includes

AnyObject: Objects derived from classes, which includes

NSObject: Foundation objects

205. Database setup and Saving Data to Firestore

@IBAction func sendPressed(_ sender: UIButton) {

    if let messageBody = messageTextfield.text, let messageSender = Auth.auth().currentUser?.email {
        db.collection(K.FStore.collectionName).addDocument(data: [
            K.FStore.senderField: messageSender,
            K.FStore.bodyField: messageBody,
            K.FStore.dateField: Date().timeIntervalSince1970
        ]) { (error) in
            if let e = error {
                print("There was an issue saving data to firestore, \(e)")
            } else {
                print("Successfully saved data.")

                DispatchQueue.main.async {
                     self.messageTextfield.text = ""
                }
            }
        }
    }
}

207. Listening for Updates on Firestore

func loadMessages() {

    db.collection(K.FStore.collectionName)
        .order(by: K.FStore.dateField)
        .addSnapshotListener { (querySnapshot, error) in

        self.messages = []

        if let e = error {
            print("There was an issue retrieving data from Firestore. \(e)")
        } else {
            if let snapshotDocuments = querySnapshot?.documents {
                for doc in snapshotDocuments {
                    let data = doc.data()
                    if let messageSender = data[K.FStore.senderField] as? String, let messageBody = data[K.FStore.bodyField] as? String {
                        let newMessage = Message(sender: messageSender, body: messageBody)
                        self.messages.append(newMessage)

                        DispatchQueue.main.async {
                            self.tableView.reloadData()
                            let indexPath = IndexPath(row: self.messages.count - 1, section: 0)
                            self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
                        }
                    }
                }
            }
        }
    }
}

211. The ViewController Lifecycle Explained

viewDidLoad(): only called once, when view is created

viewWillAppear()

viewDidAppear()

viewWillDisappear()

viewDidDisappear(): view is not deleted, user just cannot see it

Section 17: SwiftUI and Declarative Programming

221. How to Build a SwiftUI App From Scratch

Set background and overlays:

ZStack{
  Color(Color.black)   //or
  Image("background")
      .resizable()
      .edgesIgnoringSafeArea(.all)
  Text("Some Text")
}

Vertical stack:

VStack {
    Image("diceeLogo")
    Spacer()   //push other components to the edge
    HStack {
        DiceView(n: leftDiceNumber)
        DiceView(n: rightDiceNumber)
    }
    .padding(.horizontal)   //leave room between it and outer container

Extract reusable codes:

struct DiceView: View {

    let n: Int

    var body: some View {
        Image("dice\(n)")
            .resizable()
            .aspectRatio(1, contentMode: .fit)
            .padding()
    }
}

Managing state. When the variable changes, the related components in struct ContentView will update automatically.

@State var leftDiceNumber = 1
@State var rightDiceNumber = 1

Button functions:

Button(action: {
    self.leftDiceNumber = Int.random(in: 1...6)
    self.rightDiceNumber = Int.random(in: 1...6)
}) {
    Text("Roll")
        .font(.system(size: 50))
        .fontWeight(.heavy)
        .foregroundColor(.white)
        .padding(.horizontal)
}
.background(Color.red)

Section 18: Git, GitHub and Version Control

239. Version Control Using Git and the Command Line

#Initiate Git
git init

#Checking file changes
git status

#Add files to staging area (to track changes)
git add fileA
git add .

#Remove files from staging area
git rm --cached -r .

#Commit the change (use present tense)
git commit -m "Message"

#See past commits
git log

#Compare current file to last commit
git diff fileA

#Roll back to last commit
git checkout fileA

#Add remote repository ('origin' as a name convention)
git add remote origin https://somesite/projectA.git

#Sync local repo to remote repo
git push -u origin master

#Create new branch
git branch new-branch

#See which branch we are currently on
git branch

#Change to other branch
git checkout new-branch

#Merge branches
git checkout master
git merge new-branch

242. Gitignore

Common .gitignore: github/gitignore

Section 19: Local Data Persistance - User Defaults, Core Data and Realm

255. Persistent Local Data Storage Using UserDefaults

Save data of standard object (Strings, Arrays etc.) into plist:

import UIKit

let defaults = UserDefaults.standard

defaults.set(0.24, forKey: "Volume")
defaults.set(true, forKey: "MusicOn")
defaults.set("Jane", forKey: "UserName")
defaults.set(Date(), forKey: "AppLastOpenedByUser")
let array = [1,2,3]
defaults.set(array, forKey: "myArray")
let dictionary = ["name": "John"]
defaults.set(dictionary, forKey: "MyDictionary")

let volume = defaults.float(forKey: "Volume")   // 0.24
let appLastOpen = defaults.object(forKey: "AppLastOpenedByUser")
let myArray = defaults.array(forKey: "MyArray") as! [Int]
let myDict = defaults.dictionary(forKey: "myDictionary")

257. [Advanced Swift] The Swift Singleton Object

Singleton Pattern is a pattern that ensures that there is only ever one single instance of a class, such as UserDefaults.standard.

260. [Advanced Swift] The Swift Ternary Operator

var1 = condition ? valueIfTrue : valueIfFalse

263. Encoding Data with NSCoder

class Item: Codable {
  var title: String = ""
  var done: Bool = false
}

var itemArray = [Item]()

let dataFilePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("Items.plist")

let encoder = PropertyListEncoder()
do{
  let data = try encoder.encode(self.itemArray)
  try data.write(to: dataFilePath!)
} catch {
  print(error)
}

264. Decoding Data with NSCoder

func loadItems() {
  if let data = try? Data(contentsOf: dataFilePath!) {
    let decoder = PropertyListDecoder()
    do {
      try itemArray = decoder.decode([Item].self, from: data)
    } catch {
      print(error)
    }
  }

}

265. Introduction to Databases

Methods and Uses:

  • UserDefaults: Quickly persist small bits of data

  • Codable: Flash freeze custom objects

  • Keychain: Save small bits of data securely

  • SQLite: Persist large amount of data and query it

  • CoreData: Object-oriented database

  • Realm: a faster and easier database solution

266. How to Set up and Configure Core Data

When create a project, check “use coreData”. Or, add Core Data data model later to existing project.

273. How to Implement a UISearchBar and Querying with Core Data

Add a search bar. “Option + Drag” the search bar to view controller, add delegate.

Section 20: In-App Purchases and Apple StoreKit

300. Setup Your In-App Purchase on App Store Connect

Logon to Apple Developer website.

Add a new App ID.

Go to App Store Connect, check out informs.

Fill Agreements, Tax and Banking.

My Apps - In-App Purchases

Section 33: Bonus: The Complete App Design Course

441. Understanding the Mood of Your Colour Palette

Red: Love, energy, intensity

Yellow: Joy, intellect, attention

Green: Freshness, safety, growth

Blue: Stability, trust, serenity

Purple: Loyalty, wealth, femininity

442. How to Combine Colours to Create Colour Palettes

Use nearby colors in the color wheel: analogous colors. Harmonious, can be stared at for a long time.

Use opposite colors in the color wheel: complementary colors. Attention grabing, but hard to last.

Use nearby colors beside the complementary colors: split colors. Stands out without being too contrast.

Use equally spaced colors in the color wheel: triadic colors. Balanced, old fashioned.

Use single color in the color wheel, but use lightness/darkness to emphasize the elements: monochromatic.

Useful resources:

  • Curated color palettes: http://www.colorhunt.co

  • Good colors for iOS: https://flatuicolors.com

  • Good colors for Android: https://www.materialpalette.com

  • Color picker in web browser: https://www.colorzilla.com

449. How to Combine Fonts Like a Pro

Consider readability.

Match with similar moods and time-eras. Contrast serif/sans-serif and weights.

453. The Importance of Alignment

Use as few alignment guide lines as possible.

454. What is Good Practice in Interaction Design

Keep real world in mind to help user understand how to use your app.

456. The Many Ways of Designing Text Overlays

Use opaque overlay to make texts standing out.

457. How to Be an Attention Architect

Use contrast

459. What is User Experience (UX) Design?

Keep the design flexible to suit real user experience.

460. Usability

Keep core function as simple to use as possible.

461. Asking for Permissions

Ask for small thing first, so that the user may agree on bigger thing later.

462. User Profiling

Give the user full personality and tailor your app to her/him.

463. Form vs. Function

Function should always come before form.

464. Consistency

Consistency in look and function.

465. Simplicity

Make it simple, add things later if necessary.

466. Don’t Make Me Think

Don’t front-load lots of info, give a hint just when it’s needed. Don’t make users confused.

  1. Onboarding 3min

  2. Idiot Boxes 4min

  3. Further Reading on User Experience Design 1min

  4. Android vs. iOS Design 3min

  5. Navigation 3min

  6. The Devil is in the Details 1min

  7. Differences in Icon Design 1min

  8. Flat Design vs. Material Design 3min

  9. Differences in Establishing Visual Hierarchy 1min

  10. iOS and Android Design Guidelines 3min Resources

  11. Step1 - Design Patterns and Colour Palettes 3min

  12. Where to Find Design Patterns and Colour Palettes 1min

  13. Step 2 - How to Create a User Flow Diagram 8min

  14. Step 3 - How to Create Wireframes 11min

  15. Wireframing Resources 1min

  16. Step 4 - How to Create Professional Mockups 5min

  17. Tools for Creating Mockups 1min

  18. How to Use Sketch to Create Mockups 15min Resources

  19. [Optional] Watch me Create a Mock up Using Sketch 46min

  20. How to Use Canva to Create Mockups 4min Resources

  21. Your Turn to Create Your Own Mockups 2min

  22. Tools and Resources for Creating Mockups 1min

  23. Step 5 - How to Create an Animated App Prototype 4min

  24. Tools and Resources for Creating Prototypes 1min

  25. Prototyping with Keynote 8min

  26. Prototyping with Marvel 5min Resources

  27. Your Turn to Create a Prototype 1min Resources

  28. Where to Find Free-For-Commercial-Use Image As… 1min

  29. Where to Find Free-For-Commercial-Use Icons 1min

  30. How to Keep Designing and Improving 1min Resources

  31. Tip from Angela - Step Up to Challenges