Topics

#249: Enums in Swift 3 🐀

Topics

We've been looking at some the changes coming in Swift 3 recently in Bites #243 and #244. We'll continue our tour today by checking out what is new the world of Swift Enums. Let's dive right in.

We'll start with the obvious, in Swift 2 (and earlier) enums have been PascalCased. In Swift 3, they are camelCase.

Before:

enum PersonalityTrait {
  case Extraversion(multiplier: Float)
  case Agreeableness(multiplier: Float)
}

After:

enum PersonalityTrait {
  case extraversion(multiplier: Float)
  case agreeableness(multiplier: Float)
}

The Swift 3 migrator in Xcode 8 will prompt us to convert all of our enums to this new convention.

Additionally, all of Apple's frameworks have this new convention applied:

label.textAlignment = .right
label.baselineAdjustment = .alignCenters
label.lineBreakMode = .byWordWrapping

In Swift 2, there was some inconsistency about the requirements around when the "dot" in the "dot notation" was required.

In Swift 2, all of this was valid:

enum AdvertisementKind {
  case Small, Medium, Large, Giant

  func debugDescription() -> String {
    switch self {
      case Small: return "Small Ad"
      case .Medium: return "Medium Ad" // leading dot
    }

    if self == Large {
      return "Large Ad"
    }

    if self == .Giant {
      return "Giant Ad"
    }
  }
}

Note how enum cases are used interchangeable both with and without dots.

In Swift 3, this has been cleaned up and dots are now required when using this shorthand style to access enum cases. There's one exception and that is static functions on enums, which still infer self:

enum AdvertisementKind {
  case small, medium, large, giant

  static func randomKind() -> AdvertisementKind {
    return medium
  }

Watching Swift evolve in these ways is a great way to understand not just the syntactic structural changes being made, but also the reasoning and goals behind them. Those interested should definitely read over the proposal that prompted this change here.

The Swift language is always evolving. Bugs are being fixed, new proposals are implemented, etc. It'd be great if we could try out these features out as they're being implemented, without having to wait for official releases.

Today we'll take a look at how to download, install, and try out the latest Swift Toolchain in Xcode. Let's get started!

We'll begin by heading to https://swift.org/download/ and looking for the "Snapshots" section. Here we can also find many other preview releases, etc.

Click "Xcode" to download an installer:

We'll run open the installer, and complete its steps.

We can now open Xcode and select our new Toolchain.

We can also manage Toolchains in Xcode's preferences:

With our new Toolchain selected, all builds will use it, neat!

Getting involved with Swift's development is a great way to stay informed on where things are headed. We'll look at getting even more involved in future Bites!

This week we're welcoming back one of our favorite sponsors, 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.

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 or just an email/password).

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.

Now, we'll be able to trigger a new build (and run our tests with code coverage) at any time with a simple git push!

This works with repos on Github, BitBucket, GitLab or any other git server, neat!

 

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

Speaking of deployment, buddybuild's email (and Slack!) based deployment service can deploy instantly on every build, every night, or at the push of a button.

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.

If our app ever crashes, buddybuild 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 - to show you exactly what your users were doing in the moments leading up to the crash. Awesome!

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 comes with built-in integrations with tons of services we all know and love such as GitHub, BitBucket, GitLab, Slack, JIRA Pivotal Tracker, Slack, HipChat and more!

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

Today we'll be taking a look at another often overlooked, yet quite powerful tool in Core Graphics: CAEmitterLayer. It can help us create all sorts of interesting particle effects. We're going to use it to make some chocolate rain. Let's do it!

We'll start by creating a new emitter layer. (We'll need to do all of this inside some view we want to rain chocolate down upon).

var emitter = CAEmitterLayer()

Next, we'll configure its size, position, and shape so it looks essentially like a "rain bar" at the top of our view:

emitter.emitterPosition = CGPoint(x: frame.size.width / 2.0, y: 0)
emitter.emitterShape = kCAEmitterLayerLine
emitter.emitterSize = CGSize(width: frame.size.width, height: 1)

