Active Filters: UIKit

Topics

#296: Today Extension B-Sides πŸ“Ό

Topics

We first covered Today Extensions way back in Bite #36. They're a great way to offer quick, glanceable information or entry-points to our app. Today we'll take a look at some lesser known features of Today Extensions, and how we can use them in our code. Let's begin.

3D Touch Shortcut Widgets

First up, is one of the newest additions to the Today Extension world: 3D Touch Homescreen Widgets.

The coolest bit is, we don't really need to do anything to "get" this. If our app has a Today Extension, the widget will automatically appear when a user 3D Touches our app's icon.

The B-Side comes into play when we have multiple Today Extensions in our app. We'll need a way to tell the system which one to display above our icon. We can do this by adding a new key to our app's Info.plist:

UIApplicationShortcutWidget

(We can also choose "Home Screen Widget" from the keys dropdown).

Neat!

Conditonal Display

Today Extensions Widgets don't have to always be visible in the Today view. We can actually tell the system whether or not our widget should be displayed with this one function:

NCWidgetController
  .widgetController()
  .setHasContent(true, 
    forWidgetWithBundleIdentifier: "com.littlebitesofcocoa.latest")

Calling this with false will hide the widget in the Today View until our app calls the function again with a true value.

Opening Our App

This one is a bit of a stretch to truly call a B-side, but it can easily be done incorrectly, so here we are.

It's quite common for a Today Extension Widget to need to open its containing app.

Apple has tightened the reigns in recent OS releases to "validate" when and how apps (and specifically Today Extensions) can open apps. Not to worry, we can use this special function on an NSExtensionContext to open a deep link into our app.

self.extensionContext?.openURL(NSURL("lboc://bites/296"), completionHandler: nil)

Pro Tip: Opening our own app (i.e. our app that contains the Today Extension) is just fine, but beware if we start trying to open other apps using this function, Apple may scrutinize our Today Extension further during App Review.

Properly responding integrating with the software keyboard is a big part of building a solid iOS app. Traditionally, this would involve observing NSNotificationCenter notifications and some pretty lengthy boilerplate code. Today we'll look at a great little library from Toto Tvalavadze called Typist that can improve all this. Let's check it out.

Typist works by exposing a way for us to easily "opt-in" to certain keyboard events:

Typist.shared
  .on(event: .didShow) { (options) in
    // TODO
  }
  .start()
}

Yep. That's it. Gone are the wildly verbose notifications and complicated setup.

(Note: We're not required to use Typist's singleton instance, but it's provided for convenience).

Typist provides this same interface for a bunch of useful events:

Typist.shared
  .on(event: .willShow) { _ in /* TODO */ }
  .on(event: .didShow) { _ in /* TODO */ }
  .on(event: .willHide) { _ in /* TODO */ }
  .on(event: .didHide) { _ in /* TODO */ }
  .on(event: .willChangeFrame) { _ in /* TODO */ }
  .on(event: .didChangeFrame) { _ in /* TODO */ }
  .start()
}

Finally, inside each event closure, we're given a Typist.KeyboardOptions type that contains strongly-typed properties describing the event:

Typist.shared
  .on(event: .willShow) { (options) in
    guard options.belongsToCurrentApp else { return }

    updateLayout(for: options.endFrame)
  }
  .on(event: .willHide) { (options) in
    guard options.belongsToCurrentApp else { return }

    updateLayout(for: options.endFrame)
  }
  .start()
}

Here we're guard-ing to make sure the keyboard belongs to our app, and returning early if not.

Then, we use the endFrame property to adjust our own views to accomodate the keyboard.

Neat!

Typist's options type also provides startFrame, animationCurve, and animationDuration properties to help us match our app's animation to the keyboard's.

Learn more about Typist at http://git.io/typist

Text is a huge part of iOS apps. Today we'll look at one way we can polish up the text in our apps, by allowing it to naturally "flow" around objects. Let's begin.

