I have a UIViewController
that presents another UIViewController
modally. I want the modal view controller to have the blur/transparency that iOS 7 int
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
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:
viewDidLoad
of the presenting UIViewController
will not workUIViewController
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)
}
}
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;
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)
}
}
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];
Look at my sample project where I demonstrate blur effect by using only storyboard and constraints.
GitHub demo: link