Active Filters: 3D Touch

We've covered 3D Touch quite a bit here, we checked out View Controller Previews with Peek and Pop back in Bite #80.

Peek/Pop are great features, it's a shame only our users with the latest hardware can take advantage of them. Today, we'll check out a brand new library that aims to remedy this. It's from Roy Marmelstein and called PeekPop. Let's dive in.

The basic idea of PeekPop is to allow us to support 3D Touch style peeking and popping on devices that don't actually have 3D Touch hardware

The API is quite similar to UIKit's built-in one, let's try it out.

First, we'll register as a delegate, then conform to it:

peekPop = PeekPop(viewController: self)
peekPop?.registerForPreviewingWithDelegate(self, sourceView: collectionView!)

func previewingContext(previewingContext: PreviewingContext, viewControllerForLocation location: CGPoint) -> UIViewController? {
  return self.previewControllerForLocation(location)
}

func previewingContext(previewingContext: PreviewingContext, commitViewController viewControllerToCommit: UIViewController) {
  self.navigationController?.pushViewController(viewControllerToCommit, animated: false)
}

That's it! Neat.

PeekPop will use 3D Touch if available, then fall back on older devices to monitoring signficant changes in a UITouch's majorRadius property. (Pressing harder usually causes this to increase, even on older devices, since often more of the surface area of the finger is in contact with the screen).

More info about PeekPop can be found at git.io/peekpop

Topics

#95: 3D Touch 👊

Topics

We've covered a few of the new 3D Touch APIs in iOS 9 like Static and Dynamic Shortcut Items (Bite #79, #88) as well as View Controller Previews (Bite #80), but today we'll look at how to access and utilize raw force values from a user's touches. Let's get started.

We'll start with the "Single View" template. We'll open up our Main.storyboard and drag a UIProgressView out to the top of the screen. Then we'll give it an @IBOutlet in our ViewController and wire it up.

Next, the fun part. We'll create a new function called updateForTouches, that will take in an optional Set of UITouch objects. We'll guard to make sure 3D Touch is available, and assume our force is 0.0 unless a touch is present. Then, we update the progress property of our progress view and for good measure, we'll set a red background color on our view, mapping it's opacity to the touch's force as well.

func updateForTouches(touches: Set<UITouch>?) {
  guard traitCollection.forceTouchCapability == .Available else { return }

  var force: Float = 0.0

  if let touches = touches, let touch = touches.first {
    force = Float(touch.force / touch.maximumPossibleForce)
  }

  forceProgressView.progress = force
  view.backgroundColor = UIColor.redColor().colorWithAlphaComponent(CGFloat(force))
}

Lastly we'll need to implement all the touchesBegan, touchesMoves, touchesCancelled, and touchesEnded functions, calling our function and passing in the touches on each:

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
  super.touchesBegan(touches, withEvent: event)
  updateForTouches(touches)
}

override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
  super.touchesMoved(touches, withEvent: event)
  updateForTouches(touches)
}

override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
  super.touchesCancelled(touches, withEvent: event)
  updateForTouches(touches)
}

override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
  super.touchesEnded(touches, withEvent: event)
  updateForTouches(touches)
}

Success! If we build and run on an iPhone 6S or iPhone 6S Plus, we can start pressing the screen lightly and watch as the progress view and background color change as we begin pressing more firmly.

It might not be immediately obvious when playing around with Shortcut Items or View Controller Previews, but the hardware does in fact report every single tiny change in the force in real time. Neat!

You can download the project we built here.

Topics

#88: Dynamic Shortcut Items 📌

Topics

We covered Static Shortcut Items back in Bite #79, when 3D Touch was first introduced. Today we'll be taking a look at their close relative, Dynamic Shortcut Items. These offer the same behavior and functionality (allowing users to quickly jump to a particular section or feature of an app, straight from the home screen) but they can be configured and managed in code, and don't need to be defined in our Info.plist.

Dynamic Shortcut Items are particularly great for offering quick access to things such as user created content. Let's add a couple to our app. We'll use ours to let the user open recently viewed documents.

We'll create a function which we'll call whenever a document is opened in our app. It will update the current set of Dynamic Shortcut Items to match the 2 most recently opened documents.

