Topics

#305: Working with External Displays on iOS 📱📺

Topics

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!

Weekly Sponsor: Zendesk 💡

We're welcoming back one of our favorite sponsors this week, it's Zendesk!

None of us have ever built a flawless app.

Chances are, no matter how good we think our app's user experience is, there's probably something users will need help with while they're in our app.

Yet in many apps, getting help is reduced to a "contact us" button that launches an compose email screen, or a just drops the user on a web page.

By forcing folks out of the app to get help, small problems can turn into big annoyances, and those will almost certainly turn into negative App Store reviews and poor ratings.

With Zendesk's Mobile SDKs, we can join the makers of Angry Birds, Venmo and Swiftkey in bringing native, in-app support functionality to our apps quickly and easily.

Our users can view help and support content, and even submit tickets to support without ever leaving our app. Neat!

Tickets go into Zendesk and can include technical details about the user and their device, history with our apps, and more.

Best of all, it's included with Zendesk at no extra charge.

We can use Zendesk's "out-of-the-box" iOS UI components to get up and running quickly, or we can build your own UI with SDK API Providers.

A huge thanks to Zendesk for sponsoring!

Topics

#304: Simple Sound Playback with SwiftySound 🔊

Topics

Apple ships a ton of great APIs for playing audio in our apps.

They offer a wealth of great functionality, but also a fair amount of complexity.

Often we don't need all that power. Perhaps we're adding sound effects or audio feedback to one of our user interfaces, or maybe we just need to loop some background music in the menu of our video game.

Today we'll check out a new library from Adam Cichy called SwiftySound that can help us simplify our audio playback code. Let's take a look.

First up, simple playback:

Sound.play(file: "unscheduled-offworld-activation.wav")

or

Sound.play(url: someURL)

That's it. Yes, really.

This kind of UIImage-esque simplicity has always been sort of missing from UIKit. Neat to see.

SwiftySound doesn't stop there though, We can easily loop sounds:

Sound.play(file: "main-menu", fileExtension: "wav", numberOfLoops: -1)

We could pass in any Int here to loop the music, but we've passed -1 to indicate we want to loop forever.

One of the most useful features of SwiftySound is the inclusion of a UserDefaults-persisted flag for persisting the user's preference of sound playback being enabled or disabled.

We can use this on our app's Settings screen to allow users to easily enable/disable sound effects globally.

Sound.enabled = soundsSwitch.on

The value is then automatically persisted and respected across app launches. A nice touch by SwiftySound's author, Adam Cichy.

We're not required to use this singleton Sound instance by the way, we can always create a Sound instance and pass it around:

let sound = Sound(url: soundEffectURL)

Then later:

sound.play()

Last but not least, SwiftySound stands out for being a single Swift file.

It's great to see this kind of power and flexibility in such a tiny little dependency.

😍 More like this please!

Full documentation for SwiftySound is at git.io/swiftysound

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!

Weekly Sponsor: PSPDFKit 📑

This week, we're welcoming back an awesome sponsor to LBOC, it's PSPDFKit! PSPDFKit delivers an intuitive & seamless SDK for integrating PDF functionality into apps on iOS, Android, and the Web.

PDF files have been around a long time. Over that time they evolved to become an incredibly versatile and functional format. They're also wide-ranging in terms of how they're used. Comic books, Apartment leases, flight manuals, etc. All powered by PDFs.

Being able to take advantage of all this great functionality in our apps would be huge for our users.

With it, we can beautifully display PDFs, and allow our users to intuitively annotate documents, and much, much more.

Let's get started.

We'll head over to PSPDFKit's wonderful documentation area and follow the Getting Started steps to integrate the Framework directly, or through a system like CocoaPods.

Once we've got PSPDFKit in our app, we can start playing around.

PSPDFKit provides tons of great APIs for programmtically interacting with PDF files, as well as a bunch of polished, (and incredibly customizable) user interfaces to allow our users to read and manipulate PDF files themselves.

Let's try opening a PDF, and displaying it:

let document = PSPDFDocument(url: documentURL)

let controller = PSPDFViewController(document: document, configuration: PSPDFConfiguration { builder in
    builder.thumbnailBarMode = .scrollable
    builder.scrollDirection = .horizontal
})

We create a new PSPDFDocument, then a new view controller to show it. We use PSPDFKit's awesome PSPDFConfiguration type to neatly build up our settings.

