Topics

#129: More UI Testing Tips 🚸

Topics

Today we'll look at a couple more tips to get the most out of UI Testing in Xcode. (Covered previously in Bites #30, and #124).

Are We in a Test?

It'd be great if our app could know when it's running inside of a UI Test, then behave differently. We'll start by setting a flag that we can check for later. We'll add a launchArgument before we launch our app in our tests' setUp functions.

let app = XCUIApplication()
app.launchArguments = [ "IS_UI_TESTING" ]
app.launch()

Then, we'll add a new helper function to our app's code. We could add this to a helper class, or just make it a global function:

func isUITesting() -> Bool {
  return Process.arguments.contains("IS_UI_TESTING")
}

We can use this function throughout our code to do things like hit mock servers during testing, or simulate a camera preview when capturing screenshots with snapshot. (Bite #110).

Dismissing System Alerts

We have to jump through a couple of hoops here. First we'll need to set up what's called a "UI Interruption handler", which will execute a closure when the UI is interrupted by, for example, an alert being shown:

addUIInterruptionMonitorWithDescription("Authorization Prompt") {
  $0.buttons["Allow"].tap()
  return true
}

app.tap()

Then, after our app presents the authorization prompt, we'll need to .tap() on it once before the UI Interruption handler will fire. Inside our handler, we'll accept the prompt then return true to tell Xcode we've handled the UI interruption.

Have a UI Testing tip or trick of your own you'd like share? Send it along to hello@littlebitesofcocoa.com!

NSURLQueryItem is a great little class that joined Foundation in iOS 8. It can help us compose NSURLs in a safer and more predictable way.

iOS and OS X developers have long become familiar with composing URLs in Cocoa. It can be… "interesting" at times.

We've all written a line or two of NSString-concatenation or stringWithFormat code to quickly create a URL. This works in a pinch, but we could easily introduce a bug by putting an & character in the wrong spot, or some other silly typo.

NSURLQueryItem can help! Let's look at how to use it along with NSURLComponents to compose an NSURL:

let components = NSURLComponents()

components.scheme = "https"
components.host = "api.spaceshipapp.com"
components.path = "/ships"

components.queryItems = [
  NSURLQueryItem(name: "start", value: "40"),
  NSURLQueryItem(name: "max_results", value: "20")
]

let requestURL = components.URL

NSURLComponents is quite a useful class on its own that can parse and assemble URLs based on the RFC 3986 standard.

So far we've only created components from scratch, but we could also get the components of an existing NSURL like this:

let components = NSURLComponents(
  URL: NSURL(string: "https://lboc.me?page=2")!,
  resolvingAgainstBaseURL: true
)

This is great for a few reasons. For example, instead of doing something silly like string replacing shudder to change a query parameter, we can instead operate on the components' queryItems array, then export the URL again by calling .URL. Additionally, with this technique, we can now more easily validate query parameters in URLs in our tests! Double-win. Neat!

Thanks to Matthew Bischoff for suggesting today's topic! Send your topic suggestion to hello@littlebitesofcocoa.com

Today we'll take our first look at ReactiveCocoa. It's a huge topic, so we'll start with why we might want to use it. Let's dive in. 🏊

Traditionally, we'd wire up the different components in our app declaratively. We'd use common Cocoa techniques like Delegates, KVO, NSNotificationCenter notifications, target/actions, etc.

At first our app might have just a text field and delegate. But then we add a button, and maybe a segmented control. Things can quickly get messy. We inevitably end up with code like this sprinkled throughout our view controllers:

if crewNameIsValid && crewRankIsValid && !ship.full {
  createButton.enabled = true
} else {
  createButton.enabled = false
}

We're also using a ton of different mechanisms to accomplish the same conceptual task: Updating our UI to reflect state changes. Managing all of this in a small app might not seem like a big issue, but in even moderately complex codebases, this can be a big source of bugs. 🐞

Enter ReactiveCocoa (also commonly called "RAC"). It's a library for iOS and OS X that helps us write code that works with streams of values over time. It also allows us to write code that's closer to how we think about our app while building it:

"The Create Crew Member button should only be enabled: if the crew name is valid, and a rank has been selected, and the ship is not already full."

For example, in RAC, we can get a reference to a signal producer, which will send along our field's text, every time it changes:

let crewNameValid = crewNameTextField
  .rac_textSignal()
  .toSignalProducer()
  .map { $0 as! String }

crewNameValid.startWithNext { text in /* do something with text */ }

We'll do this for our rank control too. Next, we can merge both of these signal producers into one, then use the values that signal producer sends along to set our createButton's enabled property.

let formValidSignal = zip(crewNameValid, crewRankValid).map { $0 && $1 }

ReactiveCocoa is a giant departure from how we've traditionally built our apps. Don't worry if it doesn't make sense right away. In the future, we'll dive deeper into RAC, and look at all the different ways we can use it.

More info about ReactiveCocoa can be found at git.io/RAC

In our ongoing quest to build better apps, it's important for us to keep up as new techniques and best practices emerge. One such technique that's recently been growing in popularity is the use of View Models. Today we'll look at how they fit into an app, and why we'd use them.

Traditionally, we'd use the Model View Controller technique to build our apps. Sounds great, but on iOS it usually ends up looking something like this:

This approach can often lead to gigantic UIViewController subclasses, with long lists of responsibilities: networking/data loading code, table/collection view delegate methods, UI interaction, UI layout, and so on.

That's where the Model View View Model approach comes in. Don't be fooled by the name, we still need view controllers. When applied on iOS, MVVM ends up looking something like this:

We'll add a new view model type in between our model and view controller. Our view model will take over loading data, as well transforming/processing that data. Then it will expose that transformed data as read-only properties.

struct SpaceshipViewModel {
  private var ship: Spaceship

  let displayName: String { "\(ship.squadron): \(ship.name)" }
  func loadShips(start: Int = 0) { ... }
}

With our new View Model, we now have a clear place to put things like networking, data persistence, and all our business logic. Our view controllers can now relax a bit and handle things like user input and size class changes. Next time, we'll look at how we can make our view controller automatically react to changes in the view model.

Author's Note: Interested in learning more about this? This introduction from Bob Spryn is fantastic. It covers everything from start to finish and does a great job explaining both the "why" and "how" along the way. Highly recommended reading.

Weekly Sponsor: Hired 🏒

Another huge thanks to folks at Hired for sponsoring this week's bites. Finding a good job can be a daunting task. Today we'll take a look at how we can use Hired to save our sanity, and find a great job in the process. Let's dive in.

We'll start by entering our email and answering some basic questions:

That's it. Hired will then work with 2,500 pre-screened companies (both big and small) in 12 major metro areas to try find us a great job. Software engineers and designers on Hired can get 5+ job offers in a single week. Each offer will include salary and equity details upfront.

If we get a job through Hired, they'll give us a $2,000 "thank you" bonus! If we use the link littlebitesofcocoa.com/hired to sign up, Hired will double that, giving us a $4,000 bonus when we accept a job!

More Reasons to Use Hired

Full time or contract opportunities are available
View offers and accept or reject them without talking to anyone.
Completely FREE + no obligations ever.

Hired is the real deal. They have solved some very real problems and fixed this historically messy process. Looking for a job? Don't waste time, sign up for Hired today so you can get back to work!

Today we're continuing our look at the Fastlane suite of tools with gym. It can help us build and package our app into an .ipa file ready for submission to Apple, or some other kind of distribution. Let's get started.

We'll start by installing gym:

gem install gym

Now we can build and export an ipa like this:

gym

Alright, have a great weekend everyone! Just kidding, while gym is great about asking for answers to things we don't specify such as what Xcode Scheme to use when building. It also allows us to specify just about any option we can imagine when running it on the command line. For example, let's build our app using its Workspace and the app-store scheme:

gym --workspace "Spaceships.xcworkspace" --scheme "app-store" --clean

Simple enough, we're telling gym to build using our desired Workspace and Scheme, and that we'd like to do a clean build.

gym has options for just about everything: bitcode, codesigning, provisioning, team id, and much more. It's extremely flexible but including all those command line options each time can be drag. Let's unlock more of the power of gym by creating what's called a Gymfile in our project's directory. This is just a plain text file, and looks like this:

scheme "Spaceships"
sdk "iphoneos9.0"
clean true

output_directory "./build" # where to put the ipa
output_name "Spaceships"   # what to call the ipa

Now we can run gym again from the command line and we don't need to specify all those options each time, nice! More info about gym can be found at git.io/gym

Topics

#124: More on UI Testing 🚸

Topics

We covered the basics of UI Testing in Xcode when it was first announced all the way back in Bite #30. It's a fantastic way to test our app's interface. Today we'll look at a few more UI Testing odds and ends. Let's dive in:

First, let's write a test to verify that one of our table views properly loads and displays its data. The data is loaded asynchronously (which should be mocked in our test, but that's a future Bite). We'll need to give our app a little time to load and render the data we're testing for. We can use an expectation to easily pull this off:

