Open animation for image in UITableView in swift - ios

I have a UITableView with some label and images on it.
And I want to show image fullscreen when user tap on image.
I'am trying to achieve animation effect like when you tap on images on twitter account.
I've tried two days, but i couldn't find desire effect. This is my code right now :
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
println("animating transition")
var containerView = transitionContext.containerView()
var toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
var fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
var window = UIApplication.sharedApplication().keyWindow
var imageCenter = imageViewToSegue.center
var frame = window.convertRect(imageViewToSegue.frame, fromView: fromView)
var www = imageViewToSegue.image?.size.width
var hhh = imageViewToSegue.image?.size.height
var index = www! / self.view.frame.width
if (isPresenting) {
var copyImageView = UIImageView(frame: imageViewToSegue.frame)
copyImageView.frame.origin = view.convertPoint(imageViewToSegue.frame.origin, fromView: fromView)
copyImageView.image = imageViewToSegue.image
copyImageView.contentMode = imageViewToSegue.contentMode
copyImageView.clipsToBounds = true
copyImageView.userInteractionEnabled = true
window.addSubview(copyImageView)
containerView.addSubview(toViewController.view)
toViewController.view.alpha = 0
if let vc = toViewController as? ImageViewController {
vc.imageView.hidden = true
}
UIView.animateWithDuration(0.4, animations: { () -> Void in
copyImageView.frame = CGRect(x: 0, y: (self.view.bounds.height / 2) - ( hhh!/(index * 2)), width: www!/index , height: hhh!/index)
toViewController.view.alpha = 1
}) { (finished: Bool) -> Void in
copyImageView.removeFromSuperview()
if let vc = toViewController as? ImageViewController {
vc.imageView.hidden = false
}
transitionContext.completeTransition(true)
}
} else {
let vc = fromViewController as ImageViewController
var copyImageView = UIImageView(frame: vc.imageView.frame)
copyImageView.frame = CGRect(x: 0, y: (self.view.bounds.height / 2) - ( hhh!/(index * 2)), width: www!/index , height: hhh!/index)
copyImageView.image = imageViewToSegue.image
copyImageView.contentMode = imageViewToSegue.contentMode
copyImageView.clipsToBounds = true
copyImageView.userInteractionEnabled = true
window.addSubview(copyImageView)
vc.imageView.hidden = true
UIView.animateWithDuration(0.4, animations: { () -> Void in
fromViewController.view.alpha = 0
copyImageView.frame = frame
}) { (finished: Bool) -> Void in
copyImageView.removeFromSuperview()
transitionContext.completeTransition(true)
fromViewController.view.removeFromSuperview()
}
}
}

Related

How to make transition of a ViewController from Bottom to Top?

