Active Filters: Techniques

Topics

#100: Just Getting Started ๐Ÿš€

Topics

At one point or another, we've all heard some form of "Oh you make apps? I'm interested in that, but I'm having trouble just getting started". Today we'll take a look at the specific steps to create our first app and where to go from there. Let's dive in:

We'll need a Mac. We'll open the Mac App Store and search for "Xcode", then install it. (It's free).

Once it's installed, we'll open Xcode. We'll allow it to verify it's installation, and install any components it needs to.

Then we'll go to the File menu and choose File > New > Projectโ€ฆ We'll leave the Master-Detail iOS Application selected and click Next. We'll give our new app a name: "Foodstagram". We'll enter "com.somename" as the organization identifier, select Swift as the language, and click Next. Finally, we'll choose where to save our new project, then click Create.

When our project opens, we'll hit the little โ–ถ button in the top left corner. Xcode will build our new app and launch it in the Simulator.

Hey it's an app! Hit the + button to add some entries, then edit to delete them.

Congratulations, our new app-venture (sorry, had to) has begun! Next, we need to learn how to learn. When first starting out, much of our time will be spent Googling for things we don't know yet. This is completely normal. Let's start right now.

We'll look at going from idea to implementation. Our idea: "The top bar of the app should be green."

We'll Google for how to do it, using as specific a phrase as we can given what we know so far:

"ios swift change top bar background color".

We'll read the first result from stackoverflow. The first answer contains some possibly helpful code snippets, but we'll read a few more to be sure. It seems the rest of the answers all suggest a similar piece of code talking about "appearance". Sounds promising. One even mentions "color" and "green", so we'll copy it for pasting later:

UINavigationBar.appearance().barTintColor = UIColor.greenColor()

Where do we even put this code? Back to Google.

We'll keep reading our search results until we find a coderwall post from Eranga Bandara that answers our question: "Add following code to didFinishLaunchingWithOptions function in AppDelegate.swift".

Perfect, we'll do just that and click the AppDelegate file in Xcode, then paste our copied code at the end of the specified function.

We can check if it worked by clicking the โ–ถ button again. Success! Our top bar is now green!

We can't learn everything this way, so our next step is to try to find something to teach us the rest of the basics. There's plenty of great resources out there, including one that's just getting started. ๐Ÿ˜‰๐Ÿซ

Grand Central Dispatch is the name of Apple's collection of task concurrency functions in libdispatch. GCD (as it's often called) is packed full of interesting features involving concurrency, queuing, timers, and more. Let's dive in and take a look at some simple and common use cases:

Common Usage

One of the most common ways to use GCD is to hop to a background queue to do some work, then hop back to the main queue to update the UI:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
  let resizedAvatarImage = self.resizeImage(avatarImage)

  dispatch_async(dispatch_get_main_queue()) {
    self.avatarImageView.image = resizedAvatarImage
  }
}

Dispatch Once

If we wanted to run some code in viewDidAppear: but only wanted it to run the first time, we could store some flag in a Bool property. Or just use GCD:

struct Tokens {
  static var onceToken: dispatch_once_t = 0;
}

dispatch_once(&Tokens.onceToken) {
  self.initSomeState()
}

Dispatch After

We can also easily wait a specified amount of time, then run some code. Here we'll use this technique to pop back to the previous view controller a quick "beat" after the user selects an an option in a table view controller. (As seen in many apps, including Settings.app):

let delay = 0.5
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
  self.navigationController?.popViewControllerAnimated(true)
}

Using a UISegmentedControl to switch view controllers is very common, even Apple does it:

Let's build this using UIPageViewController and UISegmentedControl.

We start with a blank storyboard, drag out a UIPageViewController and set it as the initial view controller. Then drag out two UIViewControllers to switch between. We'll select the page view controller and choose Editor > Embed In > Navigation Controller.

Then we drag out a UISegmentedControl and drop it into the title area of the navigation bar of our page view controller. Finally, we create an @IBAction function that will get called when the selected segment changes.

