Chapters

Hide chapters

iOS Apprentice

Eighth Edition · iOS 13 · Swift 5.2 · Xcode 11

Getting Started with SwiftUI

Section 1: 8 chapters
Show chapters Hide chapters

My Locations

Section 4: 11 chapters
Show chapters Hide chapters

Store Search

Section 5: 13 chapters
Show chapters Hide chapters

43. Polish the Pop-up
Written by Eli Ganim

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

The Detail pop-up is working well — you can display information for the selected search result, show the image for the item, show pricing information, and allow the user to access the iTunes product page for the item. You are done with the Detail pop-up and can move on to the next item, right?

Well, not quite… There are still a few things you can do to make the Detail pop-up more polished and user friendly.

This chapter will cover the following:

  • Dynamic type: Add support for dynamic type so that your text can dispaly at a size specified by the user.
  • Gradients in the background: Add a gradient background to make the Detail pop-up background look more polished.
  • Animation!: Add transition animations so that your pop-up enters, and exits, the screen with some flair!

The iOS Settings app has an accessibility option — under General ▸ Accessibility ▸ Larger Text — that allows users to choose larger or smaller text. This is especially helpful for people who don’t have 20/20 vision — probably most of the population — and for whom the default font is too hard to read. Nobody likes squinting at their device!

You can find this setting both in your device and in the Simulator:

The Larger Text accessibility settings
The Larger Text accessibility settings

Apps have to opt-in to use this Dynamic Type feature. Instead of choosing a specific font for your text labels, you have to use one of the built-in dynamic text styles.

Configuring for Dynamic Type

To provide a better user experience for all users, whether their eyesight is good or bad, you’ll change the Detail pop-up to use Dynamic Type for its labels.

➤ Open the storyboard and go to the Detail scene. Change the Font setting for the Name label to the Headline text style:

Changing the font to the dynamic Headline style
Changing the font to the dynamic Headline style

You can’t pick a font size when selecting text styles — the font size depends on the user and the Larger Text setting they use on their device.

➤ Set the Lines attribute to 0. This allows the Name label to fit more than one line of text.

Auto Layout for Dynamic Type

Of course, if you don’t know beforehand how large the label’s font will be, you also won’t know how large the label itself will end up being, especially if it sometimes may have more than one line of text. You won’t be surprised to hear that Auto Layout and Dynamic Type go hand-in-hand.

Control-drag to make a new constraint between two views
Hewvkuy-kcep sa togu e mor deyfrqoapf fudxaat wju doibc

The possible constraint types
Rbo sevnolvu qozzhfeidd fgnid

The new vertical space constraint
Vxu yex galwibos skuci secczteodg

Attributes for the vertical space constraint
Udlraciwiy dow hge todcufev stezo hurymdoufg

The pop-up shows different constraint types
Tjo saw-ug wbiwp xaxqilecq temxdkouwk tzbuc

The constraints for the Name label
Qxe jimwckoardl vat hbe Sere turog

Adding multiple constraints at once
Ewvovv merpovla diyfkquujbm uw awze

Converting the constraint to Greater Than or Equal
Bantaxyipm fxo duqsmvauwz mu Sfiayil Nyuk es Itaol

The text overlaps the other labels
Bzi cogc odavlazx fbe amnar buyodg

The Name label’s constraints in the Size inspector
Rge Gopu ruveg’f qihmydoebyg iw dni Quge oxhbutruz

Auto Layout for Artist Name

Let’s pin the Artist Name label. Again you do this by Control-dragging.

Auto Layout for Type

For the Type: label:

Auto Layout for Genre

Two more labels to go. For the Genre: label:

Auto Layout for Price button

There is one more thing to do. The last row of labels needs to be pinned to the price button. That way there are constraints going all the way from the top of the Pop-up View to the bottom. The heights of the labels plus the sizes of the Vertical Spacing constraints between them will now determine the height of the Detail pop-up.

The height of the pop-up view is determined by the constraints
Nce reogcw iy xqo civ-ox soid iy tefodxequl rd fle luwgzgoolkp

The text properly wraps without overlapping
Rmu poqf qfudedtp lzefc sithiet awogwuvnuhk

Testing Dynamic Type

➤ Close the app and open the Settings app. Go to General ▸ Accessibility ▸ Larger Text. Toggle Larger Accessibility Sizes to on and drag the slider all the way to the right. That gives you the maximum font size — it’s huge!

Changing the text size results in a bigger font
Bbugrayc fhi cipb bequ cenuzsl ag e besduh fecf

Stack Views

Setting up all those constraints was quite a bit of work, but it was good Auto Layout practice! If making constraints is not your cup of tea, then there’s good news: as of iOS 9, you can use a handy component, UIStackView, that takes a lot of the effort out of building such dynamic user interfaces.

Gradients in the background

As you can see in the previous screenshots, the table view in the background is dimmed by the view of the DetailViewController, which is 50% transparent black. That allows the pop-up to stand out more.

The GradientView class

➤ Add a new Swift File to the project. Name it GradientView.

What the GradientView looks like by itself
Rnet bja DsifauvhWeil teomj ceve lf ubdohf

import UIKit