So here is a class for Slide in transition which adds a ViewController with animation from left to right and it works flawlessly I want a transition from bottom to top.
import UIKit
class SlideInTransition: NSObject, UIViewControllerAnimatedTransitioning {
var isPresenting = false
let dimmingView = UIView()
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toViewController = transitionContext.viewController(forKey: .to),
let fromViewController = transitionContext.viewController(forKey: .from) else { return }
let containerView = transitionContext.containerView
let finalWidth = toViewController.view.bounds.width * 0.8
let finalHeight = toViewController.view.bounds.height
if isPresenting {
// Add dimming view
dimmingView.backgroundColor = .black
dimmingView.alpha = 0.0
containerView.addSubview(dimmingView)
dimmingView.frame = containerView.bounds
// Add menu view controller to container
containerView.addSubview(toViewController.view)
// Init frame off the screen
toViewController.view.frame = CGRect(x: -finalWidth, y: 0, width: finalWidth, height: finalHeight)
}
// Move on screen
let transform = {
self.dimmingView.alpha = 0.5
toViewController.view.transform = CGAffineTransform(translationX: finalWidth, y: 0)
}
// Move back off screen
let identity = {
self.dimmingView.alpha = 0.0
fromViewController.view.transform = .identity
}
// Animation of the transition
let duration = transitionDuration(using: transitionContext)
let isCancelled = transitionContext.transitionWasCancelled
UIView.animate(withDuration: duration, animations: {
self.isPresenting ? transform() : identity()
}) { (_) in
transitionContext.completeTransition(!isCancelled)
}
}
}
To be honest I copied this code from somewhere a while ago and I don't have a source of it.
I'm fairly new to iOS so any help would be appreciated.
Try this,
class SlideInTransition: NSObject, UIViewControllerAnimatedTransitioning {
var isPresenting = false
let dimmingView = UIView()
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toViewController = transitionContext.viewController(forKey: .to),
let fromViewController = transitionContext.viewController(forKey: .from) else { return }
let containerView = transitionContext.containerView
let finalWidth = toViewController.view.bounds.width
let finalHeight = toViewController.view.bounds.height * 0.8
if isPresenting {
// Add dimming view
dimmingView.backgroundColor = .black
dimmingView.alpha = 0.0
containerView.addSubview(dimmingView)
dimmingView.frame = containerView.bounds
// Add menu view controller to container
containerView.addSubview(toViewController.view)
// Init frame off the screen
toViewController.view.frame = CGRect(x: 0, y: finalHeight, width: finalWidth, height: finalHeight)
}
// Move on screen
let transform = {
self.dimmingView.alpha = 0.5
toViewController.view.transform = CGAffineTransform(translationX: 0, y: -finalHeight)
}
// Move back off screen
let identity = {
self.dimmingView.alpha = 0.0
fromViewController.view.transform = .identity
}
// Animation of the transition
let duration = transitionDuration(using: transitionContext)
let isCancelled = transitionContext.transitionWasCancelled
UIView.animate(withDuration: duration, animations: {
self.isPresenting ? transform() : identity()
}) { (_) in
transitionContext.completeTransition(!isCancelled)
}
}
}

Custom Presentation transition broke on iOS13

