Topics

#321: Opening Files from the Files App 📂

Topics

Apple's Files.app is a great way to interact with files on iOS.

Today, we'll begin taking a look at how our app can better integrate with it. First up, we're going to learn how to allow users to open files in our app, from the Files.app. Let's get started.

We'll begin by creating a new project with the Single View App template in Xcode. In the future, we'll look at how Document Picker apps work, but for simplicity, we'll use just a plain single view app this time.

Then, we'll head into our AppDelegate.swift file, and add a new function:

func application(
  _ app: UIApplication, 
  open inputURL: URL, 
  options: [UIApplication.OpenURLOptionsKey : Any] = [:]
) -> Bool {
  // TODO
}

This application(_:open:options:) function will be called when the user selects a file inside the Files app and presses the Share (  ) button on it, and selects our app in the UIActivityViewController that appears.

Pro Tip: We can also access this by long-pressing a file in the Files app and selecting Share from the menu:

Next we'll need to tell Xcode that our app supports the type of files we want to our app to be able to open. For this, we'll choose Text Files.

We'll click on our project at the top of the project navigator and click Info at the top. We'll find this screen:

We want that area labeled Document Types (0). We'll click the + button and enter in Text for the Name and public.text for Types.

(This public.text thing is part of a system called Uniform Type Identifiers, Apple uses these to identify specific kinds of file types. Read more here).

Then, we'll add a new key to our Info.plist to make our app work more smoothly. We'll right-click on the keys at the top and select Add Row, then add the key LSSupportsOpeningDocumentsInPlace. We'll make it a Boolean and set its value to YES:

This allows our app to work with the files users open, without needing to first copy them into its own sandbox directory.

Finally, we'll head back to our AppDelegate.swift file and fill out that function we added earlier:

func application(
  _ app: UIApplication, 
  open url: URL, 
  options: [UIApplication.OpenURLOptionsKey : Any] = [:]
) -> Bool {
  let needTo = url.startAccessingSecurityScopedResource()

  do {
    let data = try Data(contentsOf: url)
    let contents = String(
      data: data,
      encoding: String.Encoding.utf8
    )

    print(contents)
  } catch(let error) { print(error) }

  if needTo {
    url.stopAccessingSecurityScopedResource()
  }

  return true
}

First we setup a do/catch block so we can catch and print any errors that might happen during all of this. Then we call a special function on our inputURL.

Since, we're opening in place, iOS has gives us what Apple calls a "security scoped" URL. (Read more about them here).

First we have to tell the system when we begin using a URL, saving the Bool value that url.startAccessingSecurityScopedResource() returns. If that Bool is true, then we'll also need to call stopAccessingSecurityScopedResource on our URL once we're finished with it.

After that we simply read the Data at the URL, then put it into a String so we can print it out.

Now we can Build & Run the app on our device, open the Files.app, choose an .txt file, share it, and our app appears in the Share sheet:

Tapping our app's icon opens it, and runs our code to print out the contents of the file, neat!

That's all for today. Shout out to friend of the show Casey for inspiring the idea for this Bite!

Have an idea for a Bite? Send it in!

Weekly Sponsor: Nevercode 🤖🛠

We're welcoming back a great sponsor this week, it's Nevercode!

Continuous integration can be one of the best ways to build solid mobile apps. Nevercode delivers an awesome implementation of it for iOS, Android, Cordova, Ionic, React Native, and even Flutter-based mobile apps.

Nevercode enables developers to build, test and deploy native and cross-platform mobile applications automatically without any human interaction or any configuration headaches.

Setting up a new project is easy thanks to Nevercode's awesome automatic configuration functionality.

Nevercode will build our project for every commit we push, and run all of our Unit tests and UI tests, each time we push a new commit.

They also allow us to enable automatic build distribution. This can help our team or clients always stay up to date with how our app is progressing. They support all sorts of deployment services, including Apple's TestFlight.

One of the coolest things about Nevercode is their support for projects using Google's new Flutter framework, read more about that in their (super helpful) documentation here. They already have an extensive base of Flutter CI testers and the final version is entirely engineered by Flutter enthusiasts. Neat!

Nevercode actually performed some analysis on more than 75,000 builds calculated that they save up to 20% of a developer's work day. That's about 10 weeks a year! That's the true power of continuous integration and continous deployed with Nevercode. Read more about this analysis here.

Also be sure to check out their blog for tons of great resources for mobile app development.

You can try Nevercode for free for 14-days right now.

Topics

#320: Counting Enum Cases with CaseIterable 🐤

Topics