class GradientView: UIView {
  override init(frame: CGRect) {
    super.init(frame: frame)
    backgroundColor = UIColor.clear
  }
  
  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    backgroundColor = UIColor.clear
  }
  
  override func draw(_ rect: CGRect) {
    // 1
    let components: [CGFloat] = [ 0, 0, 0, 0.3, 0, 0, 0, 0.7 ]
    let locations: [CGFloat] = [ 0, 1 ]
    // 2
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let gradient = CGGradient(colorSpace: colorSpace, 
                   colorComponents: components, 
                   locations: locations, count: 2)
    // 3
    let x = bounds.midX
    let y = bounds.midY
    let centerPoint = CGPoint(x: x, y : y)
    let radius = max(x, y)
    // 4
    let context = UIGraphicsGetCurrentContext()
    context?.drawRadialGradient(gradient!, 
      startCenter: centerPoint, startRadius: 0, 
      endCenter: centerPoint, endRadius: radius, 
      options: .drawsAfterEndLocation)
  }
}

Using GradientView

Putting this new GradientView class to work is pretty easy. You’ll add it to your own presentation controller object. That way, the DetailViewController doesn’t need to know anything about it. Dimming the background is really a side effect of doing a presentation, so it belongs in the presentation controller.

lazy var dimmingView = GradientView(frame: CGRect.zero)

override func presentationTransitionWillBegin() {
  dimmingView.frame = containerView!.bounds
  containerView!.insertSubview(dimmingView, at: 0)
}
view.backgroundColor = UIColor.clear
The background behind the pop-up now has a gradient
Dxe pulymtoagp nelavp hru nob-uv ney doj e zzupiukp

Animation!

The pop-up itself looks good already, but the way it enters the screen — Poof! It’s suddenly there — is a bit unsettling. iOS is supposed to be the king of animation, so let’s make good on that.

The animation controller class

➤ Add a new Swift File to the project, named BounceAnimationController.

import UIKit

class BounceAnimationController: NSObject, 
                         UIViewControllerAnimatedTransitioning {
  
  func transitionDuration(using transitionContext: 
       UIViewControllerContextTransitioning?) -> TimeInterval {
    return 0.4
  }
  
  func animateTransition(using transitionContext: 
                         UIViewControllerContextTransitioning) {
      
    if let toViewController = transitionContext.viewController(
               forKey: UITransitionContextViewControllerKey.to),
       let toView = transitionContext.view(
                       forKey: UITransitionContextViewKey.to) {

      let containerView = transitionContext.containerView     
      toView.frame = transitionContext.finalFrame(for: 
                                               toViewController)
      containerView.addSubview(toView)
      toView.transform = CGAffineTransform(scaleX: 0.7, y: 0.7)
      
      UIView.animateKeyframes(withDuration: transitionDuration(
        using: transitionContext), delay: 0, options: 
        .calculationModeCubic, animations: {
        UIView.addKeyframe(withRelativeStartTime: 0.0, 
             relativeDuration: 0.334, animations: {
          toView.transform = CGAffineTransform(scaleX: 1.2, 
                                                    y: 1.2)
        })
        UIView.addKeyframe(withRelativeStartTime: 0.334, 
             relativeDuration: 0.333, animations: {
          toView.transform = CGAffineTransform(scaleX: 0.9, 
                                                    y: 0.9)
        })
        UIView.addKeyframe(withRelativeStartTime: 0.666, 
             relativeDuration: 0.333, animations: {
          toView.transform = CGAffineTransform(scaleX: 1.0, 
                                                    y: 1.0)
        })
      }, completion: { finished in
        transitionContext.completeTransition(finished)
      })
    }
  }
}

Using the new animation controller

To use this animation in your app, you have to tell the app to use the new animation controller when presenting the Detail pop-up. That happens in the transitioning delegate inside DetailViewController.swift.

func animationController(forPresented presented: 
     UIViewController, presenting: UIViewController, 
     source: UIViewController) -> 
     UIViewControllerAnimatedTransitioning? {
  return BounceAnimationController()
}
The pop-up animates
Rmu dic-az ucuvuhiv

Animating the background

There’s no reason why you cannot have two things animating at the same time. So, let’s make the GradientView fade in while the pop-up bounces into view. That is a job for the presentation controller, because that’s what provides the gradient view.

// Animate background gradient view
dimmingView.alpha = 0
if let coordinator = 
   presentedViewController.transitionCoordinator {
  coordinator.animate(alongsideTransition: { _ in
	self.dimmingView.alpha = 1
  }, completion: nil)
}
override func dismissalTransitionWillBegin()  {
  if let coordinator = 
     presentedViewController.transitionCoordinator {
    coordinator.animate(alongsideTransition: { _ in
      self.dimmingView.alpha = 0
    }, completion: nil)
  }
}

Animating the pop-up exit

After tapping the Close button, the pop-up slides off the screen, like modal screens always do. Let’s make this a bit more exciting and make it slide up instead of down. For that you need another animation controller.

import UIKit

class SlideOutAnimationController: NSObject, 
                         UIViewControllerAnimatedTransitioning {
  func transitionDuration(using transitionContext: 
       UIViewControllerContextTransitioning?) -> TimeInterval {
    return 0.3
  }
  
  func animateTransition(using transitionContext: 
                         UIViewControllerContextTransitioning) {
    if let fromView = transitionContext.view(forKey: 
                      UITransitionContextViewKey.from) {
      let containerView = transitionContext.containerView
      let time = transitionDuration(using: transitionContext)
      UIView.animate(withDuration: time, animations: {
        fromView.center.y -= containerView.bounds.size.height
        fromView.transform = CGAffineTransform(scaleX: 0.5, 
                                                    y: 0.5)
      }, completion: { finished in
        transitionContext.completeTransition(finished)
      })
    }
  }
}
func animationController(forDismissed dismissed: 
  UIViewController) -> UIViewControllerAnimatedTransitioning? {
  return SlideOutAnimationController()
}
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now