user594161
user594161

Reputation:

How to use UIBlurEffect with modal View Controllers?

I have a UIViewController that presents another UIViewController modally. I want the modal view controller to have the blur/transparency that iOS 7 introduced. I tried using the new UIVisualEffect but it seems like that only works with UIViews, and not UIViewControllers?

Here is the code I've written, all the views I've added as subviews are what I want to be in the user interface above the blurred out view below it.

In the presenting view controller, I take a screenshot of the screen that I pass to the modal view controller before applying the blur.

In Presenting View Controller:

- (UIImage *)viewImage {
    CGSize size = CGSizeMake(self.view.frame.size.width,self.view.frame.size.height);
    UIGraphicsBeginImageContext(size);
    [self.view drawViewHierarchyInRect:(CGRect){CGPointZero, self.view.frame.size.width, self.view.frame.size.height} afterScreenUpdates:YES];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return image;
}

In Modal View Controller:

- (void)viewDidLoad {
    [super viewDidLoad];

    [self.view addSubview:[[UIImageView alloc] initWithImage:self.backgroundImage]];
    //self.backgroundImage is the image that the method above returns, it's a screenshot of the presenting view controller.
    UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
    UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
    [blurEffectView setFrame:self.view.bounds];
    [self.view addSubview:blurEffectView];

    [self.view bringSubviewToFront:self.cancelButton];
    [self.view bringSubviewToFront:self.titleLabel];
    [self.view bringSubviewToFront:self.tableView];
}

Upvotes: 14

Views: 19954

Answers (6)

Pedro Romão
Pedro Romão

Reputation: 2327

the easiest way is just copy and paste this code in viewDidLoad :)

UIVisualEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *visualEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];

visualEffectView.frame = self.view.bounds;
self.view insertSubview:visualEffectView atIndex:0];

Upvotes: 2

YarGnawh
YarGnawh

Reputation: 4654

When using UIVisualEffectView, you don't need to generate a snapshot. Try removing "- (UIImage *)viewImage" and in the model view controller add a UIVisualEffectView with size matching the view controller view, and add all "control" views into the UIVisualEffectView's contentView.

- (void)viewDidLoad {
    [super viewDidLoad];

    UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
    UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
    [blurEffectView setFrame:self.view.bounds];
    [self.view addSubview:blurEffectView];

    [blurEffectView.contentView addSubview:self.cancelButton];
    [blurEffectView.contentView addSubview:self.titleLabel];
    [blurEffectView.contentView addSubview:self.tableView];
}

https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIVisualEffectView/index.html

I haven't tried it with storyboards, but here's a tested working snippet. Might be a good place to start. Translating to objc should be pretty straightfoward

Here's the presenting view controller

import UIKit

class BaseViewController: UIViewController {

init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}

override func viewDidLoad() {
    super.viewDidLoad()

    var backgroundImage = UIImageView(image: UIImage(named: "image"))
    self.view.addSubview(backgroundImage)

    var button = UIButton(frame: CGRectMake(0, 100, 320, 50))
    button.setTitle("Lorem Ipsum", forState: UIControlState.Normal)
    button.backgroundColor = UIColor.redColor()
    button.addTarget(self, action: "onButtonTapped:", forControlEvents: UIControlEvents.TouchUpInside)

    self.view.addSubview(button)
}

func onButtonTapped(sender: UIButton?) {
    var modal = ModalViewController(nibName: nil, bundle: nil)
    modal.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
    self.presentViewController(modal, animated: true, completion: {})
}
}

Here's the modal

import UIKit

class ModalViewController: UIViewController {

init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}

override func viewDidLoad() {
    super.viewDidLoad()

    self.view.backgroundColor = UIColor.clearColor()

    let effect = UIBlurEffect(style: UIBlurEffectStyle.Light)
    let blurView = UIVisualEffectView(effect: effect)

    blurView.frame = self.view.bounds

    self.view.addSubview(blurView)
}
}

Upvotes: 11

Yung Dai
Yung Dai

Reputation: 159

In Swift 3

I found that if you take are doing a modal transition to a new UIViewController and have it over the context with a clear background so you can see the last VC. Using a UIVisualEffect view set in IB doesn't work properly. What you will get it is the blur being rendered at the end of your presentation of the new view.

In interface builder:

Build a modal segue to another view, make the Kind: Present Modally, Transition: Cross Fade. Then give it an identifier like "showMainMenu".