Back in Bite #292 we looked at Sourcery and how we could use it to add handy properties like count to our Swift enums. Sourcery is still just as awesome but Swift 4.2 actually removed the need for using it in this... case. 🤭 Let's take a look.

We'll start with the problem we're trying to solve. Let's imagine a simple enum:

enum Spaceships {
  case tantiveIV
  case ghost
  case falcon
}

We'll be adding to the list of cases in our enum frequently as new spaceships are added, and we'd like to display the number of spaceships in our app. We could extend our enum, adding a count property:

extension Spaceships {
  static var count: Int {
    return ([
      .tantiveIV,
      .ghost,
      .falcon
    ] as [Spaceships]).count
  }
}

Maintaining this code will not be fun. As we add new enum cases, we'll need to never forget to always also update this extension property's implementation as well. Yuck.

In Swift 4.2 we can solve this quite elegantly, by ditching our extension and instead conforming our enum to the new CaseIterable protocol:

enum Spaceships : CaseIterable {
  case tantiveIV
  case ghost
  case falcon
}

Now, we can access the number of spaceships anywhere in our code:

print(Spaceships.allCases.count)

Neat!

We're using count for simplicity here, but as the name suggests, the allCases static property that CaseIterable adds to our enums of course allows us to easily iterate through our enum's cases.

That's all for today, have a question or idea for a Bite? Send it to hello@littlebitesofcocoa.com.

Weekly Sponsor: Nevercode 🤖🛠

We're welcoming back a great sponsor this week, it's Nevercode!

Continuous integration can be one of the best ways to build solid mobile apps. Nevercode delivers an awesome implementation of it for iOS, Android, Cordova, Ionic, React Native, and even Flutter-based mobile apps.

Nevercode enables developers to build, test and deploy native and cross-platform mobile applications automatically without any human interaction or any configuration headaches.

Setting up a new project is easy thanks to Nevercode's awesome automatic configuration functionality.

Nevercode will build our project for every commit we push, and run all of our Unit tests and UI tests, each time we push a new commit.

They also allow us to enable automatic build distribution. This can help our team or clients always stay up to date with how our app is progressing. They support all sorts of deployment services, including Apple's TestFlight.

One of the coolest things about Nevercode is their support for projects using Google's new Flutter framework, read more about that in their (super helpful) documentation here. They already have an extensive base of Flutter CI testers and the final version is entirely engineered by Flutter enthusiasts. Neat!

Nevercode actually performed some analysis on more than 75,000 builds calculated that they save up to 20% of a developer's work day. That's about 10 weeks a year! That's the true power of continuous integration and continous deployed with Nevercode. Read more about this analysis here.

Also be sure to check out their blog for tons of great resources for mobile app development.

You can try Nevercode for free for 14-days right now.

Last week's introduction of the new iPad Pro brought us some fantastic improvements to the Apple Pencil. One of those improvements was the ability for the Apple Pencil to recognize simple gestures. Today we'll look at how to work these in our code. Let's dive in! 🏊‍♀️

First, let's cover the new system-level options for Apple Pencil. If we pair an Apple Pencil with our new iPad Pro by attaching it to the new magnetic inductive charger on the edge, we'll see a new item in our Settings.app for the Pencil.

Apple has provided a way for us to read these settings inside our own apps. This is through the UIPencilInteraction class's preferredTapAction class variable. This returns an enum representing the user's current selection for how the double-tap Pencil gesture should behave.

Apple recommends we make the default behavior of our apps match whatever the user has selected in Settings.app.

We'll begin by setting our view controller as the delegate of a new UIPencilInteraction object, then adding that new interaction object to our view controller's view.

class ViewController: UIViewController, UIPencilInteractionDelegate {
  init() {
    super.init(nibName: nil, bundle: nil)

    let pencilInteraction = UIPencilInteraction()
    pencilInteraction.delegate = self

    view.addInteraction(pencilInteraction)
  }

  // ...
}

Then, we'll implement UIPencilInteractionDelegate's one function: pencilInteractionDidTap(_:):

class ViewController: UIViewController, UIPencilInteractionDelegate {
  // ...

  // UIPencilInteractionDelegate

  public func pencilInteractionDidTap(_ interaction: UIPencilInteraction) {
    print("pencil double tapped!")
  }

  // ...
}

That function will be called anytime the user double-taps on their Pencil. Neat!

Next, we'll add some handling for the system setting:

public func pencilInteractionDidTap(_ interaction: UIPencilInteraction) {
  switch UIPencilInteraction.preferredTapAction {
    case .ignore: return
    case .showColorPalette: showColorPallet()
    case .switchEraser: change(tool: .eraser)
    case .switchPrevious: changeToPreviousTool()
  }
}