expectationForPredicate(
  NSPredicate(format: "count > 0"),
  evaluatedWithObject: app.tables.cells,
  handler: nil)

waitForExpectationsWithTimeout(5, handler: nil)
XCTAssertGreaterThan(app.tables.cells.count, 0)

Xcode will wait up to 5 seconds for the predicate we've passed in to be true (for there to be more than 0 cells in our table). After that the XCTAssert will be executed and evaluated.

We can change the simulated orientation of the device like this:

XCUIDevice.sharedDevice().orientation = .LandscapeLeft

Quite helpful for testing all that great new Adaptive UI code!

Finally, let's look at performing some interactions beyond simple taps. We can do things like long press an element:

app.buttons["+"].pressForDuration(0.5)

or perform simple pans using the convenience swipe functions:

app.tables["Crew"].swipeDown()

We can even execute more complex pan gestures using the pressForDuration(duration:thenDragToCoordinate:) function:

  let point = table.coordinateWithNormalizedOffset(CGVectorMake(0, 2)
  table.pressForDuration(0.5, thenDragToCoordinate: point)

Topics

#123: Playing Audio in the Background πŸ”Š

Topics

Sometimes we want to play audio in our apps. It might be a podcast, a song, or a voice memo. Usually, our users will expect this audio to keep playing if they press the home button on their device. Today we'll look at how to get this working. Let's get started:

First let's setup the boilerplate basic audio playback code:

func playAudioWithData(audioData: NSData) {
  do {
    self.player = try AVAudioPlayer(data: audioData)
  } catch let error as NSError {
    self.showPlaybackFailedErrorAlert(error)
    self.player = nil
  } catch {
    self.showGenericErrorAlert("Playback Failed.")
    self.player = nil
  }

  guard let player = player else {
    self.showGenericErrorAlert("Playback Failed."); return
  }

  player.delegate = self

  guard player.prepareToPlay() && player.play() else {
    self.showGenericErrorAlert("Playback Failed."); return
  }
}

func audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool) {
  do { try AVAudioSession.sharedInstance().setActive(false) } catch { }
  self.player = nil
}

