Text is a huge part of iOS apps. Today we'll look at one way we can polish up the text in our apps, by allowing it to naturally "flow" around objects. Let's begin.

We'll start with a "before" picture. Here we've got a basic setup with some text and an image in the top right corner. Nothing too fancy here, just a regular UITextView and UIImageView:

let imageView = UIImageView(image: UIImage(named: "steve"))

let textView = UITextView(frame: .zero)  
textView.text = "Here's to the crazy ones..."

This looks fine. But our text is needlessly narrow. It'd be great if we could make the text expand to the full width of the device. We'll change our Text View's layout so it does:

Well, we're getting closer. Now we just need a way to make the text "flow" or "wrap" around our image. We'll start by getting the frame of the image view, then we'll create a path from it:

let imagePath = UIBezierPath(rect: imageView.frame)

Finally, we'll set our new path as one of the exclusionPaths of our Text View's textContainer:

textView.textContainer.exclusionPaths = [imagePath]

Success! Since exclusionPaths is an Array, we can flow text around as many shapes as we want!

Validating data is usually one of the "chores" we encounter while building apps. It can be annoying and boring code to write, and can be a huge source of bugs. Today we'll check out a great library called FormValidatorSwift that can help us avoid all these issues. Let's take a look.

The core of FormValidatorSwift is built around checking if a String passes some sort of test to be deemed 'valid'.

We'll use individual Conditions or combined sets of Conditions called Validators. We'll attach these to controls like Text Fields and Text Views to allow them to be validated.

Let's try out the included ValidatorTextField to validate an email address:

let field = ValidatorTextField(validator: EmailValidator())

Then, we'll configure it to allow the user to begin entering an email address that's invalid like "hello@", and specify that it should only be validated when it loses focus.

field.shouldAllowViolation = true
field.validateOnFocusLossOnly = true

This sets us up perfectly for our final steps: Displaying the validation info.

First, we'll set the validatorDelegate:

field.validatorDelegate = self

Then, we'll implement one function to update the visual state of our field when the validation state changes:

func validatorControl(validatorControl: ValidatorControl, changedValidState validState: Bool) {
  guard let view = validatorControl as? UIView else { return }

  if validState {
    view.backgroundColor = UIColor.white
    errorLabel.hidden = true
  } else {
    view.backgroundColor = UIColor.red
    errorLabel.hidden = false
  }
}

And finally, we'll add one more function to display the actual reason that validation failed (if it failed):

func validatorControl(validatorControl: ValidatorControl, violatedConditions conditions: [Condition]) {
  var errorText = ""

  for condition in conditions {
    errorText += condition.localizedViolationString
  }

  errorLabel.text = errorText
  errorLabel.hidden = false
}

Last but not certainly not least, we can group our fields into a provided Form type, then ask if the whole thing is valid:

var form = ControlForm()

form.addEntry(field)
form.addEntry(anotherField)

print(form.isValid) // true

Learn more about FormValidatorSwift at git.io/formvalidator

Topics

#278: Saving Time With Source Editor Extensions 🤖🛠

Topics

We touched briefly on this topic back in Bite #273 with Generating Initializers. Today we'll take a look at a few additional Xcode Source Editor Extensions (Bite #239) that can help us save time while working. Let's begin.

First up, Equatables. We can use XcodeEquatableGenerator from Serg Dort to quickly generate the long list of property comparisons often required when adopting the Equatable protocol in our Swift Structs or Classes:

Whew! That used to take ages! Full installation instructions are here.

Next, Localization. We've all been on a project with no localization. Adding that first round of NSLocalizedString() calls can be a pain. No longer! Now we can use Localizer from Esteban Torres.

With it, we can select a line of code like this:

let _ = "Mission Control Panel"

Then, when we run Localizer's command, this line becomes:

let _ = NSLocalizedString("Mission Control Panel", comment: "Mission Control Panel")

Neat! Learn more about Localizer here.

Last but definitely not least, is XcodeWay by Khoa Pham. This one is a bit different in that it doesn't modify or generate any code, but rather lets us quickly open a bunch of commonly used directories, by adding them to our Editor Menu:

Nice! No more hunting around in Finder or Terminal for these.

