Active Filters: UIKit

The iPhone and iPad are awesome devices with beautiful displays. Sometimes though, we might want to show parts of our app on a bigger, external screen.

Today we'll look at how we can do just that on iOS, let's dive in.

Before we begin let's break down what exactly we're talking about here:

UIKit exposes any external display we connect to our iPhone or iPad with an adapter as a UIScreen. The same is also true when we turn-on Airplay Mirroring to an Apple TV.

Now, let's write some code.

We'll eventually use Notifications to learn when a screen is connected/disconnected, but many users may launch our app with a screen already connected, so let's start by checking for that.

We'll create a couple instance properties to hold our the UIWindow and UIViewController we'll be displaying externally:

var externalDisplayWindow: UIWindow?
var externalDisplayVC: StatusBoardViewController?

Next, we'll make a function we can call when our app first launches, and when a new display is connected:

func checkForExternalDisplay() {
  guard let screen = UIScreen.screens.last
  else { return }

We'll create a new UIWindow and assign it to the external UIScreen:

  if externalDisplayWindow == nil {
    externalDisplayWindow = UIWindow(
      frame: screen.bounds
    )

    externalDisplayWindow?.screen = screen
  }

Next, we'll need a view controller. We'll create one and set it as the rootViewController just like usual:

  if externalDisplayVC == nil {
    externalDisplayVC = StatusBoardViewController()
    externalDisplayWindow?.rootViewController = externalDisplayVC

Last but not least, we'll show the window by setting its isHidden property to false.

    externalDisplayWindow?.isHidden = false
  }
}

If we connect a display now, and launch our app, we'll see our content displayed externally, neat!

Let's finish up by wiring up those notifications we mentioned earlier. First we'll start observing them:

NotificationCenter.default
  .addObserver(
    forName: NSNotification.Name.UIScreenDidConnect,
    object: nil,
    queue: nil,
    using: displayConnected
  )

NotificationCenter.default
  .addObserver(
    forName: NSNotification.Name.UIScreenDidDisconnect,
    object: nil,
    queue: nil,
    using: displayDisconnected
)

Then we'll write those two functions to handle each notification:

func displayConnected(notification: Notification) {
  checkForExternalDisplay()
}

func displayDisconnected(notification: Notification) {
  externalDisplayWindow?.isHidden = true

  externalDisplayVC = nil
  externalDisplayWindow = nil
}

Now our app will start/stop showing on the external display automatically when the user connects it. Very cool.

There's plenty more to dive into when working with external displays and UIScreen. We'll go further in future Bites.

That's all for today. Have an idea or request for a Bite? Send it along to hello@littlebitesofcocoa.com!

Topics

#303: Editing Videos in UIKit πŸ“Ό

Topics

Today we're continuing our hunt for hidden gems in UIKit with UIVideoEditorController.

It's a cousin to UIImagePickerViewController that exposes just the basic video editing functionality from that class in a standalone, dedicated video editing view controller.

Let's give it a try.

First, we'll need a video file to edit. We'll use this one which is part of a freely available collection.

We'll download the video, rename it to something simple, and drag it into Xcode. We'll check the box next to our app in the dialog that appears, so it gets copied to our app target.

Now, let's write some code.

First, we'll make sure our video can be found:

guard let path = Bundle.main.path(forResource: "video", ofType: "mp4") else { return }

...and that UIKit knows how to edit it:

guard UIVideoEditorController.canEditVideo(atPath: path) else { return }

Nice. Next, we can create a new video editor view controller and configure a few things on it:

let editor = UIVideoEditorController()

editor.videoPath = path
editor.videoMaximumDuration = 10.0
editor.videoQuality = .typeIFrame1280x720

Here we've told it our path, and given it a 10 second max duration. (Pro Tip: Default is 10 minutes, set to 0 for no max).

From here we can simply present it like any other view controller:

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

Neat!

The best part is all the functionality is self-contained inside the view controller.

The user can scrub through:

Trim the edges:

Then save the video back to the videoPath we set earlier:

Note: The documentation mentions UIVideoEditorController "only supporting Portrait" orientations, but it seems to work fine in all orientations.

Last but not least, we can (optionally) set a delegate on our UIVideoEditorController to get notified when the user saves or cancels (or a save fails):

