Attempting to write a custom segue where the source view is scaled out, while changing the alpha of the destination to fade the destination in. The destination is a MKMapView, so I want it updating as the fade occurs.
With what I've tried I end up with the source and designation scaling out simultaneously, and I can't get just the source view to scale out.
class Map_Segue: UIStoryboardSegue {
override func perform()
{
var sourceViewController : UIViewController = self.sourceViewController as UIViewController
var destinationViewController : UIViewController = self.destinationViewController as UIViewController
destinationViewController.view.alpha = 0 sourceViewController.addChildViewController(destinationViewController) sourceViewController.view.addSubview(destinationViewController.view)
UIView.animateWithDuration(2.0,delay:1.0,options: UIViewAnimationOptions.CurveEaseInOut, // delay of 1 second for aesthetics
animations:
{
sourceViewController.view.transform = CGAffineTransformScale(sourceViewController.view.transform, 100.0, 100.0);
destinationViewController.view.alpha = 1;
},
completion:
{ (finished:Bool)->Void in
destinationViewController.didMoveToParentViewController(sourceViewController);
}
)
}
}
I've tried autoresizesSubviews=false, but that doesn't seem to do anything.
I've tried setting the destination transform in the animation to be 1/100 (and set the options to UIViewAnimationOptions.CurveLinear which has the final result correct, but the transition effect is wrong (map in the background scaled up then down again)
I'm sure this should be easy, and I'm missing a trick as I'm new to this.
Anyone got any ideas?
Update:
I've found that (somewhat obviously) I should use sourceViewController.view.superview?.insertSubview( destinationViewController.view, atIndex:0) to insert it alongside the original source view, rather than as a child of it, that way, obviously, the transform is independent, not with respect to the views parent (which it will be as a subview). The problem then is swapping to the new view. Using the method I had, viewWillAppear and similar are not called, and the changing over the views does not work. If I call presentViewController, then we get a glitch when viewWillAppear is called.
Solution so far
Forget using custom segues. Followed the suggestion and placed a UIImageView on top of the map view, and had a beautiful animating fade in about 5 minutes of coding.
I think you are a bit confused with parent and child view controllers etc. It is sufficient to temporarily add the second view controller's view to the first one, perform the transitions and then clean up.
I tested the following in a simple sample project. Note that I am adding the second view controller's view to the window rather than the first view controller's view because otherwise it would also get scaled up.
override func perform() {
let first = self.sourceViewController as ViewController
let second = self.destinationViewController as ViewController
second.view.alpha = 0
first.view.window!.addSubview(second.view)
UIView.animateWithDuration(2.0, delay: 0.0,
options: .CurveEaseInOut, animations: { () -> Void in
first.view.transform = CGAffineTransformMakeScale(100.0, 100.0)
second.view.alpha = 1.0
})
{ (finished: Bool) -> Void in
second.view.removeFromSuperview()
first.presentViewController(second, animated: false, completion: nil)
}
}
Related
I have always used standard UIKit UIViewController containers, like UIPageViewController, for my container needs, but I want to take a shot at creating a custom container. But there are some specific things I'm uncertain of. And this would be completely programmatic without a storyboard (therefore, without segue).
I want the container to house (for the sake of this example) 4 UIViewController's that will be the 4 main sections of the UI. And within each section there will be a UINavigationController to handle the stack within its section. Therefore, these 4 UIViewController's should be permanently added as children to the container (and never removed), correct? Reasoning: so that the user doesn't lose his/her place in a navigation stack when leaving and returning to that section.
I want to implement custom animation transitions between these 4 view controllers that will use pan gestures to drag them in and out of view. In the past, with UINavigationController, for example, a custom animation object would effectively override pushViewController(), but what would I be effectively overriding here?
Correct me if I'm wrong, but according to Apple, as I understand it, a view controller can either be pushed or "presented". I say "presented" because "presented" includes "present" (which I think is what confuses people), which brings in a UIViewController modally, while "show", which is also included under the "presented" umbrella, does not bring in a UIViewController modally, it works in some way like push() except that its not in a navigation stack.
You ask:
Therefore, these 4 UIViewController's should be permanently added as children to the container (and never removed), correct?
As a minor refinement, you might load them in a just-in-time manner, loading them only when you'll actually need them. This avoids any potential delays resulting from loading many view controllers that might not yet be visible (if ever). But, sure, once loaded, you might keep them in memory. This is a pattern employed by Apple's own container view controllers.
... show ... does not bring in a UIViewController modally, it works in some way like push() except that it's not in a navigation stack.
The show method will navigate the view controller hierarchy, determine in which context it finds itself and will transition as accordingly. If you're within a navigation controller or split view controller, it will perform the appropriate transition. But if you're not within one of those controllers, it will perform a modal presentation. See the show documentation.
I want to implement custom animation transitions between these 4 view controllers that will use pan gestures to drag them in and out of view...
I'd suggest referring to Custom Container View Controller Transitions, which outlines how they use the custom transition pattern in conjunction with your own view controller containment.
If that becomes too unwieldy, you might just go "old school" and just use gestures to animate views for the contained view controllers in and and out, doing all of the appropriate view controller containment related calls. It's not as elegant, but I suspect this might be easier.
If you do this manually (much easier), the only trick is to make sure you do the appropriate appearance and view controller containment calls. So, in the below example, we start with one of the four child view controllers added as a child of the container view controller, and this shows how to replace that child with another child view controller with a gesture. This keeps the view controller hierarchy in sync with the view hierarchy and makes sure that the children get their appropriate appearance methods called:
import UIKit
import UIKit.UIGestureRecognizerSubclass
import os.log
class ViewController: UIViewController {
lazy private var swipeFromRight: UIScreenEdgePanGestureRecognizer = {
let swipe = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleSwipeFromRight(_:)))
swipe.edges = .right
return swipe
}()
private var nextChildController: UIViewController?
private var currentChildController: UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(swipeFromRight)
}
#objc func handleSwipeFromRight(_ gesture: UIScreenEdgePanGestureRecognizer) {
let percent = -gesture.translation(in: gesture.view).x / gesture.view!.bounds.width
if gesture.state == .began {
os_log("starting gesture", type: .debug)
guard let next = nextViewController() else {
gesture.state = .cancelled
return
}
currentChildController = childViewControllers.first!
nextChildController = next
startAppearance(next, replacing: currentChildController!, in: currentChildController!.view.superview)
// prepare for gesture driven animation
next.view.frame = currentChildController!.view.frame
next.view.transform = .init(translationX: next.view.frame.width, y: 0)
} else if gesture.state == .changed {
// update animation based upon progress percent
nextChildController!.view.transform = .init(translationX: nextChildController!.view.frame.width * (1 - percent), y: 0)
} else if gesture.state == .ended {
// figure out whether we should finish the animation (if not, we'll reverse it)
let velocity = gesture.velocity(in: gesture.view).x
let shouldFinish = velocity < 0 || (velocity == 0 && percent > 0.5)
os_log("finishing gesture: shouldFinish=%#", type: .debug, shouldFinish ? "true" : "false")
let next = nextChildController!
let previous = self.currentChildController!
if shouldFinish {
UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut, animations: {
next.view.transform = .identity
}, completion: { finished in
self.endAppearance(next, replacing: previous)
})
} else {
beginCancelAppearance(next, replacing: previous)
UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut, animations: {
next.view.transform = .init(translationX: next.view.frame.width, y: 0)
}, completion: { finished in
self.endCancelAppearance(next, replacing: previous)
next.view.transform = .identity
})
}
}
}
// MARK: Child appearance/containment helpers
private func startAppearance(_ appearingController: UIViewController, replacing disappearingController: UIViewController, in view: UIView? = nil) {
appearingController.beginAppearanceTransition(true, animated: true)
addChildViewController(appearingController)
view?.addSubview(appearingController.view)
disappearingController.willMove(toParentViewController: nil)
disappearingController.beginAppearanceTransition(false, animated: true)
}
private func beginCancelAppearance(_ appearingController: UIViewController, replacing disappearingController: UIViewController) {
appearingController.willMove(toParentViewController: nil)
appearingController.beginAppearanceTransition(false, animated: true)
disappearingController.willMove(toParentViewController: self)
disappearingController.beginAppearanceTransition(true, animated: true)
}
private func endCancelAppearance(_ appearingController: UIViewController, replacing disappearingController: UIViewController) {
appearingController.view.removeFromSuperview()
appearingController.removeFromParentViewController()
appearingController.endAppearanceTransition()
disappearingController.endAppearanceTransition()
}
private func endAppearance(_ appearingController: UIViewController, replacing disappearingController: UIViewController) {
appearingController.endAppearanceTransition()
appearingController.didMove(toParentViewController: self)
disappearingController.view.removeFromSuperview()
disappearingController.removeFromParentViewController()
disappearingController.endAppearanceTransition()
}
// MARK: Next/Previous child helpers
private func nextViewController() -> UIViewController? { ... }
private func previousViewController() -> UIViewController? { ... }
}
Clearly, replace the animation with whatever animation you want. And, obviously, vend your view controllers however appropriate for your app. But it illustrates the nature of the gesture, appearance, and containment calls that need to be done.
Issue:
Modally presented view controller does not move back up after in-call status bar disappears, leaving 20px empty/transparent space at the top.
Normal : No Issues
In-Call : No Issues
After In-Call Disappears:
Leaves a 20px high empty/transparent space at top revealing orange view below. However the status bar is still present over the transparent area. Navigation Bar also leaves space for status bar, its' just 20px too low in placement.
iOS 10 based
Modally presented view controller
Custom Modal Presentation
Main View Controller behind is orange
Not using Autolayout
When rotated to Landscape, 20px In-Call Bar leaves and still leaves 20px gap.
I opt-out showing status bar in landscape orientations. (ie most stock apps)
I tried listening to App Delegates:
willChangeStatusBarFrame
didChangeStatusBarFrame
Also View Controller Based Notifications:
UIApplicationWillChangeStatusBarFrame
UIApplicationDidChangeStatusBarFrame
When I log the frame of presented view for all four above methods, the frame is always at (y: 0) origin.
Update
View Controller Custom Modal Presentation
let storyboard = UIStoryboard(name: "StoryBoard1", bundle: nil)
self.modalVC = storyboard.instantiateViewController(withIdentifier: "My Modal View Controller") as? MyModalViewController
self.modalVC!.transitioningDelegate = self
self.modalVC.modalPresentationStyle = .custom
self.modalVC.modalPresentationCapturesStatusBarAppearance = true;
self.present(self.modalVC!, animated: true, completion: nil)
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
toViewController!.view.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.0, options: [.curveEaseOut], animations: { () -> Void in
toViewController!.view.transform = CGAffineTransform.identity
}, completion: { (completed) -> Void in
transitionContext.completeTransition(completed)
})
}
I've been looking for a solution for 3 days. I don't like this solution but didn't found better way how to fix it.
I'he got situation when rootViewController view has bigger height for 20 points than window, when I've got notification about status bar height updates I manually setup correct value.
Add method to the AppDelegate.swift
func application(_ application: UIApplication, didChangeStatusBarFrame oldStatusBarFrame: CGRect) {
if let window = application.keyWindow {
window.rootViewController?.view.frame = window.frame
}
}
After that it works as expected (even after orientation changes).
Hope it will help someone, because I spent too much time on this.
P.S. It blinks a little bit, but works.
I faced this problem too but after I put this method, problem is gone.
iOS has its default method willChangeStatusBarFrame for handling status bar. Please put this method and check it .
func application(_ application: UIApplication, willChangeStatusBarFrame newStatusBarFrame: CGRect) {
UIView.animate(withDuration: 0.35, animations: {() -> Void in
let windowFrame: CGRect? = ((window?.rootViewController? as? UITabBarController)?.viewControllers[0] as? UINavigationController)?.view?.frame
if newStatusBarFrame.size.height > 20 {
windowFrame?.origin?.y = newStatusBarFrame.size.height - 20
// old status bar frame is 20
}
else {
windowFrame?.origin?.y = 0.0
}
((window?.rootViewController? as? UITabBarController)?.viewControllers[0] as? UINavigationController)?.view?.frame = windowFrame
})
}
Hope this thing will help you.
Thank you
I had the same issue with the personnal hospot modifying the status bar.
The solution is to register to the system notification for the change of status bar frame, this will allow you to update your layout and should fix any layout issue you might have.
My solution which should work exactly the same for you is this :
In your view controller, in viewWillAppear suscribe to the UIApplicationDidChangeStatusBarFrameNotification
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(myControllerName.handleFrameResize(_:)), name: UIApplicationDidChangeStatusBarFrameNotification, object: nil)
Create your selector method
func handleFrameResize(notification: NSNotification) {
self.view.layoutIfNeeded() }
Remove your controller from notification center in viewWillDisappear
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIApplicationDidChangeStatusBarFrameNotification, object: nil)
You also need your modal to be in charge of the status bar so you should set
destVC.modalPresentationCapturesStatusBarAppearance = true
before presenting the view.
You can either implement this on every controller susceptible to have a change on the status bar, or you could make another class which will do it for every controller, like passing self to a method, keep the reference to change the layout and have a method to remove self. You know, in order to reuse code.
I think this is a bug in UIKit. The containerView that contains a presented controller's view which was presented using a custom transition does not seem to move back completely when the status bar returns to normal size. (You can check the view hierarchy after closing the in call status bar)
To solve it you can provide a custom presentation controller when presenting. And then if you don't need the presenting controller's view to remain in the view hierarchy, you can just return true for shouldRemovePresentersView property of the presentation controller, and that's it.
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return PresentationController(presentedViewController: presented, presenting: presenting)
}
class PresentationController: UIPresentationController {
override var shouldRemovePresentersView: Bool {
return true
}
}
or if you need the presenting controller's view to remain, you can observe status bar frame change and manually adjust containerView to be the same size as its superview
class PresentationController: UIPresentationController {
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
NotificationCenter.default.addObserver(self,
selector: #selector(self.onStatusBarChanged),
name: .UIApplicationWillChangeStatusBarFrame,
object: nil)
}
#objc func onStatusBarChanged(note: NSNotification) {
//I can't find a way to ask the system for the values of these constants, maybe you can
if UIApplication.shared.statusBarFrame.height <= 20,
let superView = containerView?.superview {
UIView.animate(withDuration: 0.4, animations: {
self.containerView?.frame = superView.bounds
})
}
}
}
I've been looking for a solution to this problem. In fact, I posted a new question similar to this one. Here: How To Avoid iOS Blue Location NavigationBar Messing Up My StatusBar?
Believe me, I've been solving this for a couple of days now and it's really annoying having your screen messed up because of the iOS's status bar changes by in-call, hotspot, and location.
I've tried implementing Modi's answer, I put that piece of code in my AppDelegate and modified it a bit, but no luck. and I believe iOS is doing that automatically so you do not have to implement that by yourself.
Before I discovered the culprit of the problem, I did try every solution in this particular question. No need to implement AppDelegate's method willChangeStatusBar... or add a notification to observe statusBar changes.
I also did redoing some of the flows of my project, by doing some screens programmatically (I'm using storyboards). And I experimented a bit, then inspected my previous and other current projects why they are doing the adjustment properly :)
Bottom line is: I am presenting my main screen with UITabBarController in such a wrong way.
Please always take note of the modalPresentationStyle. I got the idea to check out my code because of Noah's comment.
Sample:
func presentDashboard() {
if let tabBarController = R.storyboard.root.baseTabBarController() {
tabBarController.selectedIndex = 1
tabBarController.modalPresentationStyle = .fullScreen
tabBarController.modalTransitionStyle = .crossDissolve
self.baseTabBarController = tabBarController
self.navigationController?.present(tabBarController, animated: true, completion: nil)
}
}
I solve this issue by using one line of code
In Objective C
tabBar.autoresizingMask = (UIViewAutoResizingFlexibleWidth | UIViewAutoResizingFlexibleTopMargin);
In Swift
self.tabBarController?.tabBar.autoresizingMask =
UIViewAutoresizing(rawValue: UIViewAutoresizing.RawValue(UInt8(UIViewAutoresizing.flexibleWidth.rawValue) | UInt8(UIViewAutoresizing.flexibleTopMargin.rawValue)))`
You just need to make autoresizingMask of tabBar flexible from top.
In my case, I'm using custom presentation style for my ViewController.
The problem is that the Y position is not calculated well.
Let's say the original screen height is 736p.
Try printing the view.frame.origin.y and view.frame.height, you'll find that the height is 716p and the y is 20.
But the display height is 736 - 20(in-call status bar extra height) - 20(y position).
That is why our view is cut from the bottom of the ViewController and why there's a 20p margin to the top.
But if you go back to see the navigation controller's frame value.
You'll find that no matter the in-call status bar is showing or not, the y position is always 0.
So, all we have to do is to set the y position to zero.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let f = self.view.frame
if f.origin.y != 0 {
self.view.frame = CGRect(x: f.origin.x, y: 0, width: f.width, height: f.height)
self.view.layoutIfNeeded()
self.view.updateConstraintsIfNeeded()
}
}
Be sure to set the frame of the view controller's view you are presenting to the bounds of the container view, after it has been added to the container view. This solved the issue for me.
containerView.addSubview(toViewController.view)
toViewController.view.frame = containerView.bounds
My goal is to perform a flip-animation from view1 to view2 and vice versa.
I only want to flip the views not the whole viewController
Set up as follows:
i have a view controller (fromViewController) in my storyboard embedded in a navigationController. I drew a segue to a second view controller (toViewController) and made it a custom segue.
Now i want to add a flip animation to flip the views of the view controllers from left to right and right to left.
The forward animation already works (from fromViewController to toViewController) but he backward animation does not work.
I checked a lot of similar questions here but i couldn't find an answer that fit my needs.
I hope someone can help me out of this.
The class for the custom segue is FlipSegue.
here is my code:
import UIKit
extension UIViewController{
var isVisible: Bool{
return self.isViewLoaded && self.view.window != nil
}
}
class FlipSegue: UIStoryboardSegue {
override func perform() {
let fromViewController = self.source
let toViewController = self.destination
if fromViewController.isVisible{
UIView.transition(from: fromViewController.view, to: toViewController.view, duration: 1, options: .transitionFlipFromLeft, completion: nil)
}
else{
UIView.transition(from: toViewController.view, to: fromViewController.view, duration: 1, options: .transitionFlipFromRight, completion: nil)
}
}
}
FYI: The toViewController is not part of the NavigationController.
EDIT: I perform the segue by tapping on a UIBarButton on the left side of the navigation controller. The Button is calling the perform function in the FlipSegue class. Maybe the problem is that the toViewController is not part of the navigation stack? So i added it to the navigation stack and then i got a back button and i could return to the fromViewController with the standard animation, but this is not what i wanted.
I haven't tried this, but you should probably use from:fromViewcontroller.view in both cases, since in a back segue, from is still the starting VC.
In implementing the following cross-dissolve custom segue on a button press (learned at https://www.youtube.com/watch?v=xq9ZVsLNcWw):
override func perform() {
var src:UIViewController = self.sourceViewController as! UIViewController
var dstn:UIViewController = self.destinationViewController as! UIViewController
src.view.addSubview(dstn.view)
dstn.view.alpha = 0
UIView.animateWithDuration(0.75 , delay: 0.1, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: { () -> Void in
dstn.view.alpha = 1
}) { (finished) -> Void in
dstn.view.removeFromSuperview()
src.presentViewController(dstn, animated: false, completion: nil)
}
}
I am getting the "Unbalanced calls to begin/end appearance transitions" warning/error.
I have thoroughly searched many stackoverflow questions:
Unbalanced calls to begin/end appearance transitions for <UITabBarController: 0x197870>
my response: I am using the segue as a button press not within a TabBar Controller so this answer does not apply
Keep getting "Unbalanced calls to begin/end appearance transitions for <ViewController>" error
my response: I tried two methods: 1) All segues done programatically with self.performseguewithidentifier 2) All segues done via interface builder, by click dragging the buttons and selecting my custom transition from the attributes pane of the selected segue. Both still yielded the aforementioned error.
"Unbalanced calls to begin/end appearance transitions" warning when push a view in a modal way in XCode 4 with Storyboard
my response: Same as above, double checked all segues were only performed once, either programmatically or via interface builder, not both.
"Unbalanced calls to begin/end appearance transitions for DetailViewController" when pushing more than one detail view controller
my response: I am not using a table view controller nor a navigation controller in my segues, so this answer does not apply.
Unbalanced calls to begin/end appearance transitions for UITabBarController
my response: I tried making all segues perform within a dispatch as shown below
let delay = 0.01 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue()) {
self.performSegueWithIdentifier("toNonFBLogin", sender: nil)
}
however still to no avail, the warning/error still pops up.
I'm aware what the error means, that it's attempting to present a new viewcontroller before the previous one loads, but not sure how to tackle it.
The only lead I have is that the previous (source) viewcontroller pops up real quickly before the final viewcontroller loads, as seen at 0:04 at
https://youtu.be/i1D5fbcjNjY
The glitch only actually appears I would guess around 5% of the time, however.
Any ideas?
Looks like this behaviour is the result of some - as of yet - undocumented change in recent iOS releases.
Getting frustrated with your exact issue and the lack of satisfying answers I've come up with this:
// create a UIImageView containing a UIImage of `view`'s contents
func createMockView(view: UIView) -> UIImageView {
UIGraphicsBeginImageContextWithOptions(view.frame.size, true, UIScreen.mainScreen().scale)
view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return UIImageView(image: image)
}
override func perform() {
let src:UIViewController = self.sourceViewController as! UIViewController
let dstn:UIViewController = self.destinationViewController as! UIViewController
let mock = createMockView(dstn.view)
src.view.addSubview(mock)
mock.alpha = 0
UIView.animateWithDuration(0.75, delay: 0.1, options: UIViewAnimationOptions.TransitionCrossDissolve,
animations: { () -> Void in
mock.alpha = 1
},
completion: { (finished) -> Void in
src.presentViewController(dstn, animated: false, completion: { mock.removeFromSuperView()})
})
}
createMockView() will create a UIImageView containing a snapshot of the destination VCs contents and use that to do the transition animation.
Once the transition is finished the real destination VC is presented without animation causing a seamless transition between both. Finally once the presentation is done the mock view is removed.
I've looked around for an answer for this and spent the last two hours pulling my hair out to no end.
I'm implementing a very basic custom view controller transition animation, which simply zooms in on the presenting view controller and grows in the presented view controller. It adds a fade effect (0 to 1 alpha and visa versa).
It works fine when presenting the view controller, however when dismissing, it brings the presenting view controller back in all the way to fill the screen, but then it inexplicably disappears. I'm not doing anything after these animations to alter the alpha or the hidden values, it's pretty much a fresh project. I've been developing iOS applications for 3 years so I suspect this may be a bug, unless someone can find out where I'm going wrong.
class FadeAndGrowAnimationController : NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
func animationControllerForPresentedController(presented: UIViewController!, presentingController presenting: UIViewController!, sourceController source: UIViewController!) -> UIViewControllerAnimatedTransitioning! {
return self
}
func animationControllerForDismissedController(dismissed: UIViewController!) -> UIViewControllerAnimatedTransitioning! {
return self
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning!) -> NSTimeInterval {
return 2
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning!) {
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as UIViewController
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as UIViewController
toViewController.view.transform = CGAffineTransformMakeScale(0.5, 0.5)
toViewController.view.alpha = 0
transitionContext.containerView().addSubview(fromViewController.view)
transitionContext.containerView().addSubview(toViewController.view)
transitionContext.containerView().bringSubviewToFront(toViewController.view)
UIView.animateWithDuration(self.transitionDuration(transitionContext), animations: {
fromViewController.view.transform = CGAffineTransformScale(fromViewController.view.transform, 2, 2)
fromViewController.view.alpha = 1
toViewController.view.transform = CGAffineTransformMakeScale(1, 1)
toViewController.view.alpha = 1
}, completion: { finished in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
})
}
}
And the code to present:
let targetViewController = self.storyboard.instantiateViewControllerWithIdentifier("Level1ViewController") as Level1ViewController
let td = FadeAndGrowAnimationController()
targetViewController.transitioningDelegate = td
targetViewController.modalPresentationStyle = .Custom
self.presentViewController(targetViewController, animated: true, completion: nil)
As you can see, a fairly basic animation. Am I missing something here? Like I said, it presents perfectly fine, then dismisses 99.99% perfectly fine, yet the view controller underneath after the dismissal is inexplicably removed. The iPad shows a blank screen - totally black - after this happens.
This seems to be an iOS8 bug. I found a solution but it is ghetto. After the transition when a view should be on-screen but isn't, it needs to be added back to the window like this:
BOOL canceled = [transitionContext transitionWasCancelled];
[transitionContext completeTransition:!canceled];
if (!canceled)
{
[[UIApplication sharedApplication].keyWindow addSubview: toViewController.view];
}
You might need to play around with which view you add back to the window, whether to do it in canceled or !canceled, and perhaps making sure to only do it on dismissal and not presentation.
Sources: Container view disappearing on completeTransition:
http://joystate.wordpress.com/2014/09/02/ios8-and-custom-uiviewcontrollers-transitions/
I was having the same problem when dismissing a content view controller. My app has this parent view controller showing a child view controller. then when a subview in the child is tapped, it shows another vc (which I am calling the content vc)
My problem is that, when dismissing the contentVC, it should go to child VC but as soon as my custom transition finishes, childVC suddenly disappears, showing the parent VC instead.
What I did to solve this issue is to
change the .modalPresentationStyle of the childVC presented by parentVC from the default .automatic to .fullscreen.
Then changed the .modalPresentationStyle of contentVC to .fullscreen as well.
This solves the issue. but it won't show your child VC as a card sheet (when using .overCurrentContext or automatic) which is new in iOS 13.
had the same problem ios 8.1
my swift code after completeTransition
if let window = UIApplication.sharedApplication().keyWindow {
if let viewController = window.rootViewController {
window.addSubview(viewController.view)
}
}