From here we can simply present it like any other view controller. Neat!

PSPDFKit stands out with an awesome, well thought-out API. It's clear that they care deeply about API design, and developer experience.

Before we go, let's try out another couple features. First up, Annotations:

// Create a new PSPDFDocument
let document = PSPDFDocument(url: documentURL)

// Create a link annotation, and set its URL
let linkAnnotation = PSPDFLinkAnnotation(url: URL(string: "https://pspdfkit.com")!)

// Position the annotation in the document
let boundingBox = CGRect(x: 200, y: 400, width: 50, height: 300)
linkAnnotation.boundingBox = boundingBox

// Customize the link annotation's appearance
PSPDFLinkAnnotationView.appearance().borderColor = .blue

// Add the newly created annotation to the document
document.add([linkAnnotation])

Very cool, Next up, Forms. Our users can of course fill out any form fields in documents they open, but we can actually fill them out programmtically too:

let document = PSPDFDocument(url: documentURL)

guard let annotations = document.annotationsForPage(at: 0, type: .widget) else { return }

for annotation in annotations {
    switch annotation {
    case let textFieldFormElement as PSPDFTextFieldFormElement:
        guard let fieldName = textFieldFormElement.fieldName else { return }
        textFieldFormElement.contents = String(format: "Test %@", arguments: [fieldName])

    case let buttonFormElement as PSPDFButtonFormElement:
        buttonFormElement.toggleButtonSelectionStateAndSendNotification(true)
    default:
        break
    }
}

Very cool. We iterated through the form fields and filled them with some example content, and toggled the selected state for any buttons in the form.

We have truly only scratched the surface of what's possible with PSPDFKit here.

PSPDFKit offers a ton of other great features like indexed search, digital signatures, and even document editing! They also provide wonderfully fast and responsive support.

Be sure to check out PSPDFKit's extensive Guides area to find complete documentation and tons of extensive guides.

Thanks to PSPDFKit for their support of LBOC!

Topics

#302: Lazy Properties 🐤💤

Topics

Today we're going to begin looking at the lazy keyword in Swift. It describes some functionality in the language for what Apple calls " just-in-time calculation" of work.

This is great for times when we have expensive work to perform, or maybe the work is just some ugly complex code we'd like to tuck away. Perhaps we simply want to better compartmentalize the bits of our code that initialize UI elements, the use cases are plenty.

Today we'll start with Lazy Variables. Let's dive in!

Say we have a View Controller with a titleLabel. Pretty old school standard stuff.

We need to create the titleLabel, save it to a property, configure some stuff about it, then add it into the view heirarchy.

We want this work to be done after the view is loaded, and not when the view controller is first created, so we are forced to put it in viewDidLoad.

Additionally, we're forced to use a UILabel! force-unwrapped type on our titleLabel property to make the compiler happy.

It ends up looking something like this:

class ViewController: UIViewController {
  var titleLabel : UILabel!

  override func viewDidLoad() {
    super.viewDidLoad()

    titleLabel = UILabel()

    titleLabel.font = UIFont(name: "Source Sans Pro", size: 18.0)
    titleLabel.textColor = UIColor.brown

    view.addSubview(titleLabel)
  }

Let's make our titleLabel lazy and clean this junk up:

class ViewController: UIViewController {
  lazy var titleLabel : UILabel = {
    let label = UILabel()

    label.font = UIFont(name: "Source Sans Pro", size: 18.0)
    label.textColor = UIColor.brown

    return label
  }()