Know of another neat Source Editor Extension? Send it along!

Weekly Sponsor: Zendesk 💡

We're welcoming back one of our favorite sponsors again this week, 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!

Topics

#277: MeasurementFormatter Basics 📐📏

Topics

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.

Xcode Source Editor Extensions are really starting to come into their own. Today we'll look at one that solves an age-old annoyance for Xcode users: Importing.

We've all been there. We're deep down in the depths of file, and we realize we need to import a module. We dutifully scroll all the way up, type the import, then scroll back down trying to find our place, and get back in "the zone". Yuck.

Let's try out a new Source Editor Extension (Bite #239) from Marko Hlebar called Import.

As the name suggests, it allows us to type an import Module statement anywhere, then press a keyboard shortcut and have the import fly to the top where it belongs.

Let's try it out.

After installing the extension, we'll import CoreGraphics.

Xcode's Autocomplete still works here to help us find the module we want to import. Neat!

By default the keyboard shortcut is Command + Control + P. This trigger is also available via the menu item: Editor > Import > ☝️.

Success! Now we don't have to lose our place, or our train of thought when we need to import something.

Learn more about Import (including full installation instructions) at git.io/import

Weekly Sponsor: Rollout 🚀

Delighted to welcome an great new sponsor this week, it's Rollout!

Rollout allows us to deploy code-level changes to native iOS apps, without waiting for App Store approval.

We can fix bugs, diagnose issues, and patch code, all without resubmitting!

Let's try this out. After signing in to Rollout, we'll add our first app.

We'll give our app a name, then use Cocoapods to install the Rollout SDK.

Following the instructions, we'll install the pod like normal, then run a quick script to get our project ready.

Next we'll add 2 lines to our AppDelegate.m, to import the SDK and set it up:

#import <Rollout/Rollout.h>

and

- (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [Rollout setupWithKey:@"12345abcdefg"];

  return YES;
}

That's it, Rollout is now integrated into our app. Neat!

Now let's fast-forward a bit, to a time after we've shipped our imaginary app to the App Store. We get approved, and we're celebrating the launch and then 😣 doh! Something is causing our app to crash on launch. Yikes.

First we'll take a look at our project, and find the culprit. In our case, it looks like part of our code is throwing a non-fatal exception when we didn't expect it to. Yuck.

Not to worry though, because our app has Rollout inside.

We'll head to the Rollout dashboard and add a hotfix.

We'll select the "Surround Invocation with Try Catch" type, and test it out.

That fixes it! Now that things are fixed up, we can switch our hot patch to "Production" mode. (We could even release it gradually to only a percentage of our user base).

This kind of flexibility and freedom really does feel like magic. It's sort of like "insurance" knowing we won't be stuck waiting on Apple to get critical changes out to users. Neat!

Rollout supports Objective-C apps, Swift is supported (in beta) and will be fully released in November.

Last but certainly not least, Rollout is offering a sweet discount for LBOC readers: Use the code cocoa20 to get 20% off any paid plan for 6 months!

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

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

In some cases, Swift Initializers can be a pain to implement.

They can often be 100% boilerplate code, and feel like a chore to write.

This issue is at its worst when defining we're Swift Structs in a framework or module.

In Swift, Structs are automatically given a synthesised initialiser... with an internal modifier. Doh. 😣

This means the initializer is only accessible/visible inside the module we're defining it in.

This means we're stuck needing to manually add our own initializers for each of our Structs.

Creating and keeping these initializers up to date can add up to a ton of boilerplate code maintence. Yuck.

Today we'll try out an Xcode Source Editor Extension (Bite #239) from Bouke Haarsma that can help us write these automatically.

The extension works by taking the Swift properties defined within our selected text and converting them to parameters in a Swift Initializer.

After we install the extension, we can write up an example Struct in our code to try it out:

public struct Record {
  public let name: String
  public let type: UInt16
  public let unique: Bool
  public let ttl: UInt32
  var data: Data
}

We'll select just the properties, then select Editor > Generate Swift Initializer > Generate Swift Initializer.

Success! We just saved ourselves a ton of time, and typing. Neat!

Learn more about SwiftInitializerGenerator (including complete installation instructions) at git.io/swiftig

Page 7 of 38