editor.delegate = self

extension SomeViewController : 
    UIVideoEditorControllerDelegate, 
    UINavigationControllerDelegate {

  func videoEditorController(_ editor: UIVideoEditorController, 
    didSaveEditedVideoToPath editedVideoPath: String) {

    print("saved!")
  }
}

That's all for today. Know of an interesting UIKit B-side?

Send it on over!

Today we'll continue our series on finding hidden gems in UIKit with UIReferenceLibraryViewController.

Believe it or not there's a entire dictionary (yes like for viewing the definition of words/terms) just hanging out inside UIKit.

Let's give it a try! πŸ“–

We can present a new reference library view controller for any word/term we want like this:

present(
  UIReferenceLibraryViewController(term: "Spaceship"),
  animated: true, 
  completion: nil
)

Very cool.

This works great, but things start to break down when we look for something and it's nowhere to be found:

We can fix this by checking if a term can be found before presenting, using the static dictionaryHasDefinition(forTerm:) function:

if UIReferenceLibraryViewController.dictionaryHasDefinition(forTerm: term) {
  present(
    UIReferenceLibraryViewController(term: term),
    animated: true, 
    completion: nil
  )
}

Much better.

UIReferenceLibraryViewController supports many different languages. New languages can be installed using the "Manage" option in the bottom-left:

Finally, it's worth noting that we get definition functionality for "free" in UITextFields via the "Look Up" item in the UIMenu that's shown when a user selects some text:

... and it's also worth noting that the view controller that appears when a user taps one of these "Look Up" menu items in a UITextField appears to be far more advanced than the what we get when presenting a plain ol' UIReferenceLibraryViewController:

The Dictionary results are still present, but we also get results across Music, Wikipedia, Movies, Websites, and even the App Store. Very cool.

(And nope, UIKit does not seem to provide a way to trigger this fancier view controller directly. Anyone interested in this should probably file a Radar.)

That's all for today. Those who want more UIKit B-Sides can check out these bites here.

Building great forms for users to enter information on iOS is tough. There's a lot of small details which are easy to get wrong. Today we'll begin looking at ways to improve this process.

First up, is navigation. πŸ“

Allowing for quick and simple navigation from one form field to the next can dramatically reduce friction for our users.

We'll use a great new library from Thanh Pham called UITextField-Navigation to pull this off. Let's get started.

UITextField-Navigation is built around the concept of telling each field which field should be "next" in the form.

Once we've done that for all of our fields, the library takes over and displays a navigation bar above the keyboard automatically:

We can set up these relationships super easily in in Interface Builder:

...or in code:

let nameTextField = UITextField()
let bioTextView = UITextView()

nameTextField.nextNavigationField = bioTextView

Now, whenever a user begins editing a UITextField or UITextView in our app, a navigation toolbar will automatically be shown above the keyboard:

We can use UIAppearance to completely customize the look and feel of the toolbar:

NavigationFieldToolbar.appearance().barStyle = .black
NavigationFieldToolbar.appearance().backgroundColor = UIColor.purple
NavigationFieldToolbarButtonItem.appearance().tintColor = UIColor.white

Last but not least, we aren't limited to a "standard" set of buttons/items in the toolbar.

We'll use the navigationFieldToolBar optional property that the library adds to UITextField and UITextView to first customize some individual properties directly:

guard let toolbar = nameTextField.navigationFieldToolbar else { return }

toolbar.barStyle = .default
toolbar.backgroundColor = UIColor.red
toolbar.previousButton.title = "Backward!"
toolbar.nextButton.title = "Onward!"
toolbar.doneButton.title = "Dismiss"

...then create our own array of toolbar items:

let expandButton = UIBarButtonItem(
  title: "Expand",
  style: .plain,
  target: self,
  action: #selector(expandForm)
)

let flexible = UIBarButtonItem(
  barButtonSystemItem: .flexibleSpace, 
  target: nil,
  action: nil
)

toolbar.items = [
  toolbar.previousButton,
  toolbar.nextButton,
  flexible,
  expandButton,
  flexible,
  toolbar.doneButton
]

Neat!

Learn more about UITextField-Navigation at git.io/textfieldnav.

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

Page 1 of 9