Active Filters: Foundation

In Bite #287, we built a small extension to Date to allow us to format relative date strings like "10 minutes ago". We used a bunch of if statements and built our final String. This was a nice way to learn about working with DateComponents, but it turns out 🛎  Foundation actually provides a class to do something similar, and it's much more powerful.

Today we'll continue our look at Foundation's date and time-related functionality with DateComponentsFormatter. Let's dive in.

Much like its cousin DateFormatter (Bite #286), DateComponentsFormatter is all about converting a TimeInterval or DateComponents into a nicely formatted, human-readable String. (Note: It doesn't support parsing Strings yet. Conversions are a one way trip for now).

We'll start with a new formatter:

let formatter = DateComponentsFormatter()

Then we'll configure a few options:

formatter.unitsStyle = .full
formatter.allowedUnits = [.minute, .second]

And finally, we'll ask for a String describing an example TimeInterval:

formatter.string(from: 543.0) // "9 minutes, 3 seconds"

Like many Foundation types, DateComponentsFormatter is a super customizable powerhouse. Let's try a few more options:

formatter.unitsStyle = .abbreviated
formatter.string(from: 123.0)

// "2m 3s"
formatter.unitsStyle = .short
formatter.string(from: 123.0)

// "2 min, 3 sec"
formatter.unitsStyle = .spellOut
formatter.string(from: 123.0)

// "two minutes, three seconds"
formatter.includesApproximationPhrase = true
formatter.includesTimeRemainingPhrase = true
formatter.unitsStyle = .brief
formatter.string(from: 123.0)

// "About 2min 3sec remaining"

Neat. Fun Fact: If you've ever seen a progress bar or "time remaining" bar in iOS or macOS, you've seen a DateComponentsFormatter in action.

We've only been allowing .minutes and .seconds. Let's try allowing some different sets of units:

let formatter = DateComponentsFormatter()

formatter.unitsStyle = .full

formatter.allowedUnits = [.minute, .second]
formatter.string(from: 1234567.0)

// "20,576 minutes, 7 seconds"
formatter.allowedUnits = [.day, .hour, .minute, .second]
formatter.string(from: 1234567.0)

// "14 days, 6 hours, 56 minutes, 7 seconds"
formatter.allowedUnits = [.day, .minute, .second]
formatter.string(from: 1234567.0)

// "14 days, 416 minutes, 7 seconds"

Neat!

These are just the very basics. Look out for some more advanced DateComponentsFormatter fun soon.

In Bite #286, we looked at the basics of using DateFormatters to transform Dates into Strings.

Today we'll explore another common use case of Dates in our apps: "relative" date strings.

Think "2 days ago", "10 minutes ago", "Yesterday", and "Just now". Representing timestamps this way has become common, and it can help our users more quickly understand how old a piece of content is.

Let's add this functionality to our app by extending Date to give it a new computed property to do this.

We'll begin by adding a new File to our Xcode Project. We'll call it Date+Relative.swift. (The Type+SomeExtensionName convention is borrowed from Objective-C.

Then, we'll set up our extension:

extension Date {
  var relativelyFormatted: String {
    // TODO
  }
}

Nice. Next, we'll need a way to calculate how much time is in between now, and self (from the extended Date's point of view). We could do a bunch of ugly math, but we've got a better way!

This is a fantastic use case for Foundation's DateComponents type. It has a function that can calculate the difference between two Dates, and provide it to us in neatly (pre-calculated) components like days, months, hours, seconds, etc.

We'll begin our implementation like so:

let now = Date()

let components = Calendar.current.dateComponents(
  [.year, .month, .weekOfYear, .day, .hour, .minute, .second],
  from: now,
  to: self
)

We ask the current Calendar to please calculate the quantity of each of the components we passed in for the time between the Date we passed in as the from argument to the Date we passed into the to argument.

Beautiful. Now, the returned DateComponents type has a bunch of properties we'll use to build our relatively formatted date `String.

Finally, we'll inspect each date component (starting with most broad at the top) and return a nicely formatted String:

if let years = components.year, years > 0 {
  return "\(years) year\(years == 1 ? "" : "s") ago"
}

if let months = components.month, months > 0 {
  return "\(months) month\(months == 1 ? "" : "s") ago"
}

if let weeks = components.weekOfYear, weeks > 0 {
  return "\(weeks) week\(weeks == 1 ? "" : "s") ago"
}
if let days = components.day, days > 0 {
  guard days > 1 else { return "yesterday" }

  return "\(days) day\(days == 1 ? "" : "s") ago"
}

if let hours = components.hour, hours > 0 {
  return "\(hours) hour\(hours == 1 ? "" : "s") ago"
}

if let minutes = components.minute, minutes > 0 {
  return "\(minutes) minute\(minutes == 1 ? "" : "s") ago"
}

if let seconds = components.second, seconds > 30 {
  return "\(seconds) second\(seconds == 1 ? "" : "s") ago"
}

return "just now"

Doing things manually like this allows us to completely customize and control exactly how this relative date strings appear in our app.

This is fairly straightforward. We get a little fancy for the "days" component, allowing for "yesterday". We could also easily add support for future dates (i.e. "tomorrow") here as well.

All that's left to do is test it out. We can do that easily by constructing some Dates in the past, then printing our new property:

Date(timeIntervalSinceNow: 1).relativelyFormatted
// "just now"

Date(timeIntervalSinceNow: 12).relativelyFormatted
// "just now"

Date(timeIntervalSinceNow: 58).relativelyFormatted
// "58 seconds ago"

Date(timeIntervalSinceNow: 123).relativelyFormatted
// "2 minutes ago"

Date(timeIntervalSinceNow: 1234).relativelyFormatted
// "20 minutes ago"

Date(timeIntervalSinceNow: 12345).relativelyFormatted
// "3 hours ago"

Date(timeIntervalSinceNow: 123456).relativelyFormatted
// "yesterday"

Date(timeIntervalSinceNow: 1234567).relativelyFormatted
// "2 weeks ago"

Date(timeIntervalSinceNow: 12345689).relativelyFormatted
// "2 months ago"

Date(timeIntervalSinceNow: 123456890).relativelyFormatted
// "3 years ago"

Date(timeIntervalSinceNow: 1234568901)
  .relativelyFormatted // "39 years ago"

Date(timeIntervalSinceNow: 12345689012)
  .relativelyFormatted // "391 years ago"

Topics

#286: DateFormatter Basics 📆

Topics

Formatting dates and times is one of those common tasks we all have to do in almost every app. Today we'll take a look at how to use Foundation's solution for this: DateFormatter.

DateFormatters are incredibly powerful. Their core purpose is transforming Dates into Strings and Strings into Dates. They handle things like localization for us under the hood. Let's try one out.

We'll create a new formatter:

let formatter = DateFormatter()

Then we'll need to set a "format" on it. This is a string of characters that represent the date we're going to try to parse or render. Often these appear as one or more repeated series of letters like:

formatter.dateFormat = "MMM yyyy"

To see a rendered date string using this format, we can ask for one like this:

formatter.string(from: Date()) // "Jan 2017"

We can play around with the format for different results:

formatter.dateFormat = "MMMM yy"
formatter.string(from: Date()) // "January 17"

Neat. Let's try going the other way. We'll pass in a string, and ask our DateFormatter to parse it into a Date for us.

formatter.dateFormat = "MMMM yy"
formatter.date(from: "February 28")

// "Feb 1, 2028, 12:00 AM"

Very cool.

A couple of pro tips before we go:

The first is that DateFormatters have historically been heavy-weight objects to create. Performance has definitely improved over the years, but if we can, it's probably a good idea to keep one around instead of creating on the fly each time we need it. (For example we wouldn't want to be creating a new DateFormatter inside a UITableView or UICollectionView "cell for row" style delegate function).

