let storyboard = UIStoryboard(name: "Main", bundle: nil)
let protectionVC = storyboard.instantiateViewController(identifier: "PrivacyProtectionViewController")
guard let windowScene = self.window?.windowScene else {
return
}
privacyProtectionWindow = UIWindow(windowScene: windowScene)
privacyProtectionWindow?.rootViewController = protectionVC
UIView.transition(with: privacyProtectionWindow!, duration: 1.2, options: .transitionCrossDissolve, animations: nil, completion: nil)
privacyProtectionWindow?.windowLevel = .alert + 1
privacyProtectionWindow?.makeKeyAndVisible()
When I put this to sceneWillResignActive function in SceneDelegate.swift,
it does not affect my transition style at all.
Do you have any idea why it does not work?!
The UIView.transition method will not work because you are animating the the window and not the controller's view which is being viewed on screen. Your animation duration (1.2 seconds) is also too long.
When the user resigns from the current scene, the iOS animations occurs faster than 1 second. You might see it when switching between apps, but when moving to the home screen the animation occurs almost instantly.
With all these considerations, you might be able to get the desired result with the following code.
privacyProtectionWindow = UIWindow(windowScene: windowScene)
privacyProtectionWindow?.rootViewController = protectionVC
privacyProtectionWindow?.windowLevel = UIWindow.Level.alert
privacyProtectionWindow?.makeKeyAndVisible()
// New animation code
protectionVC.view.alpha = 0.0
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseOut]) {
protectionVC.view.alpha = 1.0
}
Also, for additional smoothness you can add animation when the scene becomes active again.
func sceneDidBecomeActive(_ scene: UIScene) {
UIView.animate(withDuration: 0.2) {
self.privacyProtectionWindow?.rootViewController!.view.alpha = 0.0
} completion: { (completed) in
if completed{
self.privacyProtectionWindow = nil
self.window?.makeKeyAndVisible()
}
}
}
The animation of alpha values change UIView opacity. It will give you the effect you are going for with the UIView.transition method.
Related
I have a AVPlayerController that is embedded in a parentVC. Now if present the parentVC the normal way, using present(UIViewController, animated: ), everything works fine.
However, if I show the parentVC as a child controller of another controller, as shown below, the AVPlayer controls won't show. I can still tap on the center of the player to toggle video play, but can't see any controls.
How can I bring back the player controls? I've tried showsPlaybackControls not working either.
func present() {
guard let presenterView = presenter.view else {
return
}
presenter.addChild(self)
view.translatesAutoresizingMaskIntoConstraints = false
presenterView.addSubview(view)
setupExternalConstraints()
view.transform = CGAffineTransform(translationX: 0, y: presenter.view.frame.height)
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut) {
self.view.transform = .identity
} completion: { _ in
self.didMove(toParent: presenter)
}
}
I am a new Swift writer who is looking for an answer to a relatively specific question. Please forgive any novice mistakes.
I am trying to create a Pop Up on the screen programmatically that layers two view controllers so that one view controller is visible on the other, with a level of opacity that makes the background visible.
After a transition from my GameViewController see here:
let gameOverVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "gameOverID") as! GameOverViewController
self.addChildViewController(gameOverVC)
gameOverVC.view.frame = self.view.frame
self.view.addSubview(gameOverVC.view)
gameOverVC.didMove(toParentViewController: self)
}
... I instantiate a ViewController of GameOverViewController to fit over the GameViewController. After that I go to my GameOverViewController class and attempt to set the background color to be opaque with the following line of code:
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.8)
The problem is that I end up with is a background that is not opaque, when in reality I would rather the GameViewController to layer on top of the GameViewController with an opaque background to still be able to see the Game.
I also animate with the following within my GameOverViewController:
func showAnimate()
{
self.view.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
self.view.alpha = 0.0
UIView.animate(withDuration: 0.25, animations: {
self.view.alpha = 1.0
self.view.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
})
}
func removeAnimate()
{
UIView.animate(withDuration: 0.25, animations: {
self.view.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
self.view.alpha = 0.0
}, completion: { (finished: Bool) in
if (finished)
{
self.view.removeFromSuperview()
}
})
}
I'm not sure what is the source of my problem is.
Thank you for your help.
change background color of game over view Controller in interface builder like this
Also , take note of that you remove the game viewController's view in this line in removeAnimate func
self.view.removeFromSuperview()
this may cause black background if you do it before showing game over VC
for the view that sits on top, use: overFullScreen and then simply present it.
let topVC = topVC()
topVC.modalPresentationStyle = .overFullScreen
self.present(VCTobePresented, animated: true, completion: nil)
I have split my app into UIStoryboards representing the key components of that app.
I am wanting to move from Storyboard A (main) to Storyboard B.
Currently I transition from A to B like this;
let sb:UIStoryboard = UIStoryboard(name: "Second", bundle: nil)
let vc : SecondViewController = sb.instantiateInitialViewController() as! SecondViewController
// change to desired number of seconds
let when = DispatchTime.now() + 0.5
DispatchQueue.main.asyncAfter(deadline: when) {
SVProgressHUD.dismiss()
// Your code with delay
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let window:UIWindow = appDelegate.window!
UIView.transition(with: window, duration: 0.2, options: [UIViewAnimationOptions.transitionCrossDissolve], animations:{
}, completion: { (finished: Bool) -> () in
window.rootViewController = nil
window.rootViewController = vc
window.makeKeyAndVisible()
})
}
But it doesn't have the same animation as a tradition view controller push as generated from a traditional UINavigation or storyboard segue.
My question is, how can I animate it so that the storyboard I am moving to animates like a UINavigation push?
Many thanks
I'm trying to animate up a UICollectionViewController over an original UIViewController with the original UIViewController blurred as background, however, every time the animation finishes, I can see clearly through the blurred view that the original view controller is dismissed, what do I do to avoid the first UIViewController being dismissed?
Code in the first view controller to present the second:
let VC = storyboard?.instantiateViewController(withIdentifier: "PopoverCollectionVC") as! PopoverCollectionVC
VC.setDataSource(with: .calcDPSItems)
VC.collectionView?.backgroundColor = UIColor.clear
VC.transitioningDelegate = self
self.present(VC, animated: true, completion: nil)
Code in the animator object for custom animation:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let fromView = transitionContext.view(forKey: .from)!
let toView = transitionContext.view(forKey: .to)!
if presenting {
// configure blur
effectView.frame = fromView.bounds
containerView.addSubview(effectView)
// configure collection view
toView.frame = CGRect(x: 0, y: fromView.frame.height, width: fromView.frame.width, height: fromView.frame.height / 2)
containerView.addSubview(toView)
UIView.animateKeyframes(withDuration: duration, delay: 0, options: .calculationModeCubic, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.6) {
toView.center.y = fromView.center.y
}
UIView.addKeyframe(withRelativeStartTime: 0.1, relativeDuration: 1) {
self.effectView.effect = UIBlurEffect(style: .dark)
}
}) { _ in
transitionContext.completeTransition(true)
}
} else {
...
}
You need to specify that the modal presentation style will be over the current context :
VC.modalPresentationStyle = .overCurrentContext
Then to get the the presented view you need
// Get the from view from The ViewController because there is a bug in iOS when
// using some modalPresentationStyle values
// that causes the viewForKey to returm nil for UITransitionContextFromViewKey
// www.splinter.com.au/2015/04/17/ios8-view-controller-transitioning-bug/
let fromVC = transitionContext.viewController(forKey: .from)
let fromView = fromVC?.view
You should instead take a snapshot of the previous view, then blur than and use it as a background on the new VC.
let oldView = view_to_copy.snapshotView(afterScreenUpdates: false)
Then blur this, and add as a subview to your collectionView controller.
EDIT:
Just to add if you are using the VC inside something like UITabbar or UINavigationController you may need to snapshot that view to ensure all the UI is inside the snapshot.
I've created a custom segue which is a reversed version of a vertical segue, here is my perform function:
var sourceViewController = self.sourceViewController as UIViewController!
var destinationViewController = self.destinationViewController as UIViewController!
sourceViewController.view.addSubview(destinationViewController.view)
destinationViewController.view.frame = sourceViewController.view.frame
destinationViewController.view.transform = CGAffineTransformMakeTranslation(0, -sourceViewController.view.frame.size.height)
destinationViewController.view.alpha = 1.0
UIView.animateWithDuration(0.5, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: { () -> Void in
destinationViewController.view.transform = CGAffineTransformMakeTranslation(0, 0)
}) { (finished: Bool) -> Void in
destinationViewController.view.removeFromSuperview()
sourceViewController.presentViewController(destinationViewController, animated: false, completion: nil)
}
When I perform it in my app it works and the animation is exactly what I want but I have this warning in the console:
Unbalanced calls to begin/end appearance transitions for <Custom_Segues.ViewController: 0x7a3f9950>.
I read many posts concerning this problem on Stack Overflow but I didn't find one linked to my situation, does someone know what is the problem? I tried many things on my code and I know the problem is in the two last lines but I don't know what to change.
EDIT/ANSWER:
After reading the answers I found a solution: changing the view then applying the old VC on the new one and do the animation. The code is safe and there is no flash of the old VC at the end of the animation.
var sourceViewController = self.sourceViewController as UIViewController!
var destinationViewController = self.destinationViewController as UIViewController!
var duplicatedSourceView: UIView = sourceViewController.view.snapshotViewAfterScreenUpdates(false) // Screenshot of the old view.
destinationViewController.view.addSubview(duplicatedSourceView) // We add a screenshot of the old view (Bottom) above the new one (Top), it looks like nothing changed.
sourceViewController.presentViewController(destinationViewController, animated: false, completion: {
destinationViewController.view.addSubview(duplicatedSourceView) // We add the old view (Bottom) above the new one (Top), it looks like nothing changed.
UIView.animateWithDuration(0.33, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: { () -> Void in
duplicatedSourceView.transform = CGAffineTransformMakeTranslation(0, sourceViewController.view.frame.size.height) // We slide the old view at the bottom of the screen
}) { (finished: Bool) -> Void in
duplicatedSourceView.removeFromSuperview()
}
})
}
This looks runloop timing related.
destinationViewController.view.removeFromSuperview()
sourceViewController.presentViewController(destinationViewController, animated: false, completion: nil)
These two lines should not execute within the same runloop.
destinationViewController.view.removeFromSuperview()
// Force presentViewController() into a different runloop.
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(0.001 * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue()) {
sourceViewController.presentViewController(destinationViewController, animated: false, completion: nil)
}
I added this as a comment, but I think it good enough to make a second solution. After looking at How to custom Modal View Controller presenting animation?
The solution converted to swift:
var transition = CATransition()
transition.duration = 1
transition.type = kCATransitionFade
transition.subtype = kCATransitionFromBottom
sourceViewController.view.window?.layer.addAnimation(transition, forKey: kCATransition)
sourceViewController.presentViewController(destinationViewController, animated:false, completion:nil)
You can adjust this to match your needs.