The code is pretty straightforward, note the use of R.swift to grab those two view controllers from our storyboard. (covered in Bite #52).

func setViewControllerForIndex(index: Int) {
  setViewControllers(
    [index == 0 ? spaceshipsVC! : crewVC!], 
    direction: .Forward, 
    animated: false, 
    completion: nil
  )
}

override func viewDidLoad() {
  super.viewDidLoad()

  spaceshipsVC = R.storyboard.main.spaceshipsVC
  crewVC = R.storyboard.main.crewVC

  setViewControllerForIndex(0)
}

@IBAction func segmentedControlChanged(sender: UISegmentedControl) {
  setVCForIndex(sender.selectedSegmentIndex)
}

extension ViewController : UIPageViewControllerDataSource {
  func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
    if viewController == crewVC { return spaceshipsVC }

    return nil
  }

  func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
    if viewController == spaceshipsVC { return crewVC }

    return nil
  }
}

Download the complete project here: j.mp/bite055

Let's say you have a table view with some cells showing a list of crew members. It'd be great if when the user taps a cell, it would โ€œexpandโ€ to reveal some actions that can be performed on that crew member. Let's dive in.

Setup View Hierarchy

As you can see, we lean on UIStackView pretty heavily. The constraint shown here is the one we'll be animating.

Animate the Constraint

In our cell's setSelected(animated:) function, we animate the constraint just like we covered back in Bite #9. After the animation's done, we hide/show the toolbar. This triggers the top-level stack view to recalculate it's height.

let constant: CGFloat = selected ? 30.0 : 0.0
let options = [.AllowUserInteraction, .BeginFromCurrentState]

UIView.animateWithDuration(0.3, delay: 0.0, options: options, animations: {
  self.toolbarStackViewHeightConstraint.constant = constant
  self.layoutIfNeeded()
}, completion: { completed in
  self.toolbarStackView.hidden = !selected
})

Final Product

Our view controller takes care of resizing our cells by updating the 'expanded' state of each crew member when the selection changes and a pair of calls to tableView.beginUpdates/endUpdates inside the tableView:didSelectRowAtIndexPath: and tableView:didDeselectRowAtIndexPath:.

Download the complete working project here: j.mp/bite050

Patterns are one of the most powerful parts of Swift. Let's look at a few examples of cool things you can do with pattern matching.

Ranges

let since: NSTimeInterval = 127 // 2 minutes, 7 seconds

switch since {
    case (0...10): print("just now")
    case (10...60): print("a minute ago")
    default: print("\(since) seconds")
}

Enums

enum Result {
  case Success
  case Error(String)
}

let r = Result.Success

switch r {
  case .Success: print("Yay!")
  case .Error(let err): print("Boo: \(err)")
}

Types

let something = Product()

switch something {
    case is Product: print("Found a product")
    case let person as Person: print("Found a person: \(person.name)")
    default: print("Found an unknown thing.")
}

Where

let contentOffset = (0, 30.0)

switch contentOffset {
  case let (_, y) where y < 0: print("Scrolled up")
  case let (_, y) where (0...60) ~= y: print("scrolling top area")
  default: println("just scrolling")
}

Swift 2

New in Swift 2, you can now use any Swift pattern as the clause of an if statement:

let comments = 7

if case 1...10 = commentCount {
    print("some comments")
} else if case 11..100 = commentCount {
    print("lots of comments")
}

And in Swift 2's new error handling:

do {
    try send(message: "Hello")
} catch SendError.Offline {
    print("user is offline")
} catch SendError.RateLimited {
    print("stop spamming")
} catch SendError.Blocked(let reason) {
    print("user was blocked because \(reason)")
}

Topics

#19: Protocol Extensions ๐Ÿ”ญ

Topics

Protocol Extensions are a new feature of Swift 2. They allow you to add new functions (complete with implementations) to any class that implements a protocol. The functionality is best explained with a simple example. Letโ€™s say you wanted to add a way to shuffle arrays.

In Swift 1.2 and earlier, youโ€™d probably add a shuffle function using something like this:

extension Array {
  mutating func shuffle() {
    for i in 0..<(count - 1) {
      let j = Int(arc4random_uniform(UInt32(count - i))) + i
      swap(&self[i], &self[j])
    }
  }
}

Which works great, but only applies to Array types. In Swift 2, you can add it to all mutable collections using Protocol Extensions.

