Active Filters: Libraries

Topics

#195: Mastering UIColor with Hue ๐ŸŒˆ

Topics

Today we'll check out a library from Hyper called Hue. It gives us all kinds of helpful utilities for working with colors, let's dive in.

First, Hue helps us easily can parse colors from hex strings:

let beige = UIColor.hex("#FEFAF1")
let lightBrown = UIColor.hex("#AB9372")
let brown = UIColor.hex("#8C5637")

In addition, Hue can analyze colors:

beige.isDark // returns false

Hue can easily create gradients. Here we'll take a couple of colors, and create a CAGradientLayer from them:

let gradient = [lightBrown, brown].gradient { gradient in
  gradient.locations = [0.25, 1.0]
  return gradient
}

Finally, Hue can analyze a UIImage, returning a tuple of colors it finds inside.

let (bg, primary, secondary, _) = albumArt.colors()

It provides background, primary, secondary and detail colors from the image. We can the use these to create interfaces such as the one found in iTunes on OS X, where the colors in the UI match those found in the album artwork. Neat!

More info about Hue can be found at git.io/hue

Today we'll check out a library called Tactile. It's a "Swiftier" way to respond to gesture recognizers and control events. Let's get started.

Tactile gives us a safer, more idiomatic way to install/react to gesture recognizers (Check Bite #38 for gesture basics).

In it's simplest form, Tactile works like this:

let tapGR = UITapGestureRecognizer()
tapGR.numberOfTapsRequired = 2

view.on(tapGR) { (tapGR: UITapGestureRecognizer) in
  // ...
}

We can also react to specific state changes:

let panGR = UIPanGestureRecognizer()
view.on(panGR, [.Began, .Ended], beganOrEnded)

func beganOrEnded(panGR: UIPanGestureRecognizer) {
  // ...
}

Tactile also adds similar functions to UIControl:

button.on(.TouchUpInside, tapped)
button.on([
  .TouchUpInside: tapped,
  .TouchUpOutside: cancelledTap
])

We can attach a single recognizer to multiple views:

viewA.on(tapGR, { tapGR in /* ... */ })
viewB.on(tapGR, { tapGR in /* ... */ })

Last but not least, we can remove all recognizers with

view.off()

just one with:

view.off(tap)

or all of a type with:

view.off(UITapGestureRecognizer.self)

More info about Tactile can be found at git.io/tactile

Today we'll follow up with another Bite in our localization series (sort of). Units of measurement are important to get right when localizing our apps for different cultures.

Today we'll look at a library from Khoa Pham called Scale that can help us convert between different units of measurement (for either localization or just-for-fun purposes). Let's begin.

Scale makes it incredible simple to work with different units of measurement:

let length = 5.kilometer + 7.meter  // 5,007 meters
let weight = 10.0.kilogram * 5.gram // 50,000 grams

Everything is strongly-typed, and conversion is a breeze:

2.week.to(unit: .hour) // 336 hours

Scale even provides nicely formatted debug output. For example, print'ing that last statement logs:

Time(value: 336.0, unit: Scale.TimeUnit.hour)

Scale supports a wide variety of units:

let angle = 5.degree + 2.radian
let area = 5.acre + 2.hectare
let metric = 5.base + 2.kilo
let volume = 5.liter + 2.gallon
// + many more...

One more example to show off how readable Scale's API is. Here we'll add two typed units together, then convert them:

let time = 4.day + 1.week
print(time.to(unit: .week))
Time(value: 1.57142857142857, unit: Scale.TimeUnit.week)

More info about Scale can be found at git.io/scale

In iOS 9, the Contacts framework replaced AddressBook.framework as the suggested way to interact with that data. Today we'll check out a library called EPContactsPicker, which is built on top of the Contacts framework and encapsulates a lot of the boilerplate logic. Let's dive in.

let contactPickerVC = EPContactsPicker(
  delegate: self,
  multiSelection: true,
  subtitleCellType: SubtitleCellValue.Email
)

let nc = UINavigationController(rootViewController: contactPickerVC)
presentViewController(nc, animated: true, completion: nil)

Pretty simple! We can add a delegate function to grab the contact or contacts that were selected:

func epContactPicker(_: EPContactsPicker, didSelectMultipleContacts contacts: [EPContact]) {
  for contact in contacts {
    print("\(contact.displayName())")
  }
}

More info about EPContactsPicker can be found at git.io/contactspicker

Localization can go a long way towards increasing the sales of our apps. Users are browsing the App Store all over the world, and when they come across a neat looking app that's not translated into their language, they're very likely to keep on scrolling. Today weโ€™ll check out a library called Localize-Swift from Roy Marmelstein that makes this process much nicer.

The first helpful addition Localize-Swift brings us is a better way to localize our strings:

searchBar.placeholder = "Hello World".localized()

Then we can easily test our translations at runtime using:

Localize.availableLanguages()
Localize.setCurrentLanguage("fr")

We can respond to language changes using an NSNotification:

NSNotificationCenter.defaultCenter().addObserver(
  self, 
  selector: "setText",
  name: LCLLanguageChangeNotification, 
  object: nil
)

Localize-Swift also provides a custom genstrings.py script to easily fill our .strings files even though we're not using NSLocalizedString directly, neat!

More info about Localize-Swift can be found at git.io/localizeswift

When a user first installs one of our apps, it'd be nice if we could hold their hand a bit, pointing out where the important bits are.

Today we'll look at Gecco, a library that can make creating these walkthroughs quite easy. Let's dive in.

Gecco is built using UIKit's custom transition protocols.

This means there's simply a view controller that we'll configure and present over our content that will display the "spotlight" effect.

In it's most basic form, we can use Gecco by creating a SpotlightViewController, then configuring it's spotlight property to be one of the supplied Oval, Rect, or RoundRect enum cases:

let spotlightVC = SpotlightViewController()

spotlightVC.spotlight = .Oval(
  center: CGPointMake(100, 100),
  width: 100)

presentViewController(spotlightVC, animated: true, completion: nil)

Then we simply present it like normal, neat! In simple cases this is all we need, next we'll look at customizing things further.

We can actually subclass Gecco's SpotlightViewController and use it's spotlightView property to animate the spotlight effect calling out different shapes, even morphing from one to another:

class CustomSpotlightViewController : SpotlightViewController {
  var currentStep = 0
  func updateSpotlightView() {
    switch currentStep {
      case 0: spotlight = .Oval(center: CGPointMake(349, 42), width: 50)
      case 1: spotlightView.move(.Oval(center: CGPointMake(300, 42), width: 50))
      case 2: spotlightView.move(.RoundedRect(center: CGPointMake(375 / 2, 42), size: CGSizeMake(120, 40), radius: 6), moveType: .Disappear)
      case 3: dismissViewControllerAnimated(true, completion: nil)
      default: break
    }

    currentStep++
  }
}

Note about hard-coding values: We've used exact values here to make it easy to understand how Gecco works. In a real world scenario, you would probably be grabbing these values from your views after they've been through a layout pass.

More info about Gecco can be found at git.io/gecco

iOS has some great built-in support for different keyboards for different types of data, but the built-in numeric-keypad keyboard style does leave a few things to be desired (a "done" button, decimal point, a "hide" button, etc).

Today we'll look at a library called MMNumberKeyboard by Matรญas Martรญnez that does a great job of filling in these gaps. Let's try it out.

Setting up a text field to use the library's keyboard is extremely straightforward. First we'll create and configure the keyboard:

let numberKeyboard = MMNumberKeyboard(frame: CGRectZero)
numberKeyboard.allowsDecimalPoint = true
numberKeyboard.delegate = self

Then we'll set it as the inputView of our text field:

spaceshipPriceTextField = UITextField(frame: CGRectZero)
spaceshipPriceTextField.inputView = numberKeyboard

Now, when our text field becomes the first responder, the keyboard will be much more useful. Lastly, we can (optionally) add some delegate functions:

func numberKeyboard(numberKeyboard: MMNumberKeyboard!, shouldInsertText text: String!) -> Bool
func numberKeyboardShouldReturn(numberKeyboard: MMNumberKeyboard!) -> Bool

More info about MMNumberKeyboard can be found at git.io/numberkeyboard

Breakpoints in Xcode are a great way to pause execution and poke around at what's going on in our app. Today we'll look at some ways we can use them without pausing execution. Let's dive in.

We'll start by pressing (while editing) to set a breakpoint:

Then, we can right click to edit the breakpoint. The popover that appears may appear simple, but it's packing tons of power.

We'll enable that last checkbox straight away, then click Add Action to see our available options.

We can add a Debugger Command action to (for example) print a value whenever the breakpoint hits. We now have a dynamic log statement we can easily enable/disable.

The last option, "Sound", might not seem that useful at first glance. We can use this to (for example) debug high performance code, or whenever we'd like to hear an audible "ding" when a certain code path is hit.

After adding an action, we'll see a couple +/- buttons, this means we can actually pair both these techniques together, neat. Happy debugging!

Today we'll checkout another great library from Alexander Schuch called StatefulViewController.

It can help us add loading, empty, and error states to our view controllers. Let's get started:

Most apps we build must load data before showing it to the user. Those apps will also need to handle the case where there's no data available, or when something goes wrong.

StatefulViewController allows us to easily add all of this functionality to our view controllers.

While this might seem trivial, there's actually quite a bit of logic and state management involved. StatefulViewController helps eliminate boilerplate and help us focus on building the parts of our app that make it unique and useful.

Let's try it out. First, we'll make a view controller, and adopt the protocol:

class ViewController: UIViewController, StatefulViewController { }

Then we'll need to set up our placeholder views:

// inside viewDidLoad
loadingView = LoadingView(frame: view.frame)
emptyView = EmptyView(frame: view.frame)
errorView = ErrorView(frame: view.frame)

Next, we'll need to set up the initial state when the view controller's view is about to appear:

override func viewWillAppear(animated: Bool) {
  super.viewWillAppear(animated)
  setupInitialViewState()
}

Now we can visually start and stop loading when we need:

startLoading()
endLoading(error: nil)

Lastly, we'll need to implement one more function to let the library know when there's content available:

func hasContent() -> Bool {
  return spaceships.count > 0
}

More info about StatefulViewController can be found at git.io/stateful

Parallax headers have become very common in iOS apps. They're a great way to (for example) show off large graphical content, or summarize/focus a screenful of content.

Today we'll look at a fantastic library from Maxime Epain called MXParallaxHeader. Let's go:

A parallax header essentially involves displaying a view above a scroll view, which grows and shrinks as the user scrolls the scroll view.

It's also common to see the content of the parallax header view re-center itself as the user scrolls, so the content is visible for as long as possible while scrolling up.

MXParallaxHeader offers a few different options that make customizing this behavior quite simple.
We can center, fill, or lock our content to the top or bottom. Neat!

Let's use MXParallaxHeader to add a simple parallax header to a scroll view:

scrollView.parallaxHeader.view = headerView
scrollView.parallaxHeader.height = 150
scrollView.parallaxHeader.mode = .Fill
scrollView.parallaxHeader.minimumHeight = 20

Even cooler than that, MXParallaxHeader extends UIViewController to add a couple extra properties that allow us to easily add parallax header to any view controller.

Header views can optionally adopt the MXParallaxHeaderProtocol to implement custom parallax animations based on the user's scroll progress:

func parallaxHeaderDidScroll(parallaxHeader: MXParallaxHeader) {
  let angle = parallaxHeader.progress * CGFloat(M_PI) * 2
  self.falcon.transform = CGAffineTransformRotate(CGAffineTransformIdentity, angle)
}

More info about MXParallaxHeader can be found at git.io/mxparallaxheader

Page 5 of 10