Want to offer native, in-app customer service to your users? Get going quickly with Zendesk’s Mobile SDKs. Free with Zendesk.

Topics

#270: Implementing Theming with CostumeKit 🎩

Topics

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!

Weekly Sponsor: Zendesk 💡

This week we're welcoming back a fantastic sponsor, 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 bring 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!

We first looked at Handoff and NSUserActivity all the way back in Bite #29. Today we'll take a look at a new addition to NSUserActivity in iOS 10 that allows our apps to share more about the user's current context with other parts of the system. Let's dive in. 🏊

Let's imagine our app lets users view a list of Robot Stores. We'll start by re-using a technique we first learned about in Bite #47, MKLocalSearch.

First we'll create an MKLocalSearchRequest to find all the Robot Store locations within a reasonable radius of the user's current location (acquired off-camera via standard Core Location mechanisms):

let request = MKLocalSearchRequest()

request.naturalLanguageQuery = "Robot Store"
request.region = MKCoordinateRegionMakeWithDistance(
  usersCurrentLocation, 1600, 1600)

Next, we'll start an MKLocalSearch using our request, and load a bunch of mapItems representing Robot Store locations near us.

If we were building a complete app, this is the part where we'd grab the response.mapItems from above, map their values into some business objects, then display them probably in UITableView, etc.

For now, let's simply try out the new feature of NSUserActivity.

MKLocalSearch(request: request).start { (response, error) in
  guard error == nil else { return }
  guard let response = response else { return }

  guard let exampleMapItem = response.mapItems.first else { return }

  let activity = NSUserActivity(activityType: "com.robots-store.shopping-locally")

  activity.title = exampleMapItem.name
  activity.mapItem = exampleMapItem

  self.userActivity = activity
}

Finally, we can run our app, double tap our home button and see the results:

Success!

We're finishing up our extensive look into the new Notifications improvements in iOS 10 today with Notification Service Extensions. These fill out the notifications functionality by allowing us to intercept remote notifications as they are received on the device, take some action(s), and then modify the notification before it's shown to the user. Let's get started!

We'll begin by looking at the broad concept going on here. Until iOS 10, Remote Notifications were presented to the user as soon as they arrived, without any interaction from their app.

In iOS 10, Apple has provided a mechanism for us to actually "intercept" Remote Notifications when they arrive, do some work, modify the notification's content, then send it along to be displayed to the user.

The whole process now goes like this:

This new step opens up a ton of possibilites and gives us a chance to do things like download media, or perform some other short work to enrich the notification before the user sees it.

Let's try this out.

First, we'll add another new target to our app, and choose a Notification Service Extension:

In the new group that was created we'll find one file, a subclass of UNNotificationServiceExtension.

Notification Service Extensions run in the background, and never show any kind of interface themselves. Instead, they override one function that's called whenever a notification is received.

We're passed in a notification request and a completion closure.

The request is the same UNNotificationRequest type we first learned about in Bite #258, only with its trigger property set to a UNPushNotificationTrigger, neat!

We'll perform whatever work we need, grab the content from the the notification request's, modify its properties, and then send it along in the completion closure:

class NotificationService: UNNotificationServiceExtension {
  override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    guard let asteroidID = request.content.userInfo["asteroid-id"] as? String else { return }
    guard let mutableContent = request.content.mutableCopy() as? UNMutableNotificationContent else { return }

    AsteroidService.details(asteroidID: asteroidID) {
      mutableContent.body
        .append(" Estimated Mass: \($0.massInTons) tons")

      contentHandler(mutableContent)
    }
  }
}

Here we take advantage of the custom "userInfo" dictionary our server adds to the push notification payload. In this case, we're grabbing an asteroidID from it. We also grab a mutable instance of the notification's content so we can modify it later.

Then we load the details for the asteroid, and append a new piece of information to the notification's body, the estimated mass.

Finally, we send the modified content along in the contentHandler.

We don't need it here, but there's one more function we can override, and it's all about expiration.

Unlike some other more generous extension points, Notification Service Extensions are given an extremely short window to perform work and complete by the system.

We can override:

override func serviceExtensionTimeWillExpire() {

}

This will tell us when the system is about to forcibly stop our extension, if this happens, the system will display the notification using whatever push notification payload our server sent through, with no modifications.

In our example above, we lean on this fallback behavior, so we didn't need to override this function.

