When my custom segue's animation is being performed, destination view appears black. After animation ends, view appears and looks good.
Here are the screenshots:
Animation scene:
View after segue/animation finished:
Here is the custom segue class:
class SegueToPreview: UIStoryboardSegue {
override func perform() {
// Assign the source and destination views to local variables.
var firstVCView = self.sourceViewController.view as UIView!
var secondVCView = self.destinationViewController.view as UIView!
// Get the screen width and height.
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
// Specify the initial position of the destination view.
secondVCView.frame = CGRectMake(0.0, screenHeight, screenWidth, screenHeight)
// Access the app's key window and insert the destination view above the current (source) one.
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(secondVCView, aboveSubview: firstVCView)
// Animate the transition.
UIView.animateWithDuration(0.2, animations: { () -> Void in
firstVCView.frame = CGRectOffset(firstVCView.frame, -screenWidth, 0.0)
secondVCView.frame = CGRectOffset(secondVCView.frame, -screenWidth, 0.0)
}) { (Finished) -> Void in
self.sourceViewController.presentViewController(self.destinationViewController as! UIViewController,
animated: false,
completion: nil)
}
}
While unwind segue works just fine, showing both views.
I suppose that the view is not loaded while the animation is being performed, but have no idea how to fix that.
Your initial position for the destination vc's view is setup incorrectly (assuming that you want the view to move in from the right); you have it positioned below the screen, instead of to the right of the screen,
secondVCView.frame = CGRectMake(0.0, screenHeight, screenWidth, screenHeight)
This should be,
secondVCView.frame = CGRectMake(screenWidth, 0, screenWidth, screenHeight)
Related
I have a problem with a custom animation viewController transition.
I have a collectionView (fromView) and when a cell is selected, it grows to fullSize (toView).
It's working great except for the bottom cells, they are cropped when animating to center.
I tried to change the frame of the containerView to get extra space to aboid the crop but it's messing up the positions of the viewcontrollers in it.
This is the relevant code for the transition and a sketch to spot the issue.
Many thanks.
Actual behaviour:
Expected:
// originFrame is define by the selected cell in fromView
var originFrame = CGRect.zero
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toView = transitionContext.view(forKey: .to),
let fromView = transitionContext.view(forKey: .from)
else { return }
let finalFrame = toView.frame
let growFactor = finalFrame.width / originFrame.width
let centerDiffX = fromView.center.x-originFrame.midX
let centerDiffY = fromView.center.y-originFrame.midY
// Animate the transition.
UIView.animate(
withDuration: duration,
delay:0.0,
animations: {
let scaleTransform = CGAffineTransform(scaleX: growFactor, y: growFactor)
fromView.transform = CGAffineTransform(translationX: deltaX, y: deltaY).concatenating(scaleTransform)
...
Make a snapshot of the collectionview do the trick ;)
I'm writing an app where I need to use a fairly "complex" UIStoryboardSegue. The designer gave me the following:
Now the basic idea behind the segue is simple, sliding up the source.view, then sliding up one more view before eventually sliding the destination.view. However, my question is the following:
How do I insert a second view in-between the source.view and the destination.view from a subclass of UIStoryboardSegue ? Obviously I can't just do addSubview, since there is no view to add to. Is there another place to add a view in a UIStoryboardSegue, so I can still create this segue?
Thanks.
If this is what you want
You can achieve it very easily,
Step 1:
Create a subclass of UIStoryboardSegue and override perform
import UIKit
class SpecialEffectSegue: UIStoryboardSegue {
override func perform() {
let firstVCView = self.source.view as UIView!
let secondVCView = self.destination.view as UIView!
let intermediateView = UIView()
intermediateView.backgroundColor = UIColor.red
// Get the screen width and height.
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
// Specify the initial position of the destination view.
secondVCView?.frame = CGRect(x: 0.0, y: screenHeight, width: screenWidth, height: screenHeight)
intermediateView.frame = CGRect(x: 0.0, y: screenHeight, width: screenWidth, height: screenHeight)
// Access the app's key window and insert the destination view above the current (source) one.
let window = UIApplication.shared.keyWindow
window?.insertSubview(intermediateView, aboveSubview: firstVCView!)
window?.insertSubview(secondVCView!, aboveSubview: secondVCView!)
UIView.animate(withDuration: 0.4, animations: { () -> Void in
firstVCView?.frame = ((firstVCView?.frame)?.offsetBy(dx: 0.0, dy: -screenHeight))!
intermediateView.frame = (intermediateView.frame.offsetBy(dx: 0.0, dy: -screenHeight))
}) { (Finished) -> Void in
UIView.animate(withDuration: 0.4, animations: { () -> Void in
secondVCView?.frame = (secondVCView?.frame.offsetBy(dx: 0.0, dy: -screenHeight))!
}) { (Finished) -> Void in
self.source.present(self.destination, animated: false, completion: {
intermediateView.removeFromSuperview()
})
}
}
}
}
Though code looks huge and complicated what is happening in it is pretty simple.
Get the source and destination viewController's view using
let firstVCView = self.source.view as UIView!
let secondVCView = self.destination.view as UIView!
Because you need a intermediate view which is red in color here you create one more view
let intermediateView = UIView()
intermediateView.backgroundColor = UIColor.red
Now get the UIScreen width and heght so that you can configure these views frame so that it looks good as per your need
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
// Specify the initial position of the destination view.
secondVCView?.frame = CGRect(x: 0.0, y: screenHeight, width: screenWidth, height: screenHeight)
intermediateView.frame = CGRect(x: 0.0, y: screenHeight, width: screenWidth, height: screenHeight)
Note that the I set the frame of SecondVC and intermediateView such that they are below screen bounds and Ill animate them to come up in UIView.animate block
Now obviously because animations are happening on key window of your app access the key window
let window = UIApplication.shared.keyWindow
Now insert the subviews to window as per your need.
window?.insertSubview(intermediateView, aboveSubview: firstVCView!)
window?.insertSubview(secondVCView!, aboveSubview: secondVCView!)
So after this I have views stacked up as FirstVCView -> IntermediateView -> SecondVCView
Now we have pretty much what we need isnt it. Now animate it using UIView.animate
UIView.animate(withDuration: 0.4, animations: { () -> Void in
firstVCView?.frame = ((firstVCView?.frame)?.offsetBy(dx: 0.0, dy: -screenHeight))!
intermediateView.frame = (intermediateView.frame.offsetBy(dx: 0.0, dy: -screenHeight))
}) { (Finished) -> Void in
UIView.animate(withDuration: 0.4, animations: { () -> Void in
secondVCView?.frame = (secondVCView?.frame.offsetBy(dx: 0.0, dy: -screenHeight))!
}) { (Finished) -> Void in
self.source.present(self.destination, animated: false, completion: {
intermediateView.removeFromSuperview()
})
}
}
Important thing to notice is intermediateView.removeFromSuperview() in completion block.
Now I have decided to present destination using self.source.present( if you need to push destination VC with your funky animation say
self.source.navigationController?.pushViewController(destination, animated: false)
Thats all :)
Now open your storyboard, drag a segue from one your FirstVC to SecondVC and select the segue class as SpecialEffectSegue thats it now enjoy
Hope it helps :)
I'm attempting to create a custom segue where the order of the animation goes
1st UIViewController -> ImageView -> 2nd UIViewController
The code I have works, but for some reason the ImageView is black. I've tried using various images and literal image sources, but it's still black.
Here's some pictures for a better understanding-
1 (As the first UIViewController is replaced by the ImageView)
2 (As the ImageView is replaced by the second UIViewController)
/**
This segue transitions by dragging the destination UIViewController in from the right and replacing the old UIViewController by pushing it off to the left. This Segue also contains an ImageView that is between the two UIViewControllers.
*/
class CustomSegue2: UIStoryboardSegue {
override func perform() {
// Assign the source and destination views to local variables.
let firstView = self.source.view as UIView!
let secondView = UIImageView()
let thirdView = self.destination.view as UIView!
// Get the screen width and height.
let width = UIScreen.main.bounds.size.width
let height = UIScreen.main.bounds.size.height
// Set properties of secondView
secondView.frame = CGRect(x: width, y: 0, width: width, height: height)
secondView.contentMode = UIViewContentMode.scaleToFill
secondView.image = #imageLiteral(resourceName: "orangeRedGradient_MESH_tealGreenGradient")
// Specify the initial position of the destination view.
let rect = CGRect(x: width + width, y: 0.0, width: width, height: height)
thirdView?.frame = rect
// Access the app's key window and insert the destination view above the current
let window = UIApplication.shared.keyWindow
window?.insertSubview(thirdView!, aboveSubview: firstView!)
// Animation the transition.
UIView.animate(withDuration: 10, animations: { () -> Void in
firstView?.frame = firstView!.frame.offsetBy(dx: -width-width, dy: 0.0)
secondView.frame = secondView.frame.offsetBy(dx: -width-width, dy: 0.0)
thirdView?.frame = thirdView!.frame.offsetBy(dx: -width-width, dy: 0.0)
}) { (Finished) -> Void in
self.source.present(self.destination, animated: false, completion: nil)
}
}
}
secondView never appears because at no time do you insert it into the interface.
(I would describe your code overall as completely wrong-headed — if you want a custom transition, you should be writing proper custom transition animation code. But that answers the question of why the image view is "black" — it isn't black, it is completely absent.)
In my application implemented TabBarController Transitions using reference code by Apple Objective-C link And Swift link. But when switch fast between two tabs some times I am getting blank screen, I tried many answers in Stack Overflow but no luck.
Please check below code for reference while doing TabBarController Transitions using Swift
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let containerView = transitionContext.containerView
let fromView: UIView
let toView: UIView
// In iOS 8, the viewForKey: method was introduced to get views that the
// animator manipulates. This method should be preferred over accessing
// the view of the fromViewController/toViewController directly.
if #available(iOS 8.0, *) {
fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
} else {
fromView = fromViewController.view
toView = toViewController.view
}
let fromFrame = transitionContext.initialFrame(for: fromViewController)
let toFrame = transitionContext.finalFrame(for: toViewController)
// Based on the configured targetEdge, derive a normalized vector that will
// be used to offset the frame of the view controllers.
var offset: CGVector
if self.targetEdge == UIRectEdge.left {
offset = CGVector(dx: -1.0, dy: 0.0)
} else if self.targetEdge == .right {
offset = CGVector(dx: 1.0, dy: 0.0)
} else {
fatalError("targetEdge must be one of UIRectEdgeLeft, or UIRectEdgeRight.")
}
// The toView starts off-screen and slides in as the fromView slides out.
fromView.frame = fromFrame
toView.frame = toFrame.offsetBy(dx: toFrame.size.width * offset.dx * -1,
dy: toFrame.size.height * offset.dy * -1)
// We are responsible for adding the incoming view to the containerView.
containerView.addSubview(toView)
let transitionDuration = self.transitionDuration(using: transitionContext)
UIView.animate(withDuration: transitionDuration, animations: {
fromView.frame = fromFrame.offsetBy(dx: fromFrame.size.width * offset.dx,
dy: fromFrame.size.height * offset.dy)
toView.frame = toFrame
}, completion: {finshed in
let wasCancelled = transitionContext.transitionWasCancelled
// When we complete, tell the transition context
// passing along the BOOL that indicates whether the transition
// finished or not.
transitionContext.containerView.addSubview(toView)
transitionContext.completeTransition(!wasCancelled)
})
}
Below is the screen shot
It seems Like you have taken UINavigationController for your second tab. But somehow your connection from UINavigationController to your secondViewController is lost. Please check the image of a storyboard which may be in your scenario.
I also get the same as a blank screen and I fixed it by making the current view it the initial view controller.
check is Initial View Controller
I've wrote this code to create a simple flip animation:
func frontView (view:UIView) ->UIView {
var frontView: UIView
frontView = UIView()
frontView.frame = view.frame
frontView.center = CGPoint(x: 0, y: 0)
return frontView
}
func backView (view:UIView) ->UIView {
var backView: UIView
backView = UIView()
backView.frame = view.frame
backView.backgroundColor = UIColor.redColor()
backView.center = CGPoint(x: view.frame.width/2, y: 0)
view.addSubview(backView)
return backView
}
func flipViewAnimation (viewToAnimate: UIView) {
var animationOption = self.animationOption
var duration = self.duration
UIView.transitionFromView(backView(viewToAnimate), toView: frontView(viewToAnimate), duration: duration, options: animationOption, completion: nil)
//
}
As a viewToAnimate I use views with labels and imageViews inside, which I've created in AutoLayout. The result I'm trying to achieve more or less should look like this. First I see views filled with color, then they flip and show the content inside (labels and ImageViews).
But it works in a different way. Views appear already with content (labels and ImageViews) then just flip and again show the same content.
I've created each view programmatically and it works very well for me now.