We'll start with a "before" picture. Here we've got a basic setup with some text and an image in the top right corner. Nothing too fancy here, just a regular UITextView and UIImageView:

let imageView = UIImageView(image: UIImage(named: "steve"))

let textView = UITextView(frame: .zero)  
textView.text = "Here's to the crazy ones..."

This looks fine. But our text is needlessly narrow. It'd be great if we could make the text expand to the full width of the device. We'll change our Text View's layout so it does:

Well, we're getting closer. Now we just need a way to make the text "flow" or "wrap" around our image. We'll start by getting the frame of the image view, then we'll create a path from it:

let imagePath = UIBezierPath(rect: imageView.frame)

Finally, we'll set our new path as one of the exclusionPaths of our Text View's textContainer:

textView.textContainer.exclusionPaths = [imagePath]

Success! Since exclusionPaths is an Array, we can flow text around as many shapes as we want!

UITableView & UICollectionView have some fantastic animation capabilities. We can animate items and sections being inserted, removed, moved, etc. We've all been there though, reloadData is sitting right there, and is conceptually so simple. Wouldn't it be great if we could enable those fancy animations in our views, with the conceptually simplicity of reloadData?

Today we'll check out Dwifft, a library from Jack Flintermann that can help us achieve this. Let's dive in.

We'll begin with a regular UITableViewController. (Dwifft works just as well on UICollectionViews).

First we'll need a TableViewDiffCalculator. This is a generic type, so we'll tell it that we're going to be display a bunch of Strings.

class ExampleViewController: UITableViewController {
  var diffCalculator: TableViewDiffCalculator<String>?

Next we'll initialize our data with some random items (just a randomly ordered array of Strings). We'll also use a property observer to let our diff calculator know whenever our items change.

  var items: [String] = ExampleViewController.randomItems() {
    didSet { self.diffCalculator?.rows = items }
  }

We're almost there. We'll initialize the diff calculator in our view controller's viewDidLoad function:

  override func viewDidLoad() {
    super.viewDidLoad()

    tableView.register(UITableViewCell.self, forCellReuseIdentifier: "ItemCell")
    diffCalculator = TableViewDiffCalculator<String>(
      tableView: tableView,
      initialRows: items
    )
  }

Lastly, we'll implement a couple of standard UITableViewDataSource functions:

  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: "ItemCell", for: indexPath)
    cell.textLabel?.text = selfitems[(indexPath as NSIndexPath).row]
    return cell
  }
}

Success! Here's how this ends up looking:

The "Shuffle" button was wired up off-camera, but it simply sets self.items = ExampleViewController.randomItems().

Now we can simply update the diff calculator's rows and we'll get all those beautiful, scroll-position preserving animations automatically.

A couple of final notes, we can customize the individual animation types like this:

diffCalculator?.insertionAnimation = .fade
diffCalculator?.deletionAnimation = .fade

And finally for fun the science behind Dwifft is pretty neat to read about. Read more about the Longest Common Subsequence Problem here.

Learn more about Dwifft at git.io/dwifft