  override func viewDidLoad() {
    super.viewDidLoad()

    view.addSubview(titleLabel)
  }
}

Neat!

We've now dramatically improved our viewDidLoad implementation. Cool.

We've also neatly compartmentalized the bit of code that sets up that label. Cool cool.

Perhaps most importantly though, we've now better expressed our intentions using language keywords (and their resulting behaviors), Cool cool cool.

Note: We have to use var instead of let when defining lazy properties like this. Our property might not (likely won't) have an initial value until after the instance of our view controller is created and its view is loaded. This completely breaks the rules of lets (constants).

That's all for now, in future Bites we'll look at other lazy behaviors in Swift!

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.

Topics

#300: Terminology 101 🎓

Topics

Recognizing and understanding new terms can be a big challenge when we're trying to grow as creators.

Often an unknown term or phrase can make us feel inexperienced, or ask "should I already know this?"

(Pro Tip: No, you shouldn't. We all learn new things constantly. That's the whole point. Keep going!)

Today we'll begin trying to identify and shed light on some of the more opaque terms or phrases we might come across while learning to build our apps.

We'll approach them from the point of view of iOS/macOS developers, but the terms themselves can be used to describe things on just about any platform.

Let's begin!

Dependency Injection

We're starting with a classic. This is one of the fanciest names for one of the simplest concepts.

This is just a complicated way of describing the idea of creating some type or object, while passing in some other type or object that the first type depends on to do its work.

Here's a super simple example:

class PlacesViewController : UIViewController {
  let manager: PlacesManager

  init(manager: PlacesManager) {
    self.manager = manager
  }
}

Note how we're passing in the manager rather than accessing some PlacesManager.shared instance.

This technique has numerous implications, the most common one being around testing.

By passing in the "dependency" we give ourselves the chance to pass in a "mock" version that can behave in expected ways.

Additionally, by not using shared instances, we reduce global/shared state in our apps.

Singleton

This one is easy, and we actually just mentioned it.

A "singleton" is just a fancy word for a shared instance...

...which is just a fancy phrase for a thing that is created once and only once. They are usually long lived, and available globally via some kind of Thing.shared static property.

We covered more about singletons all the way back in Bite #4.

Modules

This is a fun one. A Swift "Module" describes the unit of code that makes up an app or framework.

Sometimes we use a module's name as a prefix for colliding type names. Like this:

// in SailingKit.framework
struct Ship { init() { } }
// in SpaceshipsKit.framework
struct Ship { init() { } }
// in an App:

import SailingKit
import SpaceshipsKit

func someFunction() {
  let ship1 = SailingKit.Ship()
  let ship2 = SpaceshipsKit.Ship()
}

Tests

Finally we'll finish up with tests. Testing itself is a fairly straightforward concept:

We write some code that doesn't go into our app. It calls the code we wrote in our app, and checks its output to make sure its working as expected.

The terms come into play when we start trying to describe types of tests.

Unit Tests

These kinds of tests usually target one small individual piece of code.

For example, we might call a function that calculates some CGRects for a layout, based on a given available container width. We'd then check that the CGRects had the expected values.

For these reasons, "Unit" testing libraries tend to provide low level assertion functions to ensure (for example) return values are what we expect.

Integration/UI Tests

These are generally a bit more broad. They tend to target code that is closer to the user's actual behavior or interactions in the app.

On iOS/macOS, these are also commonly referred to as "UI" tests, since this is the name Apple gives the feature in Xcode we use to similuate a user tapping around in our app.

Pro Tip: Learn more about "UI" Testing in these Bites

In contrast to "Unit" testing libraries, "Integration" testing libraries tend to provide tools for replicating user behavior. Tapping buttons, filling fields, and generally using the app.

That's all for today, we'll continue to explore more of these kinds of terms in future Bites!

Is there one you'd like to see more about here on LBOC? Feel free to share it using hello@littlebitesofcocoa.com!

Author's Note: 300 Bites! Never in my wildest dreams could I have imagined LBOC being such a success. My sincere thanks goes out to everyone who continues to read and subscribe each day, as well as the team at Spec and all of our wonderful sponsors who make this all possible. Here's to the next 300!

Weekly Sponsor: Zendesk 💡

We're welcoming back one of our favorite sponsors this week, it's Zendesk!

None of us have ever built a flawless app.

Chances are, no matter how good we think our app's user experience is, there's probably something users will need help with while they're in our app.

Yet in many apps, getting help is reduced to a "contact us" button that launches an compose email screen, or a just drops the user on a web page.

By forcing folks out of the app to get help, small problems can turn into big annoyances, and those will almost certainly turn into negative App Store reviews and poor ratings.

With Zendesk's Mobile SDKs, we can join the makers of Angry Birds, Venmo and Swiftkey in bringing native, in-app support functionality to our apps quickly and easily.

Our users can view help and support content, and even submit tickets to support without ever leaving our app. Neat!

Tickets go into Zendesk and can include technical details about the user and their device, history with our apps, and more.

Best of all, it's included with Zendesk at no extra charge.

We can use Zendesk's "out-of-the-box" iOS UI components to get up and running quickly, or we can build your own UI with SDK API Providers.

A huge thanks to Zendesk for sponsoring!

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.

Page 1 of 36