extension MutableCollectionType where Self.Index == Int {
  mutating func shuffleInPlace() {
    let c = self.count
    for i in 0..<(c - 1) {
      let j = Int(arc4random_uniform(UInt32(c - i))) + i
      swap(&self[i], &self[j])
    }
  }
}

As you can see here, we're also able to use Swiftโ€™s fantastic pattern-matching support to limit our extension to only mutable collections that use an Int as their index.

Topics

#13: PromiseKit ๐Ÿ’

Topics

PromiseKit is a library that adds Promises support to Objective-C and Swift. Promises (sometimes referred to as "Futures") can help clean up some of the spaghetti-callback-ridden code that we've all written then never talked about again.

The main thing you should take away from this bite is that PromiseKit lets you turn repulsive, close to gibberesh code like this:

SynapseAPI.loadVideos { (videos, error) in
  if let e = error { showError(e) } else {
    var completed = 0
    var total = videos.length

    for video in videos {
      PiedPiperAPI.loadVideo { loadedVideo in
        completed++
        if completed == total {
          // finally done, UGH SRSLY
        }
      }
    }
  }
}

...into much more readable, and composable code like this:

firstly {
  SynapseAPI.loadVideos()
}.then { videos in
  // `join` waits for all the requests to finish, then continues
  join(videos.map { PiedPiperAPI.loadVideo($0) })
}.catch { error in
  // called if any error happens anywhere in the chain
}.finally {
  // done, much nicer
}

The power of Promises doesn't stop at network calls! PromiseKit comes with extensions Promise-ifying many of the traditionally disperate parts of Cocoa and Cocoa Touch like CLLocationManager, UIAlertView, and even sending a user off to another UIViewController, letting them choose something, then continuing on.

More info about PromiseKit can be found at git.io/promisekit

Topics

#4: Singletons ๐Ÿšน

Topics

Singletons are a design pattern describing globally accessible instances of objects.

They should only be used when we need to read and/or write some state globally and possibly from other threads.

class VehicleManager {
  static let sharedManager = VehicleManager()

  var cars: [Car] = []

  init() {
    // init some (likely) global state
  }
}

With a singleton, there's a function that can be called from anywhere. In this case it's VehicleManager.sharedManager.

A great side effect is that the initializer for VehicleManager() isn't called until we access .sharedManager the first time.

Singletons TLDR;

  • ๐Ÿ‘ Globally accessible
  • ๐Ÿ‘ Thread-safe
  • ๐Ÿ‘ Lazily initialized
  • ๐Ÿ˜ญ Technique shown here requires Swift 1.2 or later

Topics

#2: Chainable Methods ๐Ÿ”—

Topics

Making functions chainable is quite easy and can allow us to write using an almost DSL-like syntax.

We'll add a new function that does something and then return self. It's that simple.

enum CountdownType: Int { case ToTheSecond, ToTheDay }
enum ColorScheme: Int { case AfterMidnight, ClassyYellow, Tealfish }

class Concern {
  var title: String = ""
  func title(aTitle: String?) -> Concern {
    title = aTitle ?? ""; return self
  }

  var subtitle = ""
  func subtitle(aSubtitle: String?) -> Concern {
    subtitle = aSubtitle ?? ""; return self
  }

  var countdownType: CountdownType = .ToTheSecond
  func countdownType(type: CountdownType) -> Concern {
    countdownType = type; return self
  }

  var colorScheme: ColorScheme = .AfterMidnight
  func colorScheme(scheme: ColorScheme) -> Concern {
    colorScheme = scheme; return self
  }
}

It enables us to write extremely readable and composable code like this:

Concern()
  .title("Big Meeting")
  .subtitle("With those people from that place")
  .countdownType(.ToTheDay)
  .colorScheme(.Tealfish)

Setting properties is just the tip of the iceberg here. Imagine using this technique to create chainable queries:

Event
  .withCategory(.Meeting)
  .withAttendees([User.me])
  .sort { $0.startDate < $1.startDate }

Chainables TLDR;

  • ๐Ÿ‘†write setters
  • ๐Ÿ–– return self
  • ๐ŸŽ‰ profit
Page 4 of 4