We'll grab our recent documents, then define a new shortcut item for each one. We can handle tapping a Dynamic Shortcut Item just as we did in Bite #79, by passing a URL in the shortcut's userInfo property then using JLRoutes (Bite #62) to route it correctly inside application(application:performActionForShortcutItem shortcutItem:completionHandler:).

func updateDynamicShortcutItems() {
  let recentDocs = Document.recent(2)
  var shortcutItems = [UIMutableApplicationShortcutItem]()

  for doc in recentDocs {
    shortcutItems.append(
      UIMutableApplicationShortcutItem(
        type: "com.magnus.spaceships.document",
        localizedTitle: doc.name,
        localizedSubtitle: doc.excerpt,
        icon: UIApplicationShortcutIcon(type: .Compose),
        userInfo: [ "url": "spaceships://documents/\(doc.documentID)" ]
      )
    )
  }

  UIApplication.sharedApplication().shortcutItems = shortcutItems
}

Now both our static and Dynamic Shortcut Items are available by force pressing our app's icon on the home screen.

Note: At publish time of this Bite, Xcode did not offer a way to test shortcut items in the Simulator. To generate the screenshot above (and test shortcut items' functionality) this project by Conrad Kramer was used.

Topics

#80: View Controller Previews 👓

Topics

View Controller Previews are another iOS SDK feature announced with 3D Touch on the iPhone 6S and iPhone 6S Plus. They allow users to easily "peek" into a piece of content by pressing lightly, then pressing a little more firmly to actually open it. Let's take a look.

First we'll make sure 3D Touch is available, and register for previewing:

if traitCollection.forceTouchCapability == .Available {
  registerForPreviewingWithDelegate(self, sourceView: view)
} else { print("3D Touch is not available on this device.") }

Then we need to conform to UIViewControllerPreviewingDelegate:

func previewingContext(previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
  guard let indexPath = tableView.indexPathForRowAtPoint(location),
            cell = tableView.cellForRowAtIndexPath(indexPath) else { return nil }

  let vc = DetailViewController(spaceship: spaceships[indexPath.row])

  vc.preferredContentSize = CGSize(width: 0.0, height: 200.0)
  previewingContext.sourceRect = cell.frame

  return detailViewController
}

In the first function we'll need to return a fully configured UIViewController that will serve as the preview. We're coming from a table view controller, so we'll use the passed in location to grab the cell that was pressed, and configure our preview view controller.

We also set the cell's frame to be our previewContext's sourceRect. This accomplishes "blurring out" all the other elements on the screen during the preview.

We finish things out by implementing the second function, called when the user decides to "pop" into the content. We could use a different view controller but we'll just reuse our existing preview one and show it:

func previewingContext(previewingContext: UIViewControllerPreviewing, commitViewController viewControllerToCommit: UIViewController) {
  showViewController(viewControllerToCommit, sender: self)
}

Topics

#79: Static Shortcut Items 📌

Topics

Shortcut Items were introduced with the announcement of 3D Touch on iPhone 6S and iPhone 6S Plus. They allow users to quickly jump to a particular section or feature of an app, straight from the home screen.

Shortcut Items come in two forms: Static, and Dynamic. Static shortcut items live in the Info.plist of our project. Dynamic shortcut items are defined in code, and configured at run time. Let's take a look at how to implement static shortcut items.

The first step is to configure the shortcut items in our Info.plist file:

Then, we need to implement one new function on our app delegate. We're using JLRoutes here (covered in Bite #62) to run some code when a given URL is passed in.

Then we'll call the provided completionHandler, with a Bool indicating whether we were able to handle the item.

func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: Bool -> Void) {
  let url = NSURL(string: shortcutItem.userInfo["url"])!

  let didHandle = JLRoutes.routeURL(url)

  completionHandler(didHandle)
}

Note: At publish time of this Bite, Xcode did not offer a way to test shortcut items in the Simulator. To test shortcut items' functionality, this project by Conrad Kramer was used.

UPDATE: We covered Dynamic Shortcut Items in Bite #88. Head there to read about how to modify an app's shortcut items in code, at runtime.