(Making this code "safe" in Swift can get a little ugly. πŸ˜•)

Next, we'll add a function that we'll call before we begin playback that configures our app's shared AVAudioSession to be in the β€œPlayback” category, and then we'll set the audio session to be active.

  func prepareForPlaybackWithData(audioData: NSData) {
    do {
      try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)

      do {
        try AVAudioSession.sharedInstance().setActive(true)
      } catch let error as NSError {
        self.showPlaybackFailedErrorAlert(error)
      }
    } catch let error as NSError {
      self.showPlaybackFailedErrorAlert(error)
    }
  }

Finally, we'll head over to our project's Capabilities tab in Xcode and enable the "Audio, AirPlay and Picture in Picture" background mode.

Success! Now when we send our app to the background, its audio continues playing.

Topics

#122: Notification Actions πŸ“’

Topics

Today we're going to look at how we can improve our app's notifications by adding actions to them. Notification Actions let our users perform a task directly inside the system's notification banner. Let's get started.

We'll be working in an imaginary Groceries app. The app currently notifies users when they're running low on something. We're going to make things even better by letting the user re-stock their pantry right from the notification banner.

First, we'll create a couple of UIMutableUserNotificationActions:

let reorderAction = UIMutableUserNotificationAction()
reorderAction.title = "Order More"
reorderAction.identifier = "com.groceries.running-low.reorder"
reorderAction.authenticationRequired = false
reorderAction.activationMode = .Background

let remindLaterAction = UIMutableUserNotificationAction()
remindLaterAction.title = "Remind Me Later"
remindLaterAction.identifier = "com.groceries.running-low.postpone"
remindLaterAction.authenticationRequired = false
remindLaterAction.activationMode = .Background

These action objects describe the buttons the user will see when they expand the notification banner after it appears.

Next, we'll attach these actions to a notification category, then pass that category in when we register for user notifications:

let category = UIMutableUserNotificationCategory()
category.identifier = "com.groceries.running-low"
category.setActions([reorderAction, remindLaterAction], 
  forContext: .Default)

application.registerUserNotificationSettings(
  UIUserNotificationSettings(forTypes: [.Sound, .Alert, .Badge],
    categories: [category]))

Finally, we'll implement a delegate function to handle our actions:

func application(
  application: UIApplication, 
  handleActionWithIdentifier identifier: String?,
  forRemoteNotification userInfo: [NSObject : AnyObject], 
  withResponseInfo responseInfo: [NSObject : AnyObject],
  completionHandler: () -> Void
) {
  // handle the action, based on its identifier
  completionHandler()
}

Regular Expressions are a fantastic way to search through text. Apple provides support for them via the NSRegularExpression class. It has great support for matching, extracting, etc. Its API can be a bit verbose for simple matches though. Today we'll look at Regex, a tiny little library from Adam Sharp that makes writing regular expressions in Swift much more friendly and expressive. Let's check it out:

Regex has a bunch of great features, but at its core it allows us to take regular expression code like this:

let stringToMatch = "star wars"

let regex = try! NSRegularExpression(
  pattern: "star (wars|trek)",
  options: NSRegularExpressionOptions(rawValue: 0)
)

let isWarsOrTrek = regex.firstMatchInString(
  stringToMatch,
  options: NSMatchingOptions(rawValue: 0),
  range: NSMakeRange(0, stringToMatch.characters.count)
) != nil

...and turn it into something just a tad more readable:

Regex("star (wars|trek)").matches(stringToMatch)

Regex is backed by NSRegularExpression under the hood, and it does a great job of "swift-ifying" its API. In addition to simple Boolean checks, we can also use Regex in a few other rather Swifty ways. For example, pattern matching:

switch starThing {
case Regex("gate$"): print("dial the gate!")
case Regex("wars$"): print("the force is strong")
case Regex("trek$"): print("set phasers to stun")
default: break
}

Last but not least, we can easily grab any captured strings:

let starRegex = Regex("star( trek| wars|gate)")
if let captured = starRegex.match(inputString)?.captures.first {
  print("You chose: \(captured).")
}

More info about Regex can be found at git.io/regex

Page 25 of 38