The second is that these format strings are opaque and hard to understand at a glance. To solve this, friend of the show (and creator of the awesome NSScreencasts) Ben Scheirman has created a site called nsdateformatter.com.

There we can not only find easy, glance-able examples and references for all the different date format tokens, but we can also test out formats right there in the browser! Super handy.

We've only scratched the surface of what Foundation is capable of when it comes to Dates. Tune in tomorrow to learn more.

Today we're going to continue our look at the new Measurement functionality in Foundation. (Bite #276). MeasurementFormatter offers some great ways to format and print Strings of our Measurements. Let's take a look.

First we'll make define some units to work with. One of the most common ones we'll use in our apps is Length (for example when displaying how "far away" something is).

let distance = Measurement(value: 1.4, unit: UnitLength.kilometers)

Next, we'll make a formatter:

let formatter = MeasurementFormatter()

We'll leave the locale setting alone, which causes it to use the current locale of the device. In our case, en_US.

Finally, we'll print the formatted String.

print(formatter.string(from: distance))

Nice. Now we can start to try out some of the options. Let's start with the *style, we have three choices:

formatter.unitStyle = .short // "0.87mi"

formatter.unitStyle = .medium // (the default) "0.87 mi"

formatter.unitStyle = .long // "0.87 miles"

We'll use the .long style.

