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)
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)
}
}
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.
I have a popover view (without a tab bar) that is popped over to a view controller with a tab bar. In the view controller with a tab bar I set up a button to click, so that the view controller pops up:
#IBAction func PopUpClicked(_ sender: UIButton) -> Void {
let popOverVC = UIStoryboard(name: "SpinningWheel", bundle: nil).instantiateViewController(withIdentifier: "PhotoPopUp") as! PopUpViewController
self.addChildViewController(popOverVC)
popOverVC.view.frame = self.view.frame
self.view.addSubview(popOverVC.view)
popOverVC.didMove(toParentViewController: self)
}
And in the popOver view controller, i animated the pop over.
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.8)
self.showAnimate()
// Do any additional setup after loading the view.
}
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()
}
});
}
But when the popover happens, Everything has a faded black background, which I want except for the tab bar. I would like for the pop over View to also pop over the tabbar and for the faded black background to go over it.
This is what I want it to look like:
With the black faded background covering the tabbar
This happens, because you have to present your popover as a modal ViewController. To achieve this you have to set the modal presentation style before presenting your popover from your target ViewController. This code should be called in your presenting ViewController:
let vc = YourPopOverViewController(nib: UINib(name: "PopOverViewController", bundle: nil), bundle: nil)
vc.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
tabBarController.present(vc, animated: true)
EDIT:
This should do the trick if you have designed your PopOverViewController as a fullscreen ViewController. I have done this a bunch of times and have left the space which should not be presented as clear background:
#IBAction func PopUpClicked(_ sender: UIButton) -> Void {
let popOverVC = UIStoryboard(name: "SpinningWheel", bundle: nil).instantiateViewController(withIdentifier: "PhotoPopUp") as! PopUpViewController
popOverVc.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
tabBarController.present(popOverVC, animated: true)
}
This 3rd party EzPopup can resolve the problem easily: https://github.com/huynguyencong/EzPopup
// init YourViewController
let contentVC = ...
// Init popup view controller with content is your content view controller
let popupVC = PopupViewController(contentController: contentVC, popupWidth: 100, popupHeight: 200)
// show it by call present(_ , animated:) method from a current UIViewController
present(popupVC, animated: true)
I have run into an issue when using a custom segue. I have two tableviews that I'm an trying to switch back and forth from. When I click on a cell in tableview1 it should take me to tableview2. I have a button on tableview2 that connects to the exit of the storyboard. From there it should take me back to tableview1 but whenever I press the button, the application crashes with a BAD_ACCESS error.
Here is my custom segue class:
class TableViewSegue: UIStoryboardSegue {
override func perform() {
scale()
}
func scale () {
let toViewcontroller = self.destination
let fromViewcontroller = self.source
let containerView = fromViewcontroller.view.superview
let originalCenter = fromViewcontroller.view.center
toViewcontroller.view.transform = CGAffineTransform(scaleX: 0.05, y: 0.05)
toViewcontroller.view.center = originalCenter
containerView?.addSubview(toViewcontroller.view)
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: {
toViewcontroller.view.transform = CGAffineTransform.identity
}, completion: { success in
fromViewcontroller.present(toViewcontroller, animated: false, completion: nil) //The application crashes and highlights this line as the error.
})
}
}
I have implemented this method in my tableViewController1:
#IBAction func prepareForUnwind(segue: UIStoryboardSegue) {
}
Not sure why the tableview2 does not dismiss.
EDIT: The issue had to do with needing a navigation controller.
The problem is that you are presenting the toViewcontroller each time a segue is performed. So the app presents table2 over table1, and then tries again to present table1 over table2 on the unwind.
Modify your custom segue to check - essentially - which direction you're going:
class TableViewSegue: UIStoryboardSegue {
override func perform() {
scale()
}
func scale () {
let toViewcontroller = self.destination
let fromViewcontroller = self.source
let containerView = fromViewcontroller.view.superview
let originalCenter = fromViewcontroller.view.center
toViewcontroller.view.transform = CGAffineTransform(scaleX: 0.05, y: 0.05)
toViewcontroller.view.center = originalCenter
containerView?.addSubview(toViewcontroller.view)
let fromP = fromViewcontroller.presentingViewController
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: {
toViewcontroller.view.transform = CGAffineTransform.identity
}, completion: { success in
// if nil, we are presenting a new VC
if fromP == nil {
fromViewcontroller.present(toViewcontroller, animated: false, completion: nil)
} else {
fromViewcontroller.dismiss(animated: false, completion: nil)
}
})
}
}
Note: This is assuming:
you are not trying to push/pop within a UINavigationController ... you'd need to add some other checks to handle that.
you are only going one-level-in, that is, you are not presenting, presenting, presenting, etc. and then trying to unwind.
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.