For the shape, we could have chosen a point, circle, rectangle, sphere, etc. We chose a line because we want the generated particles to "rain down" vertically.

emitter.emitterCells = (0..<5).map({ _ in
  let intensity = Float(0.5)

  let cell = CAEmitterCell()

  cell.birthRate = 6.0 * intensity
  cell.lifetime = 14.0 * intensity
  cell.lifetimeRange = 0
  cell.velocity = CGFloat(350.0 * intensity)
  cell.velocityRange = CGFloat(80.0 * intensity)
  cell.emissionLongitude = CGFloat(M_PI)
  cell.emissionRange = CGFloat(M_PI_4)
  cell.spin = CGFloat(3.5 * intensity)
  cell.spinRange = CGFloat(4.0 * intensity)
  cell.scaleRange = CGFloat(intensity)
  cell.scaleSpeed = CGFloat(-0.1 * intensity)
  cell.contents = UIImage(named: "bite")!.cgImage

  return cell
})

Finally, we'll do the bulk of the work by adding 5 CAEmitterCells to our emitter layer. (Hint: Want more particles? Add more cells.)

These cells are where we configure how particles get genreated and how they behave once they are on screen.

There's ton of examples of different techniques online and the docs have more indepth info on each property

Don't be scared by all the magic numbers. Entire books have been written about how to generate certain particles effects. Things can get quite complicated.

We'll finish up by adding our emitter layer as a sublayer of the layer of the view we're working in:

layer.addSublayer(emitter)

Success!

Looking for a library that encapsulates this technique into a handy reusable view? Check out SAConfettiView by Sudeep Agarwal

Topics

#246: UITextField B-sides πŸ“Ό

Topics

UITextField is one of the sort of "low key" hidden gems of UIKit. Today we'll look at some of the lesser-known things it can do and how to configure them. Let's dive in.

We'll begin by creating a text field and customizing its look and feel. First, instead of boring plain placeholder, let's spice things up a bit:

let placeholder = NSMutableAttributedString()

placeholder.append(
  AttributedString(
    string: "Spaceship ",
    attributes: [NSFontAttributeName : boldFont]
  )
)

placeholder.append(
  AttributedString(
    string: "Name",
    attributes: [NSFontAttributeName : font]
  )
)

textField.attributedPlaceholder = placeholder

Nice, now let's customize how our text field works.

Suppose we wanted to clear out its contents when a user tapped it. We simply set clearsOnBeginEditing to true.

Similarly, if we wanted to clear the contents not when the user tapped the field, but rather when the user began typing content, we can set clearsOnInsertion to true.

Let's add some more "padding" to our text field. This turns out to be a tricker than expected.

UITextField allows for customization of the rects it uses to draw text via subclassing:

class PaddedTextField : UITextField {
  override func textRect(forBounds bounds: CGRect) -> CGRect {
    return bounds.insetBy(dx: 16.0, dy: 8.0)
  }
}

Finally, we'd like allow users to fancy up (bold, italic, underline) the names of their spaceships. Turns out UITextField makes this super easy:

textField.allowsEditingTextAttributes = true

With attribute editing enabled, we now get this great editing rich-text UI for free. Neat!

Core Graphics and Core Animation are incredibly powerful tools with lots of useful components. Today we'll try out one of them, CATextLayer, and learn how it can help us create some fun and interesting visuals. Let's get started.

First, we'll need a view to mask. We'll create a UIImageView and use this great looking Apple TV marketing image to test things out.

let imageView = UIImageView(
  UIImage(named: β€œapple-tv-color-bars.png”)
)

imageView.sizeToFit()

Then, we'll need a CATextLayer. We'll configure it to so it has the same size as our imageView, and do some Core Graphics housekeeping:

let textLayer = CATextLayer()

textLayer.frame = imageView.bounds
textLayer.rasterizationScale = UIScreen.mainScreen().scale
textLayer.contentsScale = UIScreen.mainScreen().scale

Next, we'll configure the text layer to look the way we want. We'll set a font, alignment, and wrapping/truncation settings:

textLayer.alignmentMode = kCAAlignmentCenter
textLayer.fontSize = 42.0
textLayer.font = UIFont(name: "TrebuchetMS-Bold", size: 42.0)
textLayer.wrapped = true
textLayer.truncationMode = kCATruncationEnd

We'll give it some content to display:

textLayer.string = "Little Bites of Cocoa"

Finally, we'll set the text layer as the mask of our imageView's layer.

imageView.layer.mask = textLayer

Success!

Looking for a library that does all this for you (plus a bit more)? Check out Translucid by Lucas Ortis.

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!

Topics

#243: The Great Swift 3 Rename 🐀

Topics

We've been able to watch Swift 3 materialize through the Swift Evolution mailing list throughout the year, and with WWDC 2016 behind us, we have a pretty clear picture of what Swift 3 will be.

Today we'll start taking a look at what's new and changed in this release, and how it can help us build our apps. First up is the "Great Rename", Let's begin.

It all started with this proposal on Swift Evolution:
"Better Translation of Objective-C APIs into Swift".

When Swift was first announced, one of the huge selling points was how we could use the same Cocoa APIs we were already familiar with.

Unfortunately, this meant a lot of Objective-C baggage found its way into the Swift code we were writing.

Swift 3 aims to fix this by improving the way Swift imports Objective-C code.

Take this Swift 2 code:

path.addLineToPoint(somePoint)

The function is imported with a definition like this:

func addLineToPoint(_: CGPoint)

In Swift 3, this would instead be imported as:

func addLine(to point: CGPoint)

And we could use it like this:

path.addLine(to: containerTop)

Swift 3 removes the repetitive names of the types, and properly brings in a (now required) argument label for the first parameter.

Additionally, Swift 3 drops the NS prefix from Foundation types, so instead of:

var now = NSDate()

We'll now be writing:

var now = Date()

Many Foundation types also now get proper Swift let/var mutability semantics:

var now = Date()
now.addTimeInterval(60) // works

let now = Date()
now.addTimeInterval(60) // compiler error

Swift 3 also prepends the word β€˜is' to imported Objective-C Boolean properties, and converts enums to be lowercased:

path.isEmpty // was just path.empty in Swift 2
var formatter = NumberFormatter()
formatter.numberStyle = .spellOut

That's all for now, next time we'll continue looking at more Swift 3 changes and enhancements.

In Bite #241, we learned about all the great improvements to UICollectionView's cell lifecycle in iOS 10. Today we'll check out another iOS 10 improvement to UICollectionView: The prefetchDataSource! Let's get started.

UICollectionView gains a new property this year called prefetchDataSource. Just like the existing delegate and dataSource properties, we an simply set it to some object that implements the new UICollectionViewDataSourcePrefetching protocol.

This protocol is brand new in iOS 10, and requires we implement just one new function:

public func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath])

When this function is called, we can examine the indexPaths array we're passed in to know which cells are "coming up soon" and thus which cells we should probably begin loading the data for. The indexPaths will be ordered "ascending by geometric distance" which basically means the indexPaths that need to be displayed on screen soonest will be first.

Let's use this new functionality to let our UICollectionView pre-load the images that go in our cells, This way when they scroll on screen, the images will already be visible and won't have to "load in" in front of the user. Classy! 🎩

class SpaceshipsViewController : UICollectionViewController {
  override func viewDidLoad() {
    super.viewDidLoad()

    collectionView?.prefetchDataSource = self
  }
}

extension SpaceshipsViewController : UICollectionViewDataSourcePrefetching {
  func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
    ImagePreloader.preloadImagesForIndexPaths(indexPaths) // happens on a background queue
  }

  func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
    ImagePreloader.cancelPreloadImagesForIndexPaths(indexPaths) // again, happens on a background queue
  }
}

That's literally it. We make sure to implement the optional cancelPrefetchingForItems function so that we don't waste resources preloading images the user won't actually see.