In the destination VC, set it so the first view is a UIImageView with AspectToFit set, with the side set to 0-0-0-0 to each side for the constraints. Second SubView is a UIBlurView set to 0-0-0-0 to the sides of the VC. Then put your elements on top of the UIBlurView like nice contrasty text.

Create a var for the background to set to the UIImageView of the VC you will be segueing to.

var background: UIImage! = UIImage()

Run the segue now and you'll notice the blur pops in and looks terrible.

To solved this issue, write some code to take a snapshot with this extension code for UIImage

    extension UIImage {

        class func takeScreenshot(view: UIView) -> UIImage? {

            // Create screenshot
            UIGraphicsBeginImageContext(view.bounds.size)

            view.layer.render(in: UIGraphicsGetCurrentContext()!)
            let screenshot:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
            UIGraphicsEndImageContext()
            print("Taking Screenshot")

            UIGraphicsEndImageContext()
            return screenshot
        }

}

Then in my segue I do the following:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

    if segue.identifier == "showMainMenu" {

        let vc = segue.destination as! MainViewController

        // I am using a VERY long scroll view so self.view.window! ensures that you only take a picture of your actual view instead of a view that is longer than your screen size.
        let screenshot: UIImage = UIImage.takeScreenshot(view: self.view.window!)!

        vc.background = screenshot

    }
}

once segued, in the new view controller make sure you run this.

// set the image over to the background image. 


    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.backgroundImage.image = background

    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)


        // prevent the blurView for just popping in due to the segue
        UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveEaseInOut, animations: {

            // make the background transparent so you can now see what is beneath that layer.
            self.backgroundImage.alpha = 0
        })
    }

This way you can get the sweet modal IB Fade in without too much code! Plus when if you have moving elements on the last VC beneath your current one, you can see it under the blur.

Hope this helps!

Feel free to checkout a demonstration project in my git!

https://github.com/yungdai/Smooth-blur-transition-using-storyboard

Upvotes: 11

MB_iOSDeveloper
MB_iOSDeveloper

Reputation: 4198

This is a combination of the above answers.

This works for Interface builder in Xcode 7.2.1 (Swift 2.1.) for iOS 9.2.1.

The code has been tested on the simulator and device.

In Interface Builder:

  1. Click the storyboard seque
  2. As "Kind" set "Present Modally"
  3. As "Presentation" set "Over Current Context" NOTE: Setting the 3.rd step in you viewDidLoadof the presenting UIViewController will not work
  4. Dont forget to click your UIViewController and under Custom Class"- Class name it YOUTransparentModalVC (or whatever you want).

In code:

import UIKit

let IMAGE_OVERLAY_NAME = "backgroundColorExample"

class YOUTransparentModalVC: UIViewController
{
    private var backgroundImageOverlayIV:UIImageView!

    override func viewDidLoad()
    {
        super.viewDidLoad()
        self.setupTransparentView()
        self.setupDissmissingVCOnTap()
        self.setupBackgroudImage()
    }

    private func setupTransparentView()
    {
        self.view.backgroundColor       = UIColor.clearColor()
        let effect                      = UIBlurEffect(style: UIBlurEffectStyle.Light)
        let blurView                    = UIVisualEffectView(effect: effect)
        blurView.frame                  = self.view.bounds
        self.view.addSubview(blurView)
    }

    private func setupBackgroudImage()
    {
        self.backgroundImageOverlayIV            = UIImageView(frame: self.view.frame)
        self.backgroundImageOverlayIV.image      = UIImage(named: IMAGE_OVERLAY_NAME)
        self.view.addSubview(self.backgroundImageOverlayIV)
    }

    private func setupDissmissingVCOnTap()
    {
        let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "dismissVC")
        view.addGestureRecognizer(tap)
    }

    func dismissVC()
    {
        self.dismissViewControllerAnimated(true, completion: nil)
    }


}

Upvotes: 1

Mazen K
Mazen K

Reputation: 3662

Thanks to @YarGnawh. This tested on iOS 8

UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
[blurEffectView setFrame:self.view.bounds];
self.tableView.backgroundView = blurEffectView;

Upvotes: 1

Patrik Vaberer
Patrik Vaberer

Reputation: 666

Look at my sample project where I demonstrate blur effect by using only storyboard and constraints.

GitHub demo: link

Upvotes: 0

Related Questions