By default, our device locale being en_US is causing the distance to formatted with "miles" instead of "kilometers" (our input value). We can change this with unitOptions:

formatter.unitOptions = [.providedUnit]

// "1.4 kilometers"

We can even drop down and configure the NumberFormatter (Bite #182) that the MeasurementFormatter uses when formatting the numerical part of our Measurements:

measurementFormatter.numberFormatter.maximumFractionDigits = 0

// "1 mi"

Last but not least, we can ask the formatter for a String representing only the name of some unit:

let formatter = MeasurementFormatter()
formatter.string(from: UnitLength.lightyears)

// "light yrs"

Neat!

MeasurementFormatter is just another in a long list of wonderful little gems tucked away inside Foundation. Know of another that should be covered here? Send it along!.

Topics

#276: Measurement Basics 📏📐

Topics

Apple added something pretty neat to Foundation this year: Measurements. We can represent units, convert them to other units, compare them, and even convert them into formatted strings. Let's dive in.

It all starts with a Measurement.

let fuel = Measurement(value: 184, unit: UnitVolume.liters)

There are tons of different categories of units such as Volume, Mass, Speed, Power, Length, and many more.

Once created, a Measurement can be converted into any of other units in its category:

print(fuel.converted(to: .gallons))
// "8.18933748259766 gal"

We can also perform mathematical operations and comparisons on Measurements:

let tripLegA = Measurement(value: 1.4, unit: UnitLength.lightyears)
let tripLegB = Measurement(value: 2.3, unit: UnitLength.lightyears)

let tripTotal = tripLegA + tripLegB

print(tripTotal) // "3.7 ly"
print(tripLegA > tripLegB) // "false"

Last but certainly not least, we can nicely format a measurement into a String. Apple provides a MeasurementFormatter type that is fully locale-aware. This allows us to easily display Measurements in our apps in whichever ways our users expect.

let formatter = MeasurementFormatter()
formatter.locale = Locale(identifier: "en_US")
formatter.string(from: tripTotal)

// "21,751,587,607,342.14 mi"

formatter.locale = Locale(identifier: "fr")
formatter.string(from: tripTotal)

// "35 005 700 000 000 km"

Foundation is such a powerhouse of functionality, it can be easy to miss great new additions like this. Next time, we'll learn more about formatting Measurements for display in our apps.

Attributed Strings are a fantastic way to work with rich, styled-text. (We've covered them in Bites #143 & #144 for the curious.) We've even looked at ways to improve upon how verbose the Foundation NSAttributedString API can be.

Today we'll check out another approach using a library from Eddie Kaiger called SwiftyAttributes. It dramatically improves how we work with Attributed Strings. Let's dive in.

Let's start by looking at how things work in vanilla Foundation:

let attributes: [String: AnyObject] = [
  NSFontAttributeName: UIFont(name: "AvenirNext", size: 16.0)!,
  NSUnderlineStyleAttributeName: NSNumber(value: NSUnderlineStyle.styleSingle.rawValue),
  NSForegroundColorAttributeName: UIColor.gray
]

let someString = NSAttributedString(string: "Hello World", attributes: attributes) 

The functionality is wonderful, but even in fairly standard usage thie code can prove a bit difficult to parse at a glance.

Now, let's create the same string, but let's use SwiftyAttributes's API:

let someString = "Hello World"
  .withFont(UIFont(name: "AvenirNext", size: 16.0)!)
  .withUnderlineStyle(.styleSingle)
  .withTextColor(.gray)

Neat!

Things get even nicer when we need to concatenate two NSAttributedStrings together. We've all had to do this before and it could have involved applying attributes to specific ranges, etc. Now, we can use a much more declarative and readable syntax:

let someString = "We can easily ".withFont(.systemFont(ofSize: 16)) +
                 "underline"
                   .withFont(.systemFont(ofSize: 16))
                   .withUnderlineStyle(.styleSingle) +
                 " certain words.".withFont(.systemFont(ofSize: 16))

Under the hood, SwiftyAttributes takes a pragmatic approach by extending both Swift's String type as well as NSAttributedString with a fairly comprehensive set of delicious, sugary functions:

func withFont(_ font: UIFont)
func withParagraphStyle(_ style: NSParagraphStyle)
func withTextColor(_ color: UIColor)
func withBackgroundColor(_ color: UIColor)
func withLigatures(_ ligatures: Ligatures)
func withKern(_ kernValue: Double)
func withStrikethroughStyle(_ style: NSUnderlineStyle)
func withUnderlineStyle(_ style: NSUnderlineStyle)
func withStrokeColor(_ color: UIColor)
func withStrokeWidth(_ width: Double)
func withShadow(_ shadow: NSShadow)
func withTextEffect(_ effect: String)
func withAttachment(_ attachment: NSTextAttachment)
func withLink(_ link: URL)
func withBaselineOffset(_ offset: Double)
func withUnderlineColor(_ color: UIColor)
func withStrikethroughColor(_ color: UIColor)
func withObliqueness(_ obliquenessValue: Double)
func withExpansion(_ expansion: Double)
func withWritingDirections(_ directions: [WritingDirection])

This allows us to get creative with how we mix and match the two types:

let someAttributedString = fromSomewhereElse()

let someString = someAttributedString + "\n\nNeat!".withTextColor(.brown)

Learn more about SwiftyAttributes at git.io/swiftyattributes

Today we'll look at a new library from Krzysztof Zabłocki called KZFileWatchers. It allows us to easily monitor local and remote files, running code when changes occur. Let's dive in.

Watching files for changes is a common technique used when creating "live" updating interfaces.

This is a popular technique among web developers. For example, it's now common to use a tool to "live reload" a web page whenever a CSS file is changed.

Live updating techniques can dramatically change how we work, allowing us to try out changes more rapidly.

KZFileWatchers helps us build this type of functionality by providing two simple tools:

FileWatcher.Local is for observing local file changes. (It can even cross sandbox boundaries for debug simulator builds, think "config files on the desktop").

FileWatcher.Remote is for observe remote file changes. We can place a file on a server somewhere, and KZFileWatchers will use the Etag headers and Last-Modified-Date. This even works with Dropbox. Neat.

Let's use a FileWatcher.Local to make a live updating label.

First we'll create a label:

let textLabel = UILabel()

Then we'll create our file if it doesn't already exist:

let filename = "label-content.txt"
let middle = FileWatcher.Local.simulatorOwnerUsername()
let path = "/Users/\(middle)/Desktop/\(filename)"

if FileManager.default.fileExists(atPath: path) == false {
  try! "Hello, world."
    .data(using: String.Encoding.utf8)?
    .write(to: NSURL.fileURL(withPath: path))
}

Finally, we'll wire up a watcher to our file's path, and update our label when the file changes:

let watcher = FileWatcher.Local(path: path)

try! watcher?.start {
  switch $0 {
  case .updated(let data):
    textLabel.text = String(
      data: data,
      encoding: String.Encoding.utf8
    )
  default: break
  }
}

We can use this technique to drive all sorts of systems. With this we can easily enable/disable features, change content of labels in our UI, or build a full fledged theming system that updates in "real time".

More info about KZFileWatchers can be found at git.io/filewatchers

We've covered GCD a few times back in Bites #85 & #35. This year, GCD is getting a huge overhaul that brings it into the modern world of Swift. Today we'll take a look at what's changed, let's dive in.

TLDR: Grand Central Dispatch now has a richly typed Swift 3 API.

Here's a basic usage example:

let queue = DispatchQueue(label: "com.jakemarsh.image-processing")

queue.async {
  let thumbnail = image.resize(to: thumbnailSize(for: image))

  DispatchQueue.main.async { imageView.image = thumbnail }
}

Here we create a new queue, and enqueue some image processing work on it. Then, we do the same thing, but back on the main queue, enqueueing the work of setting resized image on our imageView there.

That's just the beginning. Here's a few more examples of how to use the new "swifty" GCD:

Time & Delay:

let delayTime = DispatchTime.now() + .seconds(1)

DispatchQueue.main.after(when: delayTime) {
  print("test")
}

For seasoned GCD users, it may take some getting used to, but the new API is much safer and more intuitive to use in Swift. Neat!

Markdown is awesome. For those not familiar, read up on why Markdown is awesome in this fantastic post by Brett Terpstra.

When it comes to using Markdown on iOS and OS X, there's tons of options. Today we'll look at one popular solution from Simon Fairbairn called SwiftyMarkdown. It helps us translate Swift Strings containing Markdown into NSAttributedStrings. Let's take a look.

let md = SwiftyMarkdown(string: "# SwiftyMarkdown\nConverts *Markdown* files and strings into NSAttributedString")
md.attributedString()

It's that simple! SwiftyMarkdown will use some defaults for fonts, colors, etc. Customizing those is quite simple:

md.body.fontName = "AvenirNextCondensed-Medium"

md.h1.color = UIColor.redColor()
md.h1.fontName = "AvenirNextCondensed-Bold"

md.italic.color = UIColor.blueColor()

We can supply custom colors and fonts for each different tag, SwiftyMarkdown even supports inline links, neat!

More info about SwiftyMarkdown can be found at git.io/swiftymarkdown

Often when working with data from an API or third-party we'll need to manipulate or analyze it in a very specific way. In these cases, many of us simply search Google for "how to do the thing to a String in Swift". This usually works out fine, but what a bummer.

Today we'll check out a library from Andrew Mayne called SwiftString, which can help us here.

SwiftString is essentially a collection of a ton of String manipulation and analysis utilities. It has functions like this:

"<rock>blah</hardplace>".between("<rock>", "</hardplace>") // "blah"
"hello".isNumeric() // false
"hello7".isNumeric() // false
"31.0".isNumeric() // true
"-31.0".isNumeric() // true
"star wars".split(" ")[0] // "star"
"star wars"[0...1] // "st"
"#203: Simplifying Common String Operations".slugify() // "203-simplifying-common-string-operations"
"Some [string], *with* %junk in it.)".stripPunctuation() // "Some string with junk in it"
"yay &quot;strings&quot;".decodeHTML() // "yay \"strings\""

We might only need one or two of these functions in a single app, but thanks to SwiftString, we'll never have to write them ourselves again.

More info about SwiftString can be found at git.io/swiftstring

Page 1 of 3