UICollectionViewDataSourcePrefetching doesn't replace our existing data loading architecture/functionality. It's essentially a hint as to which cells will be displayed soon, and which ones we might want to begin loading content for.

Finally, just like in Bite #241, we can opt-out of this new prefetching behavior by either simply leaving prefetchDataSource set to nil, or by setting isPrefetchingEnabled to false.

Oh one more thing: All of this works exactly the same on UITableView too now, thanks to the (almost identical) new UITableViewDataSourcePrefetching protocol.

UICollectionView is one of the most important tools in our toolbox as iOS developers. Achieving smooth scrolling performance is a badge of honor for many of us. This year in iOS 10, Apple has made substantial changes to how cells make their way into collection views.

Today we'll take a look at these changes, and how we can take advantage of them in our apps. Let's dive in! 🏊

Here's the TLDR:

UICollectionView will now call cellForItemAtIndexPath: way before it used to. Sometimes the cells we return from that function won't even end up being displayed. We should proceed accordingly.

This dramatically improves scrolling performance as the system intelligently manages everything, giving us beautiful, buttery-smooth, 60fps scrolling. Neat!

Here's the slightly more wordy version:

In iOS 10, Apple has enhanced UICollectionView's ability to "pre-load" cells before they appear on screen.

Essentially, UICollectionView is now aware of the direction the user is scrolling, and will "look ahead" to cells that will be coming on to screen soon, call the appropriate existing delegate functions (like cellForItemAtIndexPath:) as needed.


Let's look at how the UICollectionViewCell lifecycle has changed in iOS 10. We'll start with how things are today, in iOS 9:

First, cellForItemAtIndexPath: is called, and we dequeue a cell. UICollectionView pulls it from the reuse queue, and calls prepareForReuse on it.

Next, we'll configure the cell to our needs. Setting the content of labels, etc.

Then, right before the cell is scrolled into view, willDisplayCell:atIndexPath: is called.

The user looks at the beautiful cell we've created until they grow tired of it, and eventually scroll it off screen.

At this point, we'll get a didEndDisplayingCell:atIndexPath: call, and our cell will re-enter the reuse queue.

If the user changes their mind and scrolls back up, the whole process starts over with cellForItemAtIndexPath: 😨


Now, how about in iOS 10? Next we'll look at how this process has improved:

In iOS 10, things are mostly the same, all the way up until the point where the cell goes off screen at the end.

Yes, didEndDisplayingCell:atIndexPath: is called as usual, but the cell is not immediately put into the reuse queue.

Instead, the system keeps it around for a bit. This way, if the user starts scrolling the other direction, the cell is already ready to be displayed on screen again. Very cool!


UICollectionView also has a new trick when working with multi-column layouts.

(This is where the β€œprefetching” bit comes in).

In these layouts, the system will optimize for scrolling performance by sending cells through their lifecycle sooner, at times determined intelligently by the system.

Cells will go through their lifecycle one at a time, each being created via an incoming call to cellForItemAtIndexPath:.

Later, once the entire "row" of cells is about to be scrolled on to screen, willDisplayCell:atIndexPath: will be called on each cell.

Apple describes cell pre-fetching as an "adaptive" technology. In other words, it will respond to how users are interacting with our apps.

For example, UICollectionView will try to look for "down time" when the user isn't scrolling (or at least isn't scrolling quickly) to prefetch some cells. If the user begins wildly scrolling very quickly, cell prefetching will stop until they slow down.


Finally, here's some general advice and info that we should consider when architecting our UICollectionView code going forward:

  • Set up our cells in cellForRowIndexPath:, try to keep the willDisplay/didEndDisplay callbacks light weight.

  • We should now expect some cells to be created (cellForItemAtIndexPath:) but never displayed.

If for some wacky reason we didn't want this wonderful new behavior, we can set our collection view's isPrefetchingEnabled property to false (it defaults to true in iOS 10).

These improvements are obviously really great, but what about actually doing the work of loading the data that gets displayed in our cells. Well, for that you'll have to come back for tomorrow's Bite!

Page 11 of 38