Finally, we'll push an example notification through using Knuff, (covered in Bite #177), and try this all out:

Success!

Weekly Sponsor: Bugsee 🐞

Thsi week we're welcoming back one of our favorite sponsors, it's Bugsee! Allowing our users to report bugs is a crucial part of building our app. We can release builds through TestFlight, but what about allowing our users to report issues they encounter? Today we'll check out Bugsee's SDK and how it can help us super-charge this process. Let's take a look.

What is Bugsee?

Bugsee is a free SDK. It gives us truly effective debugging. It changes how we receive bug and crash reports from our users. We're going to be using it on iOS, but it works just as well on Android and on the Web!

How Does it Work?

After we integrate Bugsee into our app, our users can simply snap a screenshot to report a bug. (We can also configure different methods of reporting like shaking the device, etc.)

Bug reports include video of the last minute, full events log, console logs and network traffic logs along with all environment details.

For crash reports, Bugsee will include the full video leading up to the crash, including the reason, method, as well as the file and number that caused the crash.

Bugsee's viewer will allow us to review the video synchronized with all system events and logs. We can essentially "play back" a complete recreation of what was going on right before a crash or bug report occurred. Super neat!

Installation

Bugsee is straightforward to install, it's just one line of code (and it passes all App Store checks):

Bugsee.launchWithToken("app_token_goes_here")

Bugsee also integrates with our bug tracking systems, dramtically speeding up QA and debugging round trips, saving hours for both engineering and QA teams alike.

Bugsee doesn't impact the performance of our UI, and doesn't consume network bandwidth (no streaming). The SDK also automatically obscures secure fields in our app. Nice!

Mobile developers have needed a complete solution like Bugsee for quite some time. Signup right now at bugsee.com/jake for a free lifetime account.

A huge thanks to Bugsee for sponsoring this week's Bites!

Playing audio is an important part of many apps. One common trick is to fade in the volume of audio playback so we don't surprise or startle the user. This year, Apple has made this much simpler to implement using AVAudioPlayer. Let's take a look.

First we'll set up a standard AVAudioPlayer, and begin playing it at a volume of 0:

guard let asset = NSDataAsset(name: "alarm") else { print("Error Loading Audio."); return }

let player: AVAudioPlayer

do {
  player = try AVAudioPlayer(data: asset.data)
} catch { print("Error Playing."); return }

player.volume = 0
player.numberOfLoops = -1

player.play()

At this point the audio is playing but we can't hear it.

Before we check out the new feature, let's review the "old" way we might do this.

Before iOS 10, macOS 10.12, and tvOS 10, fading this audio in was, well, let's just call it "verbose":

func fadeInPlayer() {
  if player.volume <= 1 - fadeVolumeStep {
    player.volume += fadeVolumeStep
    dispatchAfterDelayHelper(time: fadeVolumeStepTime) { fadeInPlayer() }
  } else {
    player.volume = 1
  }
}

fadeInPlayer()

Recursive functions, GCD delays, manually managing state. Yuck.

Thankfully, there's now a better way.

Here's all it takes:

player.setVolume(1, fadeDuration: 1.5)

This single line of code will fade in the audio from our initial volume of 0 up to 1 over a period of 1.5 seconds.

🙌

Neat!

Xcode Project folders can be a messy place. Today we'll check out a tool from the folks at Venmo that can help us tidy up called Synx. Let's take a look.

At its core, Synx's main purpose is to reorganize the files and folders in our Xcode project folder to match the groups and structure we've setup inside Xcode's Navigator pane.

We can start by organizing a project full of content:

Then we'll install Synx:

gem install synx

Then we can simply head into our project's directory and run the main command:

synx ./OCMock.xcodeproj

Synx will work its magic and re-organize our files on disk, creating directories and moving files as needed to make things match the groups in our project.

Additionally, we can also use Synx to remove files no longer referenced in our project, like this:

synx --prune ./OCMock.xcodeproj

We're also provided a couple arguments to help us control how Synx behaves.

We can exclude files:

synx --exclusion /OCMockTests ./OCMock.xcodeproj

Last but not least, for those of us who like to manually sort our files by concept rather than name, we can disable sorting:

synx --no-sort-by-name ./OCMock.xcodeproj

Choosing how to organize projects can be a very subjective and personal choice, this is just one approach. Always use whatever works best.

Learn more about Synx at git.io/synx.

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!

Page 1 of 31