I have this CollectionView with small images and a UIViewController with single image on the center of a screen with full-screen width.
When user taps on small image it should scale to take full screen width.
There's a custom transition animation between those two.
let previewVC = PreviewTutorialViewController(image: image!, imageFrame: frame, text: data!.text, imageView: cell.toDoImageView)
previewVC.modalPresentationStyle = .overCurrentContext
previewVC.transitioningDelegate = previewVC
self.present(previewVC, animated: true, completion: nil)
AnimationController
class AnimationController: NSObject, UIViewControllerAnimatedTransitioning {
var duration = 10.4
var isPresenting: Bool
init(forTransitionType type: TransitionType) {
self.isPresenting = type == .presenting
super.init()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return self.duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
var imageRectAfter : CGRect = .zero
var imageRectInitial : CGRect = .zero
var previewTutorialVC : PreviewTutorialViewController?
if self.isPresenting {
containerView.addSubview(toVC.view)
containerView.layoutIfNeeded()
previewTutorialVC = (toVC as! PreviewTutorialViewController)
imageRectInitial = previewTutorialVC!.initialRect
imageRectAfter = previewTutorialVC!.tutorialImageView.frame
previewTutorialVC?.initialImageView?.alpha = 0
print("$$$ animating image view frame : PRESENTING ", imageRectInitial, " to ", imageRectAfter)
} else {
previewTutorialVC = (fromVC as! PreviewTutorialViewController)
imageRectAfter = previewTutorialVC!.initialRect
imageRectInitial = previewTutorialVC!.tutorialImageView.frame
print("$$$ animating image view frame : DISSMISSING ", imageRectInitial, " to ", imageRectAfter)
//let frame2 = previewTutorialVC!.tutorialImageView.convert(imageRectInitial, to: containerView)
//imageRectInitial = CGRect(x: 0, y: 430, width: 414, height: 155)
//print("$$$ converted :", frame2)
print("$$$ ", containerView.bounds.size.width, previewTutorialVC?.view.bounds.size.width)
}
previewTutorialVC?.tutorialImageView.transform = .identity
previewTutorialVC?.tutorialImageView.frame = imageRectInitial
previewTutorialVC?.containerView.alpha = isPresenting == true ? 0 : 1
previewTutorialVC?.closeButton.alpha = self.isPresenting == true ? 0 : 1
//previewTutorialVC?.textContainerView.transform = isPresenting == true ? CGAffineTransform(translationX: 0, y: previewTutorialVC?.textContainerView.frame.size.height ?? 0) : .identity
UIView.animate(withDuration: duration, delay: 0, options: [.curveEaseOut], animations: {
previewTutorialVC?.tutorialImageView.frame = imageRectAfter
previewTutorialVC?.containerView.alpha = self.isPresenting == true ? 1 : 0
previewTutorialVC?.closeButton.alpha = self.isPresenting == true ? 1 : 0
// previewTutorialVC?.textContainerView.transform = self.isPresenting == true ? .identity : CGAffineTransform(translationX: 0, y: previewTutorialVC?.textContainerView.frame.size.height ?? 0)
}) { (_) in
if self.isPresenting == false { previewTutorialVC?.initialImageView?.alpha = 1 }
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
Console log:
$$$ animating image view frame : PRESENTING (15.0, 461.5, 384.0, 143.5) to (0.0, 370.5, 414.0, 155.0)
$$$ animating image view frame : DISSMISSING (0.0, 370.5, 414.0, 155.0) to (15.0, 461.5, 384.0, 143.5)
It used to work fine, but since iOS 13 i've noticed dismiss animation starts with way too wide image than it should be, although frame prints correct values.. also dismiss animation ends with image being a bit too high. Present animation's working fine
previewTutorialVC?.tutorialImageView.translatesAutoresizingMaskIntoConstraints = true
this inside dismiss block fixed the issue. As mentioned here

How to solve this problem with custom transition in iOS?

I built a class to implement a circular transition between view controllers. When I hit the button to navigate to the other view controller a circle starts growing from the button until it fills the screen with the new controller. When I dismiss the view controller I expected this circle to shrink down back to the original position. It's also working. The only problem is that when the dismiss is underway the back of the screen while the circle is shrinking is completely black and after the animation is completed the new viewController appears abruptly.
Here are some photos of the effect:
Here's the code of the custom class:
class customTransition: NSObject, UIViewControllerAnimatedTransitioning{
var duration: TimeInterval = 0.5
var startPoint = CGPoint.zero
var circle = UIView()
var circleColor = UIColor.white
enum transitMode: Int {
case presenting, dismissing
}
var transitionMode: transitMode = .presenting
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView
guard let to = transitionContext.view(forKey: UITransitionContextViewKey.to) else {return}
guard let from = transitionContext.view(forKey: UITransitionContextViewKey.from) else {return}
circleColor = to.backgroundColor ?? UIColor.white
if transitionMode == .presenting {
to.translatesAutoresizingMaskIntoConstraints = false
to.center = startPoint
circle = UIView()
circle.backgroundColor = circleColor
circle.frame = getFrameForCircle(rect: to.frame)
circle.layer.cornerRadius = circle.frame.width / 2
circle.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
circle.alpha = 0
circle.addSubview(to)
to.centerXAnchor.constraint(equalTo: circle.centerXAnchor).isActive = true
to.centerYAnchor.constraint(equalTo: circle.centerYAnchor).isActive = true
to.widthAnchor.constraint(equalToConstant: to.frame.width).isActive = true
to.heightAnchor.constraint(equalToConstant: to.frame.height).isActive = true
container.addSubview(circle)
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.circle.center = from.center
self.circle.transform = CGAffineTransform.identity
self.circle.alpha = 1
}) { (sucess) in
transitionContext.completeTransition(sucess)
}
} else if transitionMode == .dismissing {
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.circle.center = self.startPoint
self.circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
self.circle.alpha = 0
}) { (sucess) in
transitionContext.completeTransition(sucess)
}
}
}
func getFrameForCircle(rect: CGRect) -> CGRect{
let width = Float(rect.width)
let height = Float(rect.height)
let diameter = CGFloat(sqrtf(width * width + height * height))
let x: CGFloat = rect.midX - (diameter / 2)
let y: CGFloat = rect.midY - (diameter / 2)
return CGRect(x: x, y: y, width: diameter, height: diameter)
}
}
and the implementation...
let circularTransition = customTransition()
the call for the present view controller... I tried to set secondVC.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext but when I set this line it ignores completely the animation transition I don't know why...
`
#objc func handlePresent(sender: UIButton){
let secondVC = nextVC()
secondVC.transitioningDelegate = self
present(secondVC, animated: true, completion: nil)
}
delegate methods:
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
circularTransition.startPoint = presentButton.center
circularTransition.transitionMode = .presenting
return circularTransition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
circularTransition.transitionMode = .dismissing
circularTransition.startPoint = presentButton.center
return circularTransition
}
What am I missing here? Any suggestions?
No storyboard being used, just code.
If you don't use navigationController, it's necessary to use the .custom mode in the presentedviewController.
import UIKit
class TransViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
let circularTransition = customTransition()
#IBOutlet var presentButton : UIButton!
#IBAction func handlePresent(sender: UIButton){
if let secondVC = storyboard?.instantiateViewController(withIdentifier: "next"){
secondVC.modalPresentationStyle = .custom
secondVC.transitioningDelegate = self
present(secondVC, animated: true, completion: nil)
}
}
}
class BackViewController: UIViewController {
#IBAction func dismissMe(sender: UIButton){
self.dismiss(animated: true, completion: nil)
}
}
extension TransViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
circularTransition.startPoint = presentButton.center
circularTransition.transitionMode = .presenting
return circularTransition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
circularTransition.transitionMode = .dismissing
circularTransition.startPoint = presentButton.center
return circularTransition
}
}
If there is no from or to view, we have use the from and to view from containView.
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView
var to : UIView!
var from : UIView!
to = transitionContext.view(forKey: UITransitionContextViewKey.to)
if to == nil {to = container}
from = transitionContext.view(forKey: UITransitionContextViewKey.from)
if from == nil {from = container}
The rest is same:
circleColor = to.backgroundColor ?? UIColor.white
if transitionMode == .presenting {
to.translatesAutoresizingMaskIntoConstraints = false
to.center = startPoint
circle = UIView()
circle.backgroundColor = circleColor
circle.frame = getFrameForCircle(rect: to.frame)
circle.layer.cornerRadius = circle.frame.width / 2
circle.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
circle.alpha = 0
circle.addSubview(to)
to.centerXAnchor.constraint(equalTo: circle.centerXAnchor).isActive = true
to.centerYAnchor.constraint(equalTo: circle.centerYAnchor).isActive = true
to.widthAnchor.constraint(equalToConstant: to.frame.width).isActive = true
to.heightAnchor.constraint(equalToConstant: to.frame.height).isActive = true
container.addSubview(circle)
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.circle.center = from.center
self.circle.transform = CGAffineTransform.identity
self.circle.alpha = 1
}) { (sucess) in
transitionContext.completeTransition(sucess)
}
} else if transitionMode == .dismissing {
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.circle.center = self.startPoint
self.circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
self.circle.alpha = 0
}) { (sucess) in
transitionContext.completeTransition(sucess)
}
}
}

Present UIViewController from frame

I am showing image slider as in this order.
UIViewController > UITableview > UITableviewCell > UICollectionview > UICollectionViewCell > UIImage
User can slide UICollectionview and view images.Problem is that I need to do animation. When user tap on my UICollectionviewCell, it should animate from that cell and show full screen as in this library.
https://github.com/suzuki-0000/SKPhotoBrowser
Problem is that I need to use MWPhotoBrowser and I can't present like that.
I am thinking to use hero animation library as well.
https://github.com/lkzhao/Hero
But my view hierachy and their example is different. How shall I do?
You have to use custom transition
Reference:
https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/CustomizingtheTransitionAnimations.html
Code Work
On image selection
add in VC_A
var selectedImage: UIImageView?
let transition = PopAnimator()
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(
alongsideTransition: {context in
self.bgImage.alpha = (size.width>size.height) ? 0.25 : 0.55
self.positionListItems()
},
completion: nil
)
}
//position all images inside the list
func positionListItems() {
let listHeight = listView.frame.height
let itemHeight: CGFloat = listHeight * 1.33
let aspectRatio = UIScreen.main.bounds.height / UIScreen.main.bounds.width
let itemWidth: CGFloat = itemHeight / aspectRatio
let horizontalPadding: CGFloat = 10.0
for i in herbs.indices {
let imageView = listView.viewWithTag(i) as! UIImageView
imageView.frame = CGRect(
x: CGFloat(i) * itemWidth + CGFloat(i+1) * horizontalPadding, y: 0.0,
width: itemWidth, height: itemHeight)
}
listView.contentSize = CGSize(
width: CGFloat(herbs.count) * (itemWidth + horizontalPadding) + horizontalPadding,
height: 0)
}
// On image selection
VC_B.transitioningDelegate = self
present(VC_B, animated: true, completion: nil)
// add extension
extension VC_A: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.originFrame = selectedImage!.superview!.convert(selectedImage!.frame, to: nil)
transition.presenting = true
selectedImage!.isHidden = true
return transition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.presenting = false
return transition
}
}
and animation class
class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let duration = 1.0
var presenting = true
var originFrame = CGRect.zero
var dismissCompletion: (()->Void)?
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)!
let herbView = presenting ? toView : transitionContext.view(forKey: .from)!
let initialFrame = presenting ? originFrame : herbView.frame
let finalFrame = presenting ? herbView.frame : originFrame
let xScaleFactor = presenting ?
initialFrame.width / finalFrame.width :
finalFrame.width / initialFrame.width
let yScaleFactor = presenting ?
initialFrame.height / finalFrame.height :
finalFrame.height / initialFrame.height
let scaleTransform = CGAffineTransform(scaleX: xScaleFactor, y: yScaleFactor)
if presenting {
herbView.transform = scaleTransform
herbView.center = CGPoint(
x: initialFrame.midX,
y: initialFrame.midY)
herbView.clipsToBounds = true
}
containerView.addSubview(toView)
containerView.bringSubview(toFront: herbView)
UIView.animate(withDuration: duration, delay:0.0, usingSpringWithDamping: 0.4,
initialSpringVelocity: 0.0,
animations: {
herbView.transform = self.presenting ?
CGAffineTransform.identity : scaleTransform
herbView.center = CGPoint(x: finalFrame.midX,
y: finalFrame.midY)
},
completion:{_ in
if !self.presenting {
self.dismissCompletion?()
}
transitionContext.completeTransition(true)
}
)
}
}

Display 2 view controllers at the same time with animation

I'm following this awesome video to create a custom transition for my project, because I'm developing for the iPad, so instead of presenting destination view controller full screen, I want to have it occupy half of the screen like this:
My code of the custom transition class is:
class CircularTransition: NSObject {
var circle = UIView()
var startingPoint = CGPoint.zero {
didSet {
circle.center = startingPoint
}
}
var circleColor = UIColor.white
var duration = 0.4
enum circularTransitionMode: Int {
case present, dismiss
}
var transitionMode = circularTransitionMode.present
}
extension CircularTransition: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
if transitionMode == .present {
if let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to) {
var viewCenter = presentedView.center
var viewSize = presentedView.frame.size
if UIDevice.current.userInterfaceIdiom == .pad {
viewCenter = CGPoint(x: viewCenter.x, y: viewSize.height)
viewSize = CGSize(width: viewSize.width, height: viewSize.height)
}
circle = UIView()
circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
circle.layer.cornerRadius = circle.frame.size.width / 2
circle.center = startingPoint
circle.backgroundColor = circleColor
circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
containerView.addSubview(circle)
presentedView.center = startingPoint
presentedView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
presentedView.alpha = 0
containerView.addSubview(presentedView)
UIView.animate(withDuration: duration, animations: {
self.circle.transform = CGAffineTransform.identity
presentedView.transform = CGAffineTransform.identity
presentedView.alpha = 1
presentedView.center = viewCenter
}, completion: {(sucess: Bool) in transitionContext.completeTransition(sucess)})
}
} else {
if let returningView = transitionContext.view(forKey: UITransitionContextViewKey.from) {
let viewCenter = returningView.center
let viewSize = returningView.frame.size
circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
circle.layer.cornerRadius = circle.frame.size.width / 2
circle.center = startingPoint
UIView.animate(withDuration: duration + 0.1, animations: {
self.circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
returningView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
returningView.center = self.startingPoint
returningView.alpha = 0
}, completion: {(success: Bool) in
returningView.center = viewCenter
returningView.removeFromSuperview()
self.circle.removeFromSuperview()
transitionContext.completeTransition(success)
})
}
}
}
func frameForCircle(withViewCenter viewCenter: CGPoint, size viewSize: CGSize, startPoint: CGPoint) -> CGRect {
let xLength = fmax(startingPoint.x, viewSize.width - startingPoint.x)
let yLength = fmax(startingPoint.y, viewSize.height - startingPoint.y)
let offsetVector = sqrt(xLength * xLength + yLength * yLength) * 2
let size = CGSize(width: offsetVector, height: offsetVector)
return CGRect(origin: CGPoint.zero, size: size)
}
}
And the part of code in my view controller:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let secondVC = segue.destination as! ResultViewController
secondVC.transitioningDelegate = self
secondVC.modalPresentationStyle = .custom
}
// MARK: - Animation
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transtion.transitionMode = .dismiss
transtion.startingPoint = calculateButton.center
transtion.circleColor = calculateButton.backgroundColor!
return transtion
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transtion.transitionMode = .present
transtion.startingPoint = calculateButton.center
transtion.circleColor = calculateButton.backgroundColor!
return transtion
}
But the controller shows up full screen.
You may try the two different Container View for half of top and bottom.
then give animation on it...
So I have finished creating my answer, It takes a different approach than the other answers so bear with me.
Instead of adding a container view what I figured would be the best way was to create a UIViewController subclass (which I called CircleDisplayViewController). Then all your VCs that need to have this functionality could inherit from it (rather than from UIViewController).
This way all your logic for presenting and dismissing ResultViewController is handled in one place and can be used anywhere in your app.
The way your VCs can use it is like so:
class AnyViewController: CircleDisplayViewController {
/* Only inherit from CircleDisplayViewController,
otherwise you inherit from UIViewController twice */
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func showCircle(_ sender: UIButton) {
openCircle(withCenter: sender.center, radius: nil, resultDataSource: calculator!.iterateWPItems())
//I'll get to this stuff in just a minute
//Edit: from talking to Bright Future in chat I saw that resultViewController needs to be setup with calculator!.iterateWPItems()
}
}
Where showCircle will present your ResultViewController using the transitioning delegate with the circle center at the sending UIButtons center.
The CircleDisplayViewController subclass is this:
class CircleDisplayViewController: UIViewController, UIViewControllerTransitioningDelegate, ResultDelegate {
private enum CircleState {
case collapsed, visible
}
private var circleState: CircleState = .collapsed
private var resultViewController: ResultViewController!
private lazy var transition = CircularTransition()
func openCircle(withCenter center: CGPoint, radius: CGFloat?, resultDataSource: ([Items], Int, String)) {
let circleCollapsed = (circleState == .collapsed)
DispatchQueue.main.async { () -> Void in
if circleCollapsed {
self.addCircle(withCenter: center, radius: radius, resultDataSource: resultDataSource)
}
}
}
private func addCircle(withCenter circleCenter: CGPoint, radius: CGFloat?, resultDataSource: ([Items], Int, String])) {
var circleRadius: CGFloat!
if radius == nil {
circleRadius = view.frame.size.height/2.0
} else {
circleRadius = radius
}
//instantiate resultViewController here, and setup delegate etc.
resultViewController = UIStoryboard.resultViewController()
resultViewController.transitioningDelegate = self
resultViewController.delegate = self
resultViewController.modalPresentationStyle = .custom
//setup any values for resultViewController here
resultViewController.dataSource = resultDataSource
//then set the frame of resultViewController (while also setting endFrame)
let resultOrigin = CGPoint(x: 0.0, y: circleCenter.y - circleRadius)
let resultSize = CGSize(width: view.frame.size.width, height: (view.frame.size.height - circleCenter.y) + circleRadius)
resultViewController.view.frame = CGRect(origin: resultOrigin, size: resultSize)
resultViewController.endframe = CGRect(origin: resultOrigin, size: resultSize)
transition.circle = UIView()
transition.startingPoint = circleCenter
transition.radius = circleRadius
transition.circle.frame = circleFrame(radius: transition.radius, center: transition.startingPoint)
present(resultViewController, animated: true, completion: nil)
}
func collapseCircle() { //THIS IS THE RESULT DELEGATE FUNCTIONS
dismiss(animated: true) {
self.resultViewController = nil
}
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.transitionMode = .dismiss
transition.circleColor = UIColor.red
return transition
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.transitionMode = .present
transition.circleColor = UIColor.red
return transition
}
func circleFrame(radius: CGFloat, center: CGPoint) -> CGRect {
let circleOrigin = CGPoint(x: center.x - radius, y: center.y - radius)
let circleSize = CGSize(width: radius*2, height: radius*2)
return CGRect(origin: circleOrigin, size: circleSize)
}
}
public extension UIStoryboard {
class func mainStoryboard() -> UIStoryboard { return UIStoryboard(name: "Main", bundle: Bundle.main) }
}
private extension UIStoryboard {
class func resultViewController() -> ResultViewController {
return mainStoryboard().instantiateViewController(withIdentifier: "/* Your ID for ResultViewController */") as! ResultViewController
}
}
The only function that is called by the VCs that inherit from DisplayCircleViewController is openCircle, openCircle has a circleCenter argument (which should be your button center I'm guessing), an optional radius argument (if this is nil then a default value of half the view height is taken, and then whatever else you need to setup ResultViewController.
In the addCircle func there is some important stuff:
you setup ResultViewController however you have to before presenting (like you would in prepare for segue),
then setup the frame for it (I tried to make it the area of the circle that is visible but it is quite rough here, might be worth playing around with),
then this is where I reset the transition circle (rather than in the transition class), so that I could set the circle starting point, radius and frame here.
then just a normal present.
If you haven't set an identifier for ResultViewController you need to for this (see the UIStoryboard extensions)
I also changed the TransitioningDelegate functions so you don't set the circle center, this is because to keep it generic I put that responsibility to the ViewController that inherits from this one. (see top bit of code)
Finally I changed the CircularTransition class
I added a variable:
var radius: CGFloat = 0.0 //set in the addCircle function above
and changed animateTransition:
(removed the commented out lines):
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
if transitionMode == .present {
if let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to) {
...
// circle = UIView()
// circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
circle.layer.cornerRadius = radius
...
}
} else {
if let returningView = transitionContext.view(forKey: UITransitionContextViewKey.from) {
...
// circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
...
}
}
}
Finally I made a protocol so that ResultViewController could dismiss the circle
protocol ResultDelegate: class {
func collapseCircle()
}
class ResultViewController: UIViewController {
weak var delegate: ResultDelegate!
var endFrame: CGRect!
var dataSource: ([Items], Int, String)! // same as in Bright Future's case
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidLayoutSubviews() {
if endFrame != nil {
view.frame = endFrame
}
}
#IBAction func closeResult(_ sender: UIButton) {
delegate.collapseCircle()
}
}
This has turned out to be quite a huge answer, sorry about that, I wrote it in a bit a of rush so if anything is not clear just say.
Hope this helps!
Edit: I found the problem, iOS 10 has changed the way they layout views, so to fix this I added an endFrame property to ResultViewController and set it's views frame to that in viewDidLayoutSubviews. I also set both the frame and endFrame at the same time in addCircle. I changed the code above to reflect the changes. It's not ideal but I'll have another look later to see if there is a better fix.
Edit: this is what it looks like open for me
Thanks to everyone for the suggestions, I tried to use a container view, here's how I did it:
First I added a containerView property in CircularTransition class:
class CircularTransition: NSObject {
...
var containerView: UIView
init(containerView: UIView) {
self.containerView = containerView
}
...
}
Then commented out these code in its extension:
// let containerView = transitionContext.containerView
// if UIDevice.current.userInterfaceIdiom == .pad {
// viewCenter = CGPoint(x: viewCenter.x, y: viewSize.height)
// viewSize = CGSize(width: viewSize.width, height: viewSize.height)
// }
In my mainViewController, I added a method to add a container view:
func addContainerView() {
let containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(containerView)
NSLayoutConstraint.activate([
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
containerView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5),
containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
])
transtion.containerView = containerView
}
The reason I don't use story board is, if I put the animated view controller (ResultViewController) in the container view, it gets loaded whenever mainViewController is loaded, however, ResultViewController needs the data from prepareForSegue, thus it'll crash.
Then I changed a little bit in prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
transtion.containerView = view
if UIDevice.current.userInterfaceIdiom == .pad {
addContainerView()
}
let secondVC = segue.destination as! ResultViewController
secondVC.transitioningDelegate = self
secondVC.modalPresentationStyle = .custom
secondVC.dataSource = calculator!.iterateWPItems().0
}
And created CircularTransition class this way in mainViewController:
let transtion = CircularTransition(containerView: UIView())
That's basically all I did, I could display the gorgeous dual vc view
on the iPad, however, the return transition doesn't work, I still
haven't figured out what caused that.
Hi i did some changes in your animateTransition method try this out. You might have to play a little bit with withRelativeStartTime of the animations and the frame and center to perfect the animation. But i guess this should get you started.
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
if transitionMode == .present {
if let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to) {
var viewCenter = presentedView.center
var viewSize = presentedView.frame.size
if UIDevice.current.userInterfaceIdiom == .pad {
viewCenter = CGPoint(x: viewCenter.x, y: viewSize.height)
viewSize = CGSize(width: viewSize.width, height: viewSize.height)
}
circle = UIView()
circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
circle.layer.cornerRadius = circle.frame.size.width / 2
circle.center = startingPoint
circle.backgroundColor = circleColor
circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
circle.layer.masksToBounds = true
containerView.addSubview(circle)
presentedView.center = startingPoint
presentedView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
presentedView.alpha = 0
containerView.addSubview(presentedView)
UIView.animateKeyframes(withDuration: duration, delay: 0, options: .calculationModeLinear, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1, animations: {
self.circle.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
presentedView.alpha = 1
})
UIView.addKeyframe(withRelativeStartTime: 0.19, relativeDuration: 1, animations: {
presentedView.transform = CGAffineTransform(scaleX: 1, y: 1)
presentedView.frame = CGRect(x: 0, y: (containerView.frame.size.height / 2)+10, width: containerView.frame.size.width, height: containerView.frame.size.height*0.5)
})
}, completion: { (sucess) in
transitionContext.completeTransition(sucess)
})
}
} else {
if let returningView = transitionContext.view(forKey: UITransitionContextViewKey.from) {
let viewCenter = returningView.center
let viewSize = returningView.frame.size
circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
circle.layer.cornerRadius = circle.frame.size.width / 2
circle.center = startingPoint
UIView.animate(withDuration: duration + 0.1, animations: {
self.circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
returningView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
returningView.center = self.startingPoint
returningView.alpha = 0
}, completion: {(success: Bool) in
returningView.center = viewCenter
returningView.removeFromSuperview()
self.circle.removeFromSuperview()
transitionContext.completeTransition(success)
})
}
}
}
Hope this helps.

Resources