Some apps don't have tools like erasers though, and some apps might not have "tools" at all. In these cases, we can define our own behavior to respond to a double-tap on the Pencil.

In our case we'd like to support both system and custom actions.

We'll add a preference (stored in UserDefaults) to our app for our users to optionally choose to ignore the system double-tap behavior, and add a guard statement to our function to look for it:

public func pencilInteractionDidTap(_ interaction: UIPencilInteraction) {
  guard UserDefaults.standard.bool(forKey: "customPencilActionsEnabled") == false else {
    doSomethingCustom()
    return
  }

  switch UIPencilInteraction.preferredTapAction {
  case .ignore: return
  case .showColorPalette: showColorPallet()
  case .switchEraser: change(tool: .eraser)
  case .switchPrevious: changeToPreviousTool()
  }
}

That's all for now, have a question or idea for a Bite? Send it to hello@littlebitesofcocoa.com.

Weekly Sponsor: Nevercode 🤖🛠

We're welcoming a brand new sponsor this week, it's Nevercode!

Continuous integration can be one of the best ways to build solid mobile apps. Nevercode delivers an awesome implementation of it for iOS, Android, Cordova, Ionic, React Native, and even Flutter-based mobile apps.

Nevercode enables developers to build, test and deploy native and cross-platform mobile applications automatically without any human interaction or any configuration headaches.

Setting up a new project is easy thanks to Nevercode's awesome automatic configuration functionality.

Nevercode will build our project for every commit we push, and run all of our Unit tests and UI tests, each time we push a new commit.

They also allow us to enable automatic build distribution. This can help our team or clients always stay up to date with how our app is progressing. They support all sorts of deployment services, including Apple's TestFlight.

One of the coolest things about Nevercode is their support for projects using Google's new Flutter framework, read more about that in their (super helpful) documentation here. They already have an extensive base of Flutter CI testers and the final version is entirely engineered by Flutter enthusiasts. Neat!

Nevercode actually performed some analysis on more than 75,000 builds calculated that they save up to 20% of a developer's work day. That's about 10 weeks a year! That's the true power of continuous integration and continous deployed with Nevercode. Read more about this analysis here.

Also be sure to check out their blog for tons of great resources for mobile app development.

You can try Nevercode for free for 14-days right now.

Topics

#318: Codable Enums 📠

Topics

In Bites #316 and #317, we began looking at Swift's new Codable (Encodable & Decodable) protocols. Today we'll continue by learning more about how Swift Enums work can work with these protocols. Let's get started.

First off, lets try a basic example. Here's an enum:

enum SpaceshipKind {
  case transport
  case freighter
  case fighter
}

If we simply do this:

enum SpaceshipKind : Codable {

We'll get an error: Type 'SpaceshipKind' does not conform to protocol 'Decodable'.

We can get around this by making our enum a "raw" value type like a String:

enum SpaceshipKind : String, Codable {

Nice. Since Strings are Codable, this works great. Same goes for Int and other basic Codable types.

This is a fine solution for simple use cases, but things get a little (alright, a lot) more involved when one or more of our enum cases has associated values.

Here's an example:

public enum ContentKind : Codable {
  case app(String)
  case movie(Int)
}

If we compile now, we get that same does not conform... error.

Let's fix that by extending our enum. First, since we'll be taking on more of the encoding and decoding logic ourselves, we'll need our own Swift Error type to throw when things go wrong.

extension ContentKind {
  enum CodingError: Error { case decoding(String) }

Then, we'll need to tell Swift the keys we'll be using to encode or decode our data. We'll use another enum for this, this one adopting the built-in CodingKey protocol:

extension ContentKind {
  enum CodingError: Error { case decoding(String) }
  enum CodableKeys: String, CodingKey { case app, movie }

At this point we simply have to implement two more functions:

init(from decoder: Decoder) throws
func encode(to encoder: Encoder) throws

First we want to add the ability to decode a new ContentKind enum from some encoded data, then we want to
enable encoding a ContentKind into some data.

Let's look at the implementation of decoding all at once for clarity:

init(from decoder: Decoder) throws {
  let values = try decoder.container(keyedBy: CodableKeys.self)

  if let bundleID = try? values.decode(String.self, forKey: .app) {
    self = .app(bundleID)
    return
  }

  if let storeID = try? values.decode(Int.self, forKey: .movie) {
    self = .movie(storeID)
    return
  }

  throw CodingError.decoding("Decoding Failed. \(dump(values))")
}

First we pull all the values out of the decoder's container.

Then we check for each of our two possible cases, configuring ourselves and returning if successful. Finally, if we didn't return successfully and reach the end, we throw one of those CodingErrors we defined earlier.

Neat.

Last but not least, lets implement encoding:

func encode(to encoder: Encoder) throws {
  var container = encoder.container(keyedBy: CodableKeys.self)

  switch self {
  case let .app(bundleID):
    try container.encode(bundleID, forKey: .app)
  case let .movie(storeID):
    try container.encode(storeID, forKey: .movie)
  }
}

This time we grab the encoder's container up front, then simply switch on ourselves to see which value to encode.

Now our associated value enum can be encoded and decoded all we want. Nice!

Pro Tip: Writing all of this by hand can get extremely tedious for anything more than a few simple cases. Try something like Sourcery (Bites #292, #294, and #295) to generate this more boilerplate-ish Codable implementation.

That's all for now, have a question or idea for a Bite? Send it to hello@littlebitesofcocoa.com.

Weekly Sponsor: Buddybuild 🤖🛠

We're very happy to welcome back one of our favorite sponsors again this week. It's buddybuild!

Long time readers will remember that buddybuild is a fantastic mobile continuous integration, delivery, crash reporting and feedback platform, all in one.

Thousands of development teams, like Slack, Meetup and The Washington Post love buddybuild because it's the easiest way to build, test and distribute their mobile applications.

It takes just minutes to set up, let's check it out!

We'll head over to buddybuild.com and click Get Started.

We'll sign up with Github (we could also use Bitbucket, GitLab, or sign up with email / password for repos on any other git server). Neat!

Next, buddybuild will let us choose a git repo to create our first project with.

 

Once we select the repo we want to build, buddybuild will get to work building our app for the first time.

After that build is complete, our app is set up on buddybuild - it's that easy.

buddybuild installs and configures the necessary web hooks for our project so new builds are triggered whenever code is pushed. It also scans your repo for any tests you might have written, and runs them reliably on the simulator and physical devices.

 

With buddybuild, we won't need to wait for App Store processing time or reviews before deploying to testers.

buddybuild's continuous deployment system can send our app to users instantly on every build, on a schedule that works for our team, or at the push of a button.

This kind of comprehensive Continuous Integration and Continuous Deployment workflow is a game changer for iOS and Android teams. It also means we can have our whole development workflow set up in just minutes. We can also infinitely customize and extend buddybuild so it exactly just the way we want.

Buddybuild's deployment service also handles the process of adding new users (and their devices) by automatically managing UDIDs, iOS provisioning profiles and signing identities. (Fantastic UX for both testers and devs!)

 

Only buddybuild goes even farther, and gives us a fully integrated feedback platform.

Once users have installed our app and are testing it out, they can send their feedback (along with important diagnostic details) by simply taking a screenshot.

buddybuild also has rich pull request support. We can even pair it up with Danger to automate (the usually tedious) code review processes.

If our team works with pull requests frequently, buddybuild can be configured to build the pull request, then merge, commit, and update the commit status in GitHub, Bitbucket and GitLab. This ensures we always know before when a pull request is safe to merge.

If we have Unit Tests or UI Tests for our app, buddybuild will consistently and reliably run them as part of every build and automatically display how much of our codebase is covered by our tests. Neat!

Should a test fail, buddybuild will provide us with a video replay of our test's run and detailed test results to quickly understand the cause of the failure. (Learn more about testing on buddybuild here.)

If our app ever crashes, buddybuild's Source Context functionality will trace back and highlight the exact line of offending source code that caused the crash, telling us which users were affected, and how many times the crash happened.

Each crash is also recorded with an Instant Replay - this is a video replay of our users' actions in the moments leading up to the crash. Never wonder about "steps to reproduce" again! Super cool.

Buddybuild is also a great community citizen, and has our backs as iOS developers. For example, they'll give us a heads up on any potential breaking changes in Xcode. Within 48 hours of any Xcode release (including betas!), buddybuild will automatically takes our most recent successful build, and build and run tests against it using the latest version of Xcode. Then it will email us the results. How cool is that?!

Last but certainly not least, buddybuild not only comes with built-in integrations with tons of services we all know and love like GitHub, BitBucket, GitLab, Slack, JIRA Pivotal Tracker, Slack, HipChat but it's also infinitely customizable to meet the exact needs of your development team!

Buddybuild is the real deal, give them a try today at buddybuild.com!

Topics

#317: Crafting Great Reverse-DNS Identifiers 🆔

Topics

Apple platforms make heavy use of "reverse-DNS" identifiers.

They appear everywhere from our Application's Identifier all the way down to the Dispatch Queues we create in our code.

These identifiers are somewhat opaque in a conceptual sense, and have been around in programming for many years.

We create or set them once, and sometimes reference them in our code, but the main idea is that they're a unique, human-readable way to identify some unique "thing". Their nature also offers the benefit of being unlikely to "collide" with one another.

The first thing we'll want to define is some kind of top level prefix to put on all our identifiers.

Fun Fact: Historically these identifiers were literally reversed-DNS. That is to say, to form one in code we might take our website's domain name, reverse it, then append our app's name (for example). These days, on Apple platforms, this isn't really a "thing" anymore. Now, we're usually given a string property or text field where we can supply just about any value we want.

This means that the common TLD's on the internet are also commonly used here. A company may begin all of their identifiers with com., while a non-profit may start theirs with org..

Next, we'll want to think of some way to group all of our identifiers, across all of our apps.

Often this is a domain-style version of our company or organization's name, or perhaps simply our own name, all lowercased, with no punctuation.

Pro Tip: Identifiers can contain numbers, letters, hyphens (-), and dot (.) characters. Bundle IDs can have hyphens, not underscores. They're also case-sensitive. (Thanks to reader @pointum for the tips here!)

This gives us something like com.littlebites.

Great. Now we can apply this wherever we need to identify things.

Here are a few examples:

Applications and Extensions

com.littlebites.reader - An identifier for an imaginary app to read Bites. We might be tempted to just use com.littlebites.app or something generic, but this way we leave ourselves open to the possibility of making other apps in the future.

In-App Purchase Identifiers

With these, we want to think about the complete set of In-App Purchases we plan on offering and create groups within them if possible.

com.littlebites.reader.subscriptions.monthly, com.littlebites.reader.subscriptions.yearly - Here we have an imaginary subscription plan In-App Purchase.

We know we want to offer other types of In-App Purchases in the future, so we've "grouped" all subscription purchases under a subscriptions. segment. Then we're able to add a shorter phrase denoting the type as the final segment.

App Groups

App Groups help us share data between our apps or our app's extensions.

group.com.littlebites.cache, group.com.littlebites.user - Apple encourages to begin these with the group.. This way we know right away what we're dealing with. Here we've created two app groups, one to share less-important cache data (images, etc.) between apps while we keep user data in it's own group.

That's all for now, have a tip or some advice about these identifiers that to share? Send it to hello@littlebitesofcocoa.com.

Topics

#316: Codable Dates 📠

Topics

In Bite 315 we started looking at the new Codable protocol in Swift 4. Today we'll learn how to work with Date types when encoding and decoding. Let's dive in.

We'll start with another simple struct:

struct Spaceship : Codable {
  var name: String
  var createdAt: Date
}

Then, we'll create one and encode it into JSON to see what it looks like:

let ship = Spaceship(
  name: "Skyhopper",
  createdAt: Date()
)

let encoder = JSONEncoder()

if let data = try? encoder.encode(ship) {
  print(String(data: data, encoding: .utf8)!)
}

This prints:

{
  "name":"Skyhopper",
  "createdAt":524735194.61138701
}

There's a lot more than meets the eye here though. That JSONEncoder we created has some incredibly helpful properties on it.

The one we want is .dateEncodingStrategy. Yep, it's a Swift enum with all sorts of goodies inside. Let's check them out.

We've already seen the default option, which is called .deferredToDate.

Another great one is .iso8601:

{
  "name":"Skyhopper",
  "createdAt":"2017-08-18T07:50:53Z"
}

Super neat to see that built in to the system in such a clean way.

Decoders have a similar property as well.

We've all had to decode some crazy date format from an HTTP API before. Now it's (hopefully) a little less painful.

Let's try decoding a funky format.

All we need to do is use the .formatted(DateFormatter) value for our JSONDecoder's .dateDecodingStrategy property.

We'll give it this JSON to decode:

{
  "name":"Skyhopper",
  "createdAt":"Friday, Aug 18, 2017"
}

Then, we'll set up our decoder and formatter:

let decoder = JSONDecoder()

let formatter = DateFormatter()
formatter.dateFormat = "EEEE, MMM d, yyyy"

decoder.dateDecodingStrategy = .formatted(formatter)
let data = jsonString.data(using: .utf8)!

try? decoder.decode(Spaceship.self, from: data)

Which decodes to our struct as expected:

Spaceship(
  name: "Skyhopper",
  createdAt: 2017-08-18 05:00:00 +0000
)

Neat!

That's all for now. Happy coding!

Page 1 of 38