Active Filters: Text

Topics

#309: UIFontMetrics 📐

Topics

Happy WWDC 2017! Today we're beginning our look at the incredibly large list of updates and improvements announced this week with UIFontMetrics. Let's jump in.

We'll start with the problem we're trying to solve. It's all about Dynamic Type.

Our users can adjust their preferred Dynamic Type value in Settings.app to display the text in our apps larger and more prominently.

This works great when we want to use the system default font, since we can simply write some code like this:

let font = UIFont.preferredFont(forTextStyle: .headline)

This will return a UIFont that is appropriate for the given text style, adjusted by size and weight to match the user's Dynamic Type setting.

But what about custom fonts? In the past this was a bit cumbersome and we often had to resort to ugly hacks.

Enter UIFontMetrics! We can use this new type in iOS 11 to ask the system to scale our font size for us:

let headlineMetrics = UIFontMetrics(forTextStyle: .headline)
let fontBeforeScaling = UIFont(name: "Chicago", size: 16.0)
let font = headlineMetrics.scaledFont(for: fontBeforeScaling)

Neat!

There's always one more fun trick tucked away in UIFontMetrics, and that is scaling arbitrary values. This is great for helping us size our UI elements (for example buttons or headers) to accomodate dynamically sized fonts that live inside:

let headlineMetrics = UIFontMetrics(forTextStyle: .headline)
let heightBeforeScaling = 44.0
let height = headlineMetrics.scaledValue(forValue: heightBeforeScaling)

No more large text in tiny buttons. Very cool!

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!

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!

Let's draw some strings! There's plenty of text-drawing capabilities in iOS and OS X, with full frameworks like Text Kit (or the lower-level Core Text) dedicated to the task. Today, we'll start looking at these capabilities by using Core Graphics and UIKit to draw a multiline string.

Sometimes we need to draw text ourselves, “manually”. This can be helpful when optimizing for scrolling performance, or when complete customization is necessary.

First we'll need a view to draw into, we'll make a new UIView, and override drawRect.

Next, we'll start implementing our drawRect function with a few variables. We'll choose a font, then setup a paragraph style to make our text multiline, with some tall lines.

We'll also setup some drawing options to tell the system we want multiline text:

let textFont = UIFont(name: "Avenir Next", size: 17.0)!
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineHeightMultiple = 1.2
paragraphStyle.lineBreakMode = .ByWordWrapping

let drawingOptions: NSStringDrawingOptions = [
  .UsesLineFragmentOrigin, .UsesFontLeading]

We'll save these off into a textAttributes dictionary. Next, we'll need to figure out what rectangle we'll be drawing our text into.

We'll use the boundingRectWithSize function to find out how tall it will be when constrained to the width of our view:

let textRect = (text as NSString)
  .boundingRectWithSize(
    CGSizeMake(bounds.size.width, CGFloat.max),
    options: drawingOptions,
    attributes: textAttributes,
    context: nil)

Then we'll just pass that calculated textRect into a call to drawWithRect:

(text as NSString).drawWithRect(textRect,
  options: drawingOptions,
  attributes: textAttributes,
  context: nil)

Topics

#54: Dynamic Type Basics 📄

Topics

Dynamic Type is a system introduced back in iOS 7 with the intention of unifying and simplifying how users change their preference for how big text should be on the screen. While the user choice is simple, under the hood Dynamic Type earns its name by dynamically adjusting things such as spacing between characters and character weights to make text as readable as possible.

Let’s take a look at how to add simple Dynamic Type support.

Text Styles

The main way you interact with Dynamic Type is through the built-in set of text styles. As of iOS 9, 9 styles are available to choose from:

  • UIFontTextStyleTitle1
  • UIFontTextStyleTitle2
  • UIFontTextStyleTitle3
  • UIFontTextStyleHeadline
  • UIFontTextStyleSubheadline
  • UIFontTextStyleBody
  • UIFontTextStyleFootnote
  • UIFontTextStyleCaption1
  • UIFontTextStyleCaption2

Retrieving Fonts

If you’re using the system font, all you need is:

UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)

For custom fonts, you’ll need to grab a font descriptor first, then create the UIFont manually:

let fontSize = UIFontDescriptor
  .preferredFontDescriptorWithTextStyle(UIFontTextStyleHeadline)
  .pointSize

let font = UIFont(name: "Avenir Next", size: fontSize)

Lastly, you'll want to update your UI when the user changes their preferences in Settings.app. Observe this notification and trigger a re-layout when it occurs:

NSNotificationCenter.defaultCenter().addObserver(self,
  selector: "preferredContentSizeChanged:",
  name: UIContentSizeCategoryDidChangeNotification, object: nil)