Alerts have been around since iOS was iPhone OS. First in the form of UIAlertView, and now through UIAlertController (Bite #44). The built-in system alerts work great, but sometimes we want something fancier 🎩. Today we'll check out PMAlertController from Paolo Musolino, a library that allows us to easily display great looking alerts in our app. Let's take a look.

PMAlertController is essentially a fancier replacement for the styles that UIKit's UIAlertController already provides.

PMAlertController two styles of alerts: "Alert" and "Walkthrough".

The "Alert" Style is 270 points wide (matching the system's alerts):

The "Walkthrough" Style is almost the full width of the screen and is appropriate for more in-depth alerts/dialogs (such as those for explaining why our app is requesting permission for some of the user's data):

Using PMAlertController is almost just like UIAlertController.

We're given a title, description and image to work with:

let alertVC = PMAlertController(
  title: "Locate your device", 
  description: "Enables access to your...",
  image: UIImage(named: "flag.png"),
  style: .alert
)

alertVC.addAction(PMAlertAction(
  title: "Cancel", style: .cancel,
  action: { in }
))

alertVC.addAction(PMAlertAction(
  title: "Allow", style: .default,
  action: { in }
))

present(alertVC, animated: true, completion: nil)

We instantiate the view controller, add some actions (each with their own closure handler that will be called when the action is tapped), and present it.

Neat!

Libraries like PMAlertController can help us easily add some polish to our apps, but they can also be a great way to learn about creating custom controls in general.

Learn more about PMAlertController at git.io/pmalert

We've looked at protocols in Swift in Bite #232, but we haven't really seen a ton of "real world" examples. Today we'll check out a library called CostumeKit. It's a set of base protocols that can assist us in visually styling or "themeing" our apps. Along the way we'll use some protocols "for real". Let's jump in.

Author's Note: Full disclosure, CostumeKit is written by me. I'm a huge fan of conventions, also it's a nice basic example of Protocols. I use it in all my apps.

To try this out, we'll be working on an imaginary Little Bites of Cocoa app.

We'll start by adding CostumeKit to our project with Carthage:

github "jakemarsh/CostumeKit"

Next, we'll start implementing our types. First up is the colors we'd like to use.

We'll make an enum and paste in all the colors we want to use, giving each a friendly name:

public enum LittleBitesColors : Color, ColorPalette {
  case beige = "FEFAF1"
  case lightBrown = "AB9372"
  case brown = "8C5637"
  case lightGray = "ECECEC"
  case darkGray = "6A6A6A"
  case sponsorRowGray = "F5F5F5"
}

Sharped-eyed-readers will actually recognize this technique from Bite #255 on Creating a ColorConvertible Protocol.

Moving along, LBOC uses the Source Sans Pro font. Let's implement CostumeKit's Font protocol for this. We'll add the font files to our app then implement the protocol:

public struct LittleBitesFont : Font {
  public init(size: FontSize = .textStyle(.body)) {
    self.size = size
  }

  // Font

  public var size: FontSize

  // FontConvertible

  public var FontValue: UIFont {
    return UIFont(name: "SourceSansPro", size: pointSize)!
  }
}

We're almost there, next we need to create the actual Costume our app will wear. For this we'll implement one more protocol. This protocol has no requirements. It serves as more of a convention really.

open class LittleBitesCostume : Costume {
  let spacing = CGFloat(8)

  public func wearRootBackground(_ view: UIView) {
    view.backgroundColor = Color.white.colorValue
  }

  public func wearHeadline(_ label: UILabel) {
    label.font = LittleBitesFont(size: .textStyle(.title1)).fontValue
    label.textColor = contentTextColor().colorValue
  }
  public func contentTextColor() -> Color {
    return LittleBitesColors.darkGray
  }

  public var name: String { return "Default" }
  public var description: String { return "The default costume." }

  public init() { }
}

For good measure, we'll define a night-mode costume as well:

open class LittleBitesNightCostume : LittleBitesCostume {
  public override func contentTextColor() -> Color {
    return Color.white
  }

  override public var name: String { return "Night Mode" }
  override public var description: String { return "For reading in the dark." }
}

That's where CostumeKit stops. We get a super-tiny bit of functionality "for free" (fonts, color parsing), but the idea is that we're merely following a set of conventions to help guide us through writing this code.

Let's finish up by looking at how we might actually use costumes in our app. Everything from this point on would live inside our app's code (and isn't part of CostumeKit).

We'll make a Wardrobe type and static instance to manage all our costumes:

import CostumeKit

public class Wardrobe {
  private(set) var current: LittleBitesCostume

  init(initialCostume: LittleBitesCostume) {
    current = initialCostume
  }

  public func change(costume: LittleBitesCostume, animated: Bool = true) {
    // TODO: In the future, we'll animate this change using UIView transitions and RxSwift.

    current = costume
  }
}

public let wardrobe = Wardrobe(initialCostume: LittleBitesCostume())
public var costume: LittleBitesCostume { return wardrobe.current }

Now in our regular UIKit code, we can use our current costume:

let headlineLabel = UILabel()
headlineLabel.text = "Hello World".
costume.wearHeadline(headlineLabel)

Learn more about CostumeKit at git.io/costumekit

Topics

#269: Taptic Engine Basics πŸ‘‹

Topics

With the iPhone 7 and iPhone 7 Plus, Apple added a remarkable new piece of hardware to our devices. It's called the Taptic Engine and it is a big change from the old vibration feedback mechanisms we're used to. Today we'll look at how we can use it in our apps. Let's get started.

Haptic feedback on iOS is all about subtlety.

We want to help inform and/or guide the user, not annoy them. For these reasons, Apple has provided a few different classes we can use to generate haptic feedback that matches what we find around the system.

First up, UIImpactFeedbackGenerator. This is great for more prominent user interactions like when a part of our UI "snaps" into place (for example, imagine a drawing app that snaps when you drag over the exact center of the document).

Next, UISelectionFeedbackGenerator. These are perfect for small, subtle changes in selection. We can preview this one by 3D Touching on an app's icon on the home screen (one with a few 3D Touch shortcuts). If we keep our finger down and drag between the menu items, we'll feel the slightest "click" as we change selection. Neat!

Last, we have UINotificationFeedbackGenerator. These are pretty heavy and are meant for things like errors or warnings. (Imagine a login form that uses this generator when the user enters an incorrect password).

Now, lets try this out in code. First we need to instantiate a generator, and call the prepare function on it.

let generator = UISelectionFeedbackGenerator()
generator.prepare()

The prepare function is crucial here. It has to do with the physical hardware inside the device.

Basically, prepare will "wake up" the Taptic Engine hardware and put it into a state where it's ready to generate feedback immediately when we ask it to. iOS is relentless about saving battery and power. The Taptic Engine isn't always using "full" power, and only does so for a few seconds after the prepare function is called. Neat.

Things will still work if we don't call prepare, but they might not perfectly match up with the changes on screen (another crucial part of crafting effective Haptic feedback).

Now all we need to do trigger the actual haptic to "play" is:

generator.selectionChanged()

Each generator has its own function for "playing" the haptic. (i.e. impactOccurred, selectionChanged, and notificationOccurred, respectively).

Finally, let's look at a "complete" example using a gesture recognizer:

var generator : UISelectionFeedbackGenerator? = nil

func panned(_ sender: UIPanGestureRecognizer) {
  switch(sender.state) {

  case .began:
    generator = UISelectionFeedbackGenerator()
    generator?.prepare()
  case .changed:
    if selectionChanged(translationPoint: sender.translation(in: view)) {
      generator?.selectionChanged()

      // we call prepare again right after "playing",
      // to make sure the taptic engine is "ready" if the user
      // moves their finger again quickly.
      generator?.prepare()
    }

  case .cancelled, .ended, .failed: generator = nil

  default: break
  }
}

Success!

Topics

#268: What's New in Tab Bar Customization 🎨

Topics

Tab Bars have been around since the very first iPhone. They are a fantastic way to organize the top level screens in our app, and give users a quick way to get to each. Today we'll check out the latest improvements to how e can customize the look and feel of UITabBars in our app. Let's begin.

First up: Badge Customization.

For years we've had to resort to custom drawing or other methods to change how the badge would appear on a tab bar in our app.

In iOS 10, Apple has added a couple of features to allow us to completely customize how this looks with only a few lines of code.

Let's try this out. First, we'll set a badge value on one of the tabs:

func configureTabBar() {
  guard let tabBarItem = self.tabBarItem else { return }

  tabBarItem.badgeValue = "268"
}

Nice. Now let's customize that badge. All we need is one function and some attributes:

tabBarItem.setBadgeTextAttributes([
  NSFontAttributeName : UIFont(name: "AvenirNextCondensed-Medium", size: 14.0)
], for: .normal)

Neat.

Next, we can now easily change the background color of the badge:

Lastly, we'll customize the tabs themselves using one final new feature, unselectedItemTintColor (Finally):

guard let tabBar = self.tabBarController?.tabBar else { return }

tabBar.tintColor = UIColor(white: 0.1, alpha: 1.0)
tabBar.unselectedItemTintColor = UIColor.lightGray

Success!

Topics

#263: Changing the Color of the Status Bar 🏴🏳

Topics

Today we're going to take a look at how to control how the status bar appears in our app. Let's get started.

The simplest way to change how the status bar looks in our app is to override preferredStatusBarStyle:

class SpaceshipViewController : UIViewController {
  override var preferredStatusBarStyle: UIStatusBarStyle {
    return .lightContent
  }
}

The .lightContent style lives up to its name, as it makes the status bar's content white.

This work great until we put our view controller inside a navigation controller. Overriding the property then has no effect, as the system is actually asking the navigation controller.

We've all been here:

Yuck. Let's fix that up.

Since our content here is in a navigation controller we can use a "tried and true" approach that involves simply subclassing UINavigationController:

class LightStatusBarNavigationController : UINavigationController {
  override var preferredStatusBarStyle: UIStatusBarStyle {
    return .lightContent
  }
}

To make this work, we can set this custom subclass in Interface Builder, or use it when creating our UI in code. Let's have a look now:

Nice.

This approach works in our case, but might not be the best way to do this. If the navigation bar ever gets hidden, UIKit will actually start asking the contained view controller for the status bar's style, which may or may not be what we want.

We could have accomplished the same thing using a plain old UINavigationController by simply setting its barStyle property when we create it (or by setting this property in Interface Builder) to the .blackOpaque style:

let nc = UINavigationController(rootViewController: spaceshipsVC)
navigationController?.navigationBar.barStyle = .blackOpaque

Or, even better, we could set this style using UINavigationBar's appearance proxy, so this style is applied to all the navigation bars in our app:

UINavigationBar.appearance().barStyle = .blackOpaque

(A huge hat tip to Caleb Davenport for helping out with these techniques!)

That takes care of the screens in our code, what about when the app first launches though?

For this, we'll need to turn to our project's settings.

Here we can hide and show the status bar during launch, and change its color. Let's take another look:

Perfect!

Topics

#256: UIPasteboard Revisited πŸ“‹

Topics

UIPasteboard is a wonderfully simple little type with a ton of great functionality inside. (Covered previously in Bite #45). Today we'll check out the latest pasteboard improvements in iOS 10. Let's begin.

The biggest new user-facing feature of pasteboards in iOS 10 and macOS Sierra is the Universal Clipboard. This allows users to copy some content into their clipboard on one device, and retrieve it on another.

We don't need to do anything to get this functionality in our apps. This behavior is on by default for UIPasteboard.general, but we can configure how items behave when we add them.

Expiration Dates

let tomorrow = Date().addingTimeInterval(60 * 60 * 24)

UIPasteboard.general.setItems(
  [[ "URL": URL(string: "https://littlebitesofcocoa.com")! ]],
  options: [.expirationDate : tomorrow]
)

Preventing Universal Clipboard

Sometimes there's going to be data that doesn't make sense to sync using the Universal Clipboard. This is easy.

UIPasteboard.general.setItems(
  [[ "URL": URL(string: "https://littlebitesofcocoa.com")! ]],
  options: [.localOnly : true]
)

has* Properties

Last but not least, UIPasteboard has a few new helper properties for easily checking the current state of a pasteboard:

// we can check for images, URLs,
// strings, and colors this way
if pasteboard.hasURLs {
  print(UIPasteboard.general.urls)
}
Page 1 of 8