OS 7 raised the bar for iOS app design, especially when it came to on-screen effects.
One of the most significant changes was the use of blur throughout iOS 7, most notably in Notification Center and Control Center, as illustrated below:
However, when developers were handed the task of adding similar blur effects to their own apps, they hit a serious roadblock. Apple had decreed that the crop of devices available at the time simply weren’t powerful enough to support real-time blurs in third-party apps, claiming developers would over-use blurs resulting in slower apps with much degraded user experiences.
Developers, being the crafty individuals they are, soon created their own blurring algorithms from static blurring of images all the way to live-blur hacks.
A lot of these solutions worked pretty well. However, iOS 8 adds official, high-performance blur effects to the developer’s toolbox – and you’ll be surprised how easy it is to use the new blur effects.
Note: For one approach to using static images to mimic live blur effects, check out this blog post.
Clear Facts on Blurs
Executing blurs in a tasteful – and efficient – manner takes a bit of finesse. In this section you’ll learn about a common blurring algorithm and how you can use blurs to improve the user experience of your apps.
How Blurs Work
All blurs start with an image. To achieve a blur, you apply a blurring algorithm to each pixel in the image; the resulting image then contains an evenly blurred version of the original. Blurring algorithms vary in style and complexity, but you’ll use a common algorithm known as Gaussian blur in this tutorial’s project.
Blurring algorithms generally examine each pixel of an image and use the surrounding pixels to calculate new color values for that pixel. For example, consider the following theoretical image grid below:
Each cell in the above grid represents an individual pixel, and each pixel has a value between 1 and 10. Consider the case where the algorithm is evaluating the center pixel. The algorithm averages the values of the surrounding pixels and inserts this averaged value into the center pixel, resulting in the following new pixel grid:
You then repeat this process for every pixel in the original image.
The sample algorithm above only examined one pixel in each direction to create the new averaged value. You can expand this blur radius even further to increase the amount of blurring in your image, as demonstrated in the image below:
Note: Generally, the greater your blur radius is, the more processing power you’ll require to process the image. iOS offloads most image processing activities to the GPU to keep the main thread free.
Blur Design Strategies
Humans have a tendency to pay attention to elements that are in-focus and ignore things that aren’t. Believe it or not, this is a natural consequence of how our eyes work. Focusing on an object as it moves closer or further away from the eye is known as accommodation; it’s what helps you perceive depth and the distance of objects around you.
App designers exploit this fact and blur unimportant items on the screen to direct the user’s attention to the remaining non-blurred elements, as demonstrated in this screenshot of the popular Twitter client Tweetbot:
The user interface in the background is barely recognizable in the image above. This provides contextual awareness to the user about where they are in the navigational hierarchy. For instance, you’d expect to return to the blurred-out view in the background once you select one of the accounts displayed.
Note: Be careful about the overuse of blurs in your mobile apps. Although blurs can provide great-looking effects, they can be distracting and annoying if used inappropriately or too often.
Follow the standard design approach to use blurs to direct a user’s attention to things that matter and you’ll seldom go wrong. See the Designing for iOS section of the iOS Human Interface Guidelines document found on Apple’s iOS developer center for more.
Getting Started
To learn how to implement blurring, you’ll add some blur effects to a brand new Brothers Grimm fairytale app – aptly named Grimm.
The app displays a library of fairytales to the user; when the user taps on a fairytale, the app presents the full story on screen. The user can customize the display font, text alignment, or even the color theme for daytime or nighttime reading.
Start by downloading the starter project, then open Grimm.xcodeproj in Xcode. Open Grimm.storyboard and take a look at the view controllers contained in the app as illustrated below:
You can ignore the very first view controller in the image above as it’s simply the root navigation controller of the app. Taking each numbered view controller in turn, you’ll see the following:
- The first controller is a
StoryListController
, which displays a list of all of the fairy tales in the database. - When you tap a story in the list you segue to this detail
StoryViewController
, which displays the title and text of the selected fairy tale. - This is a
OptionsController
which is contained inside theStoryViewController
and displays the available font, text alignment, and color options. To display it simply tap the settings icon in the detail controller.
Build and run. You’ll see the initial screen as shown below:
Have some fun exploring the app; select different stories, tap the ellipsis icon and swipe to different fonts and reading modes to understand how the user interface functions.
Note: You can use any simulator or iOS 8 device to run this app except for the iPad 2. Apple disabled much of the iPad 2’s ability to display blurs for performance reasons. The app will still work on the iPad 2; however, you won’t be able to see any of the neat blur effects.
Once you have a handle on how the app behaves, head straight to the next section to learn how to apply blur effects to the app.
Manual Blur Techniques
Eagle-eyed readers might have noticed that there is some legacy Objective-C code in this project.
Don’t fret – the category in this project has been used by hundreds of apps and is incredibly solid. It’s been bridged into all of your Swift files using Grimm-Bridging-Header.h since there’s no need to rewrite it for Swift.
Note: Swift was architected to be interoperable with Objective-C so that developers, including Apple’s own developers, could start adding Swift code to their projects without refactoring. Bridging Headers allow you to include Objective-C with your Swift files.
To have a look at the category in question, open Grimm\Categories\UIImage+ImageEffects.m from theProject Explorer and skim past all of the legalese comments until you find
applyBlurWithRadius:tintColor:saturationDeltaFactor:maskImage:
. This tutorial won’t cover all of the ins and outs of this code, but it will help you to understand some of the basic functionality contained within.
Apple provided this
UIImage
category with the release of iOS 7 to demonstrate how to apply static blurs to images. It takes advantage of the Accelerate.framework
to simplify vector and matrix math and is optimized for image processing.applyBlurWithRadius:tintColor:saturationDeltaFactor:maskImage:
takes a blur radius, saturation delta and an optional masking image and uses a lot of math to produce a new, manipulated image.Obtaining a Snapshot Image
You’ll need a snapshot image before you can apply your blur effect. The focus of your efforts today (pardon the pun) will be the options drawer at the bottom of the
StoryViewController
view.
Open StoryViewController.swift and look for
setOptionsHidden:
. You’ll capture a screenshot of the entireStoryViewController
view, blur it, and then set it as a subview of the options view.
Add the following function above
setOptionsHidden:
:func updateBlur() { // 1 optionsContainerView.hidden = true // 2 UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, true, 1) // 3 self.view.drawViewHierarchyInRect(self.view.bounds, afterScreenUpdates: true) // 4 let screenshot = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() } |
Taking each numbered comment in turn:
- You don’t want a screenshot of the options if they are on the screen so make sure that they are hidden before you take the screenshot.
- In order to draw a screenshot you need to create a new image context. Since you’re going to blur the image anyway, you don’t need the full-resolution Retina image. This saves a ton of processing power and resources.
- Draw the story view into the image context. Since you have hidden the optionsContainerView, you will need to wait for screen updates before capturing a screenshot.
- Pull a UIImage from the image context and then clean it up.
You will need to actually call
updateBlur
when you tap on the ellipsis icon. Find the method setOptionsHidden:
and add the following code to the top of the function:if !hidden { updateBlur() } |
Before you go any further, you should check that you’re capturing the screenshot you think you’re capturing.
Add a breakpoint to the
UIGraphicsEndImageContext()
line you added to updateBlur
in the previous step. Then build and run your project and tap on any fairy tale to select it.
Once that fairy tale is displayed, tap the ellipsis icon to trigger the breakpoint. In the variables section of theDebugging Editor, expand the
screenshot
variable and select the nested Some
variable, as shown below:
Tap the Space key to open Quick Look; you should see a non-Retina screenshot of your story view, as you can see here:
Notice there aren’t any navigation elements from your
UINavigationController
; that’s because the story view is a subview of UINavigationController
, therefore the navigation controls are outside of the screenshot area.
Now that you’re sure you are grabbing the correct screenshot, you can continue on and apply the blur using the
UIImage
category described earlier.Blurring Your Snapshot
Still working in StoryViewController.swift, find the updateBlur function you added and add the following line directly below
UIGraphicsEndImageContext()
:let blur = screenshot.applyLightEffect() |
Next, move your breakpoint to the line you just added, like so:
Note: You can move a breakpoint by dragging it up and down in the gutter.
Build and run. Tap any fairy tale, then tap the ellipsis icon in the navigation bar. Over in the Debugging Editor, find the blur variable and hit the space key to open Quick Look again.
Wait a second… there’s nothing there! What gives?
You don’t see anything because your breakpoint is right on the line that sets the variable and Xcode stopped executing just before it executed that line.
To execute the highlighted line of code, press F6 or click the Step over button as shown below:
Now expand the blur variable, select the nested
Some
variable, then press the Space key to open up Quick Look and see your new blurred image:
Note: LLDB (the debugging tool that Xcode uses) sometimes doesn’t play nicely with Swift. You may have to click the Step over button twice to get the
Some
variable to appear.
You’ve managed to take a screenshot and blur it – now it’s time to add the blurred image to the app.
Displaying the Blur in Your View
Open StoryViewController.swift and add the following property to the top of the file along with the other the property declarations:
var blurView = UIImageView() |
This initializes a new
UIImageView
with every StoryViewController
instance.
Find
viewDidLoad
and add the following code to the very end of the method:optionsContainerView.subviews[0].insertSubview(blurView, atIndex:0) |
Grimm.storyboard puts
OptionsController
inside a container view displayed each time the user taps the ellipsis icon. Since you don’t have direct access to the OptionsController
view, you need to get the first subview of the container. In this case, that happens to be the view owned by OptionsController
. Finally, you insertblurView
as a subview to the very bottom of the view stack, beneath all other subviews.
Still inside StoryViewController.swift, go back to
updateBlur
and add the following code to the end of the function:blurView.frame = optionsContainerView.bounds blurView.image = blur optionsContainerView.hidden = false |
Since
Also remember that you hid
blurView
isn’t setup in the Storyboard, it will have a frame of CGRectZero
unless you set the frame manually. As well, you set the image property to the blurred image you’ve already created.Also remember that you hid
optionsContainerView
before taking the screenshot. You need to make sure that you unhide the view before the method ends.
Disable the breakpoint you set earlier, then build and run. After selecting a fairy tale and displaying the options, behold the blur in all its glory, as shown below:
Hmm. That blur looks kind of… funky, doesn’t it? Why doesn’t it match up with the text behind it?
By default,
UIImageView
resizes the image to match the frame of the view. That means the huge blurred image is being squished into much smaller options view. That just won’t do!
To fix this, you need to set the
UIImageView’s
contentMode
property to something other than the defaultUIViewContentMode.ScaleToFill
.
Add the following line to
updateBlur
, just below where you set the blurred image on blurView
:blurView.contentMode = .Bottom
|
The
UIViewContentMode.Bottom
content mode forces the image to retain its size and instead fixes it to the bottom-center of the UIImageView
.
Build and run. How does the blur-positioning look now?
There’s just one more thing you need to do before your static blur is ready for use. Try changing the orientation of your device or simulator (⌘-Left/Right Arrow). The view doesn’t resize!
Since you are using auto-layout with all of your text, the screenshot wont be valid anymore. You’ll need to take a new screenshot after the device rotates and update the
blurView
.
To do this, add the following function override inside StoryViewController.swift:
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) { // 1 coordinator.animateAlongsideTransition(nil, completion: { context in // 2 self.updateBlur() }) } |
- The
animateAlongsideTransition
method gives you the ability to animate changes alongside the orientation change as well as do some cleanup after the rotation completes. You’re only using the completion block since you need the frame of theoptionsViewController
after the rotation change. - Setup the blur again after the orientation animation completes. This will use the new text layout.
Build and run and try changing the orientation to see everything be resized with a new blur.
The blur is sized correctly, but it’s not terribly exciting. Scroll the text behind the blur and you’ll notice the blur doesn’t update.
You can definitely do better than that. iOS 8 gives you the tools to create dynamic, real-time blur effects in your apps – it’s a long way from the developer-led solutions of iOS 7!
Blur Effects in iOS 8
iOS 8 gives you an entire suite of visual effects goodies;
UIBlurEffect
, a subclass of UIVisualEffect
, is particularly relevant to your interests. UIBlurEffect
provides the nice blurs you see in navigation bars, Notification Center, and Control Center – and you can use it in your apps as well.Adding a UIBlurEffect
Open StoryViewController.swift and find
setOptionsHidden:
. Comment out the call to updateBlur
inside the if-statement you built up in the previous sections; afterwards it should look like the following screenshot:
While you’re at it, you should ensure blurView isn’t added to the scene at all. In
viewDidLoad
, comment out the following line:optionsContainerView.subviews[0].insertSubview(blurView, atIndex:0) |
Note: Instead of deleting all the code you added previously, you’re simply commenting it out so you can go back and see the differences. If you’re not interested in preserving your manual blur code, you can delete the code instead of commenting it out.
Build and run. Check that everything compiles and that your static blur is gone.
Open Grimm.storyboard and find the Options Controller Scene. Select the view, open the Attributes Inspector and change the view’s Background to Clear Color, as shown below:
Open OptionsController.swift and add the following code to
viewDidLoad
, just after the line where you addoptionsView
to the view:// 1 let blurEffect = UIBlurEffect(style: .Light) // 2 let blurView = UIVisualEffectView(effect: blurEffect) // 3 blurView.setTranslatesAutoresizingMaskIntoConstraints(false) view.insertSubview(blurView, atIndex: 0) |
Taking each numbered comment in turn:
- Create a
UIBlurEffect
with aUIBlurEffectStyle.Light
style. This defines which effect to use. The other available styles areUIBlurEffectStyle.ExtraLight
andUIBlurEffectStyle.Dark
. - Create a
UIVisualEffectView
and tell it which effect to use. This class is a subclass ofUIView
; its sole purpose is to define and display complex visual effects. - Disable translating the auto-resizing masks into constraints on the
blurView
, as you’ll manually add constraints in just a moment, and add it at the bottom of view stack. If you just addedblurView
on top of the view, it would end up blurring all of the controls underneath it instead!
Now you need to ensure your
blurView
lays out properly with the rest of the view.
Still working in
viewDidLoad
, add the following code just before the line where you call addConstraints:
:constraints.append(NSLayoutConstraint(item: blurView, attribute: .Height, relatedBy: .Equal, toItem: view, attribute: .Height, multiplier: 1, constant: 0)) constraints.append(NSLayoutConstraint(item: blurView, attribute: .Width, relatedBy: .Equal, toItem: view, attribute: .Width, multiplier: 1, constant: 0)) |
These constraints keep the frame of the
blurView
consistent with that of the OptionsController
view.
Build and run. Select a fairy tale, and then scroll the text. Behold as the blur updates in real-time.
You now have a dynamic blur effect in your app that not only looks great – you’re also using core iOS functionality to make it happen.
Adding Vibrancy to your Blur
Blur effects are great – but as usual, Apple has taken it to the next level with the
UIVibrancyEffect
, which when used in combination with UIVisualEffectView
adjusts the colors of the content to make it feel more vivid.
The following image demonstrates how vibrancy makes your labels and icons pop off the screen, while at the same time blending with the background itself:
The left side of the image shows a normal label and button, while the right side shows a label and button with vibrancy applied.
Note:
UIVibrancyEffect
must be added to a UIVisualEffectView
that has been setup and configured with aUIBlurEffect
object; otherwise, there won’t be any blurs to apply a vibrancy effect!
Open OptionsController.swift and add the following code to
viewDidLoad
, just before where the Auto Layout constraints are added:// 1 let vibrancyEffect = UIVibrancyEffect(forBlurEffect: blurEffect) // 2 let vibrancyView = UIVisualEffectView(effect: vibrancyEffect) vibrancyView.setTranslatesAutoresizingMaskIntoConstraints(false) // 3 vibrancyView.contentView.addSubview(optionsView) // 4 blurView.contentView.addSubview(vibrancyView) |
Taking each numbered comment in turn:
- Create a
UIVibrancyEffect
that uses theblurEffect
you set up earlier.UIVibrancyEffect
is another subclass ofUIVisualEffect
. - Create a
UIVisualEffectView
to contain the vibrancy effect. This process is exactly the same as creating a blur. Since you’re using Auto Layout, you make sure to disable auto-resizing translations here. - Add the
optionsView
to your vibrancy view’scontentView
property; this ensures the vibrancy effect will be applied to the view that contains all of the controls. - Finally you add the vibrancy view to the blur view’s
contentView
to complete the effect.
The final thing to do is set up the Auto Layout constraints for the vibrancy view so that it uses the same height and width of your controller’s view.
Add the following constraints below the others, at the end of
viewDidLoad
:constraints.append(NSLayoutConstraint(item: vibrancyView, attribute: .Height, relatedBy: .Equal, toItem: view, attribute: .Height, multiplier: 1, constant: 0)) constraints.append(NSLayoutConstraint(item: vibrancyView, attribute: .Width, relatedBy: .Equal, toItem: view, attribute: .Width, multiplier: 1, constant: 0)) |
Build and run. Bring up the options view to see your new vibrancy effect in action:
Unless you have high-contrast vision, the vibrancy effect makes it really difficult to read the labels and controls. What’s going on?
Ah – the content behind the blur view is white and you’re applying a
UIBlurEffectStyle.Light
effect. That’s counterproductive, to be sure.
Modify the line near the top of
viewDidLoad
that initializes the blurEffect
:let blurEffect = UIBlurEffect(style: .Dark) |
This changes the blur effect to add more contrast between the background and text.
Build and run. You’re now experiencing some true vibrancy:
Where To Go From Here?
You can download the finished project here.
You’ve seen how to blur images manually, as well as how to create real-time blur effects. You can just as easily add
UIVisualEffectViews
to your own apps.
Your manual blur technique relied on static images, so you couldn’t animate the image or efficiently update the blur in real-time.
UIBlurEffect
, however, does update in real-time, so you can achieve all sorts of weird and wonderful things with these effects, such as animations etc.
Whilst you might be tempted to go ahead and blur all the things, keep in mind what was talked about earlier in the tutorial regarding using these effects sparingly and only where appropriate. As is often the case, with blur and vibrancy, less is definitely more.
If you want to learn more about new iOS 8 APIs like this, check out our book iOS 8 by Tutorials, where we have 30 chapters and over 750 pages of tutorials on new iOS APIs ready for you!
In the meantime, if you have any questions or comments about this tutorial or visual effects in general, please join the forum discussion below!
0 comments:
Post a Comment