29

I would like to implement UIVisualEffectView to apply a blur effect to a view to show the view that lies behind it.

This view that should have its background blurred is a UITableViewController that is embedded in a UINavigationController, and it will either be presented in a popover on iPad or it will be presented full screen modally on iPhone, thanks to iOS 8 adaptive segues (Present as Popover). When this view controller is in a popover I want the background to blur what's underneath the popover, and when it's presented full screen I want the background to blur the previous view controller.

I have tried to implement this and have not been successful. I cannot even get the blur effect to work for the popover. I thought this code should do the trick:

//In viewDidLoad on the UITableViewController subclass:
let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .Light))
effectView.frame = tableView.frame
tableView.addSubview(effectView)

I also tried adding the subview to the tableView.backgroundView, I tried setting the backgroundView to my effectView, I tried using Autolayout constraints instead of setting the frame, but nothing has worked. Can you help me accomplish the desired behavior?

An example of what I am trying to obtain:
iPad popover:
enter image description here

iPhone modal presentation:
enter image description here

Jordan H
  • 52,571
  • 37
  • 201
  • 351

4 Answers4

48

I've finally found a solution. I had to create two separate segues - one for a Popover Presentation which is called only on iPad, and the other a modal segue with Presentation Style set to Over Full Screen (that's important) for iPhone.

In the table view controller that is being presented, in viewDidLoad, this code will apply the desired blur effect (and bonus, only if they haven't disabled transparency effects):

if (!UIAccessibilityIsReduceTransparencyEnabled()) {
    tableView.backgroundColor = UIColor.clear
    let blurEffect = UIBlurEffect(style: .light)
    let blurEffectView = UIVisualEffectView(effect: blurEffect)
    tableView.backgroundView = blurEffectView

    //if inside a popover
    if let popover = navigationController?.popoverPresentationController {
        popover.backgroundColor = UIColor.clear
    }

    //if you want translucent vibrant table view separator lines
    tableView.separatorEffect = UIVibrancyEffect(blurEffect: blurEffect)
}

This causes the table background to appear just like it does in the screenshots. The trick for iPhone was to ensure it's presented over the screen, while the trick for iPad was to remove the backgroundColor in the popoverPresentationController.

Jordan H
  • 52,571
  • 37
  • 201
  • 351
  • I am not seeing the "Over Full Screen" presentation style. Is that iOS 8 only? – livingtech Sep 23 '14 at 17:56
  • 2
    @livingtech Yes. Adaptive segues (and other APIs used in the solution) are only available on iOS 8+. – Jordan H Sep 23 '14 at 22:48
  • 2
    I also noticed that iPad 2 and iPad Retina simulators don't show blurs, it makes the view only semi transparent. All other simulators work fine, including iPhone 4s. Just don't get caught by this, as I first thought that UIVisualEffectView doesn't work – Dmitry Nov 11 '14 at 15:12
  • what about the cells? – Yariv Nissim Dec 04 '14 at 18:39
  • @yar1vn set their backgrounds to `UIColor.clearColor()` – Jordan H Dec 04 '14 at 22:33
  • I already tried that, they just blend with the background. I eventually add an ExtraLight effect to all the cells, they stick out but still look good. Try it out – Yariv Nissim Dec 04 '14 at 22:47
  • @yar1vn Oh, if you wanted it to look like the iPhone screenshot then you'd use 0 alpha for the background color, but for something like the iPad screenshot you can use white color with your desired alpha, shouldn't need to add another effect to the cells unless you're trying to obtain a different look. – Jordan H Dec 05 '14 at 00:48
  • You're right. For most cases clear background is enough, I was making it look like the Maps.app info screen – Yariv Nissim Dec 05 '14 at 17:17
  • I noticed that if you just set the background to clear, you don't need to add a blur for popovers – Yariv Nissim Jan 20 '15 at 19:49
  • Also, use the adaptive methods instead of creating 2 segues. Let me know if you want a code sample – Yariv Nissim Jan 20 '15 at 22:01
  • 1
    @yar1vn feel free to add an answer – Jordan H Jan 21 '15 at 00:55
  • I had to add `tableView.separatorColor = UIColor.clearColor()` for the separator vibrancy effect to work on iPad. – sleep Jun 27 '16 at 02:01
  • Adding it makes the cells jump while scrolling. Why's that? – Tarvo Mäesepp Aug 08 '16 at 20:28
  • If the popover background is clear color, then the popover arrow will not appear, at least for me. – CyberMew Mar 13 '17 at 12:19
6

A quick example to add the blur using Adaptivity:

extension ViewController: UIPopoverPresentationControllerDelegate {

 override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

    // Grab the destination view controller and set the presentation delegate
    let viewController = segue.destinationViewController as UIViewController
    viewController.popoverPresentationController?.delegate = self
    viewController.presentationController?.delegate = self
  }

  // Note: Only called for FormSheet and Popover
  func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
    switch controller.presentedViewController {
    case let vc as PopoverViewController:
      return .None // Keep the popover in Compact screens
    default:
      return .OverFullScreen
    }
  }

  func presentationController(controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
    // This is a custom method on UIViewController
    controller.presentedViewController.createBlur()

    // Wrap in a Navigation Controller, the controller should add a title and bar buttons
    if !(presentedViewController is UINavigationController) {
      return UINavigationController(rootViewController: presentedViewController)
    }
  }
}

extension UIViewController {
  func createBlur(effectStyle: UIBlurEffectStyle = .Light) {
    if !UIAccessibilityIsReduceTransparencyEnabled() {
      view.backgroundColor = UIColor.clearColor()

      let blurView = UIVisualEffectView(effect: UIBlurEffect(style: effectStyle))
      blurView.autoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth
      blurView.frame = view.bounds

      view.insertSubview(blurView, atIndex: 0)
    }
  }
}

extension UITableViewController {
  override func createBlur(effectStyle: UIBlurEffectStyle = defaultBlurEffectStyle) {
    if !UIAccessibilityIsReduceTransparencyEnabled() {
      tableView.backgroundColor = UIColor.clearColor()

      let blurEffect = UIBlurEffect(style: effectStyle)      
      tableView.backgroundView = UIVisualEffectView(effect: blurEffect)
      tableView.separatorEffect = UIVibrancyEffect(forBlurEffect: blurEffect)
    }
  }
}
Yariv Nissim
  • 13,273
  • 1
  • 38
  • 44
5

Slightly late to the table with this one, but the best solution for me (in ios8 +), was to take a UIVisualEffectView in the Storyboard and make it the root view of my ViewController. Then add my tableview to that

enter image description here

To find the Visual Effect View, go to the Components Picker (not sure what thats called) on the bottom right hand side and search for VisualEffectView

enter image description here

This seems like a much easier way to go about it, and falls in line with Apple's recommendation to do as much on the Storyboard as possible

bolnad
  • 4,533
  • 3
  • 29
  • 41
1

A small change for IOS 10 Swift 3

if #available(iOS 10.0, *) {
        let blurEffect = UIBlurEffect(style: .prominent)
        let blurEffectView = UIVisualEffectView(effect: blurEffect)
        groupTable.backgroundView = blurEffectView

    } else {
        let blurEffect = UIBlurEffect(style: .dark)
        let blurEffectView = UIVisualEffectView(effect: blurEffect)
        groupTable.backgroundView = blurEffectView

    }
Jeremy Andrews
  • 807
  • 12
  • 17