Hello I prepared simple tvOS project, where I present view controller modally. I'm receiving fade animation with white flickering (see below) while presenting view controller modally. How can I remove the flickering?
My code:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.black
}
#IBAction func button1(_ sender: Any) {
let vc = UIViewController()
vc.view.backgroundColor = UIColor(red: 0.2, green: 0, blue: 0, alpha: 1.0)
present(vc, animated: true, completion: nil)
}
}
Animation I receive:
Possibly the outgoing view is having its alpha animated to zero, which means that the wallpaper image behind it may be starting to show through a bit before the incoming view has animated its alpha in enough to cover it up.
Is this a custom presentation animation? Can you adjust the way the alpha on the outgoing view is changed?
To remove the flickering animation change animated to false.
Code:
present(vc, animated: false, completion: nil)
Related
I have created a small project to replicate this problem.
The only file is this one...
Brief bit of code
class RootViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
showBlue()
}
#objc func showBlue() {
let vc = UIViewController()
vc.view.backgroundColor = .blue
let nvc = UINavigationController(rootViewController: vc)
vc.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(showGreen))
transition(to: nvc)
}
#objc func showGreen() {
let vc = UIViewController()
vc.view.backgroundColor = .green
let nvc = UINavigationController(rootViewController: vc)
vc.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(showBlue))
transition(to: nvc)
}
func transition(to toVC: UIViewController) {
if let fromVC = children.first {
transitionWithAnimation(fromVC: fromVC, toVC: toVC)
} else {
addWithoutAnimation(child: toVC)
}
}
func addWithoutAnimation(child toVC: UIViewController) {
addChild(toVC)
view.addSubview(toVC.view)
toVC.view.frame = view.bounds
toVC.didMove(toParent: self)
}
func transitionWithAnimation(fromVC: UIViewController, toVC: UIViewController) {
addChild(toVC)
toVC.view.frame = view.bounds
fromVC.willMove(toParent: nil)
transition(
from: fromVC,
to: toVC,
duration: 1.0,
options: .transitionCrossDissolve,
animations: nil) { _ in
fromVC.removeFromParent()
toVC.didMove(toParent: self)
}
}
}
Explaining the code
The RootViewController first does showBlue. This adds a child UINavigationController with a rootViewController with a blue background. The blue view controller has a Done button that then targets showGreen.
showGreen transitions to a UINavigationController with a green background and a Done button that targets showBlue.
What I expected
What I expected (and what I want to happen) is for the navigation bar to cross dissolve in place without resizing.
Animation of the problem
The problem is that during the animated transition the navigation bar has a strange animation to it. Which you can see here...
Apple documentation around this
All the code is followed exactly from the Apple documentation about adding child view controllers to a custom container view controller... https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html
Things I tried
I have also tried by using AutoLayout constraints rather than setting the view's frame directly but this didn't change anything.
I've tried running view.setNeedsLayout and then view.layoutIfNeeded() on the new view controller's view but that doesn't seem to have fixed it either.
No strange animation if child is not UINavigationController
The really odd thing is that if you use any other type of view controller (other than UINavigationController) then this animation glitch doesn't happen. For example: If one of the view controllers is a UITabBarController then the tabs do not have this odd animation. Even stranger, if the tab contains a UINavigationController then it doesn't have this animation either. It's literally just if the direct child is a UINavigationController.
Has anyone experienced this before? And did you manage to stop the strange animation?
If you place the transition code within a CATransaction and use the kCATransactionDisableActions key to turn off implicit actions it will resolve the issue:
CATransaction.begin()
CATransaction.setValue(kCFBooleanTrue, forKey:kCATransactionDisableActions)
transition(
from: fromVC,
to: toVC,
duration: 1.0,
options: [.transitionCrossDissolve],
animations: nil) { _ in
fromVC.removeFromParent()
toVC.didMove(toParent: self)
}
CATransaction.commit()
I would like to use blur background for my view controller but as soon as the view is loaded, the whole thing turns into gray.
I present the view controller by performing a segue from another view, and in the viewDidLoad() method of the view controller, I implement the blur effect as given below
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.clear
let blurBgView = UIVisualEffectView(effect: UIBlurEffect(style: .extraLight))
blurBgView.translatesAutoresizingMaskIntoConstraints = false
view.insertSubview(blurBgView, at: 0)
NSLayoutConstraint.activate([
blurBgView.heightAnchor.constraint(equalTo: view.heightAnchor),
blurBgView.widthAnchor.constraint(equalTo: view.widthAnchor),
])
}
This is how it looks like
How can I implement a blur background for my view controller?
I use XCode 9.3, Swift 4.1
Thanks
Present your view controller and set its modalPresentationStyle from your initial vc:
let newController = NewViewController()
newController.modalPresentationStyle = .overCurrentContext
present(newController, animated: true, completion: nil)
Or, if you are preparing for the segue:
let newController: NewViewController = segue.destination as! NewViewController
newController.modalPresentationStyle = .overCurrentContext
And in the NewViewController class in viewDidLoad() add your code.
I want to make the view of a view controller semi-transparent. For that I have set the background color like this in the viewDidLoad method.
view.backgroundColor = UIColor(white: 0, alpha: 0.5)
When the view controller is presented, the background appears as I need it and then it turns black right away.
Why is this happening?
This is the code for showing the PopupViewController:
#IBAction func didTapShowButton(_ sender: UIButton) {
let navController = UINavigationController(rootViewController: PopupViewController())
present(navController, animated: true, completion: nil)
}
I uploaded a demo project here as well.
You may add the flag overCurrentContext (or custom), so your present might be something like:
#IBAction func didTapShowButton(_ sender: UIButton) {
let navController = UINavigationController(rootViewController: PopupViewController())
navController.modalPresentationStyle = .overCurrentContext
present(navController, animated: true, completion: nil)
}
My apps is currently contains the 2 UIViewController VC1 to VC2.
In VC1 is the home screen and has an orange gradient image on UINavigationBar
In VC2 has a translucent UINavigationBar and set UIImage on UINavigationBar
but when navigation between this two view controllers, it appears a black navigation bar until the transition have done. I have try to set setNavigationBarHidden but I do not want hide the navigation bar, so how can I remove the black navigation bar?
Like this-
In VC1
override func viewWillAppear(animated: Bool) {
if let navController = self.navigationController {
UIGraphicsBeginImageContext(gradientLayer.frame.size)
gradientLayer.renderInContext(UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIView.animateWithDuration(0.3, animations: {
navController.navigationBar.setBackgroundImage(image, forBarMetrics: .Default)
navController.navigationBar.translucent = false
})
}
}
in VC2
override func viewWillAppear(animated: Bool) {
UIView.animateWithDuration(0.3, animations: {
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: .Default)
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.translucent = true
})
}
override func viewWillDisappear(animated: Bool) {
if let navController = self.navigationController {
UIGraphicsBeginImageContext(gradientLayer.frame.size)
gradientLayer.renderInContext(UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIView.animateWithDuration(0.3, animations: {
navController.navigationBar.setBackgroundImage(image, forBarMetrics: .Default)
navController.navigationBar.translucent = false
})
}
}
If you want to hide navigation bar without black color in transition.
(It's in second screen)
Swift4:
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
Swift 5 Simple way
//MARK:- Only use this code where you want to hide the navigation bar
self.navigationController?.setNavigationBarHidden(true, animated: true)
It depends on how you are setting the color of your Navigation Bar. If you set it in the next view's viewDidLoad then when you press a button for the transition it will change before the view pushes the next controller.
What timing do you want it to change at?
For Before:
viewDidLoad or viewWillAppear
For After:
viewDidAppear
And make sure you aren't setting it or the imageView on viewWillDisappear so it won't be black.
You will need to set the NavigationBar color directly with e.g.: navigationController.navigationBar.barTintColor = .orange
Obj C:
self.extendedLayoutIncludesOpaqueBars = true;
Swift:
extendedLayoutIncludesOpaqueBars = true
I'm developing an ios app. I have a a main view and in this view
im trying to present a modal view controller with dimmed background(black with opacity).
The problem is that the status bar is not affected by this color and remains the same.
This is how i present the view controller:
let shareViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ShareViewController") as! ShareViewController
shareViewController.battle = battle
shareViewController.delegate = self
let animation = CATransition()
animation.duration = 1
animation.type = kCATransitionFade
self.view.window?.layer.addAnimation(animation, forKey: kCATransition)
presentViewController(shareViewController, animated: false) {
() in
// nothing here
}
Here are some screenshots to demonstrate the problem:
This is the problem(status bar color):
Problem illustration
This is the modal view in storyboard:
storyboard
I cannot reproduce your problem, the following code works without problems in my single view app:
let viewController = UIViewController()
viewController.modalPresentationStyle = .overFullScreen
viewController.view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
let animation = CATransition()
animation.duration = 1
animation.type = kCATransitionFade
self.view.window?.layer.add(animation, forKey: kCATransition)
self.present(viewController, animated: false, completion: nil)
However note that you should be presenting over the root controller of the view. Sometimes you can get strange effects when presenting from your internal controllers:
self.view.window?.rootViewController?.present(viewController, animated: false, completion: nil)
Also make sure you are using the correct modalPresentationStyle.
Set your view controller as the root view controller of a UIWindow, then present the window at the UIWindowLevelAlert level.
Below is a Swift 3 class used to animate a modal popup over all other UI elements, including the status bar. A scrim view is used to shade background UI and intercept touches to dismiss the view.
import UIKit
class ModalViewController: UIViewController {
private let scrimView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.black
view.alpha = 0.0
return view
}()
private var myWindow: UIWindow?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.clear
// Setup scrim View
view.addSubview(scrimView)
view.topAnchor.constraint(equalTo: scrimView.topAnchor).isActive = true
view.leftAnchor.constraint(equalTo: scrimView.leftAnchor).isActive = true
view.rightAnchor.constraint(equalTo: scrimView.rightAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: scrimView.bottomAnchor).isActive = true
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismiss as (Void) -> Void))
scrimView.addGestureRecognizer(tapGestureRecognizer)
// Layout custom popups or action sheets
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 0.25) {
self.scrimView.alpha = 0.5
// Animate in custom popups or action sheets
}
}
func present() {
myWindow = UIWindow(frame: UIScreen.main.bounds)
myWindow?.windowLevel = UIWindowLevelAlert
myWindow?.backgroundColor = UIColor.clear
myWindow?.rootViewController = self
myWindow?.isHidden = false
}
func dismiss() {
UIView.animate(
withDuration: 0.25,
animations: {
self.scrimView.alpha = 0.0
// Animate out custom popups or action sheets
},
completion: { success in
self.myWindow = nil
}
)
}
}
To present the view:
let modalView = ModalViewController()
modalView.present()
To dismiss the view, tap anywhere on the scrim.
this code works for me, when I am presenting UIViewController with alpha != 1. present UIViewController like:
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let destinationVC = storyBoard.instantiateViewController(withIdentifier: "AddComment") as! AddCommentViewController
destinationVC.modalPresentationStyle = .overCurrentContext //this line is important
destinationVC.delegate = self
destinationVC.restId = self.restaurant.id
self.present(destinationVC, animated: true, completion: nil)
then in destinationVC view controller
override func viewWillDisappear(_: Bool) {
UIView.animate(withDuration: 1, animations: { () in
self.view.backgroundColor = .clear
})
super.viewWillDisappear(true)
}
override func viewWillAppear(_: Bool) {
UIView.animate(withDuration: 1, animations: { () in
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
})
super.viewWillAppear(true)
}
and set its backgroundColor to .clear in viewDidLoad or storyboard. So UIViewController covers whole screen including status bar.
Here is the solution you might be looking for:
if let window = UIApplication.shared.keyWindow {
window.windowLevel = UIWindowLevelStatusBar + 1
}
The main idea behind this code is, window of your application has a window level which is lower than status bar window level. And what this code does is, just put your window's window level higher than status bar window level, and your window can now cover the status bar. Don't forget, this code has to be called on main thread, just before presenting your view controller. Good luck!
Custom animation transitions should be performed using UIViewControllerAnimatedTransitioning. Here is a tutorial for this purpose:
https://www.raywenderlich.com/110536/custom-uiviewcontroller-transitions
If all you want is a fade animation you can have it by changing the modalTransitionStyle property of the viewController you are going to display.
Try by fixing your code this way:
guard let shareViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ShareViewController") as! ShareViewController else {
//Fallback in case of nil?
return
}
shareViewController.modalTransitionStyle = .crossDissolve
presentViewController(shareViewController, animated: true, completion: nil)
Also please note that presentViewController(shareViewController, animated: true, completion: nil) is for swift 2. The equivalent swift 3 would be present(shareViewController, animated: true, completion: nil)
you can add this code to view controller for Swift 3:
let statusView: UIView = UIView(frame: CGRect(x: 0.0, y: -20.0, width: UIScreen.main.bounds.size.width, height: 20.0))
statusView.backgroundColor = UIColor.black
statusView.alpha = 0.8
self.addSubview(self.statusView)
You could be extremely practical and simply hide the status bar when your modal view controller is up:
override func prefersStatusBarHidden() -> Bool {
return true
}