Swift, animate to specific CGPoint - ios

How could I animate programatically created object to go to a specific CGPoint. Not using CGAffineTransformMakeTranslation, as this is just on how to move it, not exactly where to move it.
I have tried: translatesAutoresizingMaskIntoConstraints = true
But it makes no difference.
I have tried this:
//Define screen sizes
let screenSize: CGRect = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
// Sizes of centre cards
let centreCardWidth = screenWidth/5
let centreCardHeight = (screenWidth/5) / 0.625
let centralizeViewHorizontalCard = (screenWidth/2) - ((screenWidth/5) / 2)
let centralizeViewVerticleCard = screenHeight / 2
UIView.animateWithDuration(0.25, delay: 0.5, options: [], animations: {
centreCard3.frame = CGRectMake(centralizeViewHorizontalCard, centralizeViewVerticleCard, centreCard3.frame.size.width, centreCard3.frame.size.height)
}, completion: nil)
But when run in the simulator, absolutely nothing happened.

Your UI hasn't rendered yet when you are animating. Put it in viewDidAppear. This works for me:
class ViewController: UIViewController {
#IBOutlet weak var centreCard3: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
let screenSize: CGRect = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
// Sizes of centre cards
let centralizeViewHorizontalCard = (screenWidth/2) - ((screenWidth/5) / 2)
let centralizeViewVerticleCard = screenHeight / 2
UIView.animateWithDuration(0.5, delay: 0.5, options: [], animations: {
self.centreCard3.frame = CGRectMake(centralizeViewHorizontalCard, centralizeViewVerticleCard, self.centreCard3.frame.size.width, self.centreCard3.frame.size.height)
}, completion: nil)
}
}
Result:

Related

Why does my UI button stop responding after a certain amount of time has elapsed?

My code is supposed to print out the current number of taps recorded, which it does do starting out, but then it randomly stops. Any tips on fixing this?
I think it has something to do with the animation, but I'm not sure what about it is causing the app to stop responding.
import UIKit
class ViewController: UIViewController {
var taps = 0 // tracks number of screen taps
override func viewDidLoad() {
super.viewDidLoad()
// get screen dimensions (in points) for current device
let screenBounds = UIScreen.main.bounds
let screenWidth = screenBounds.width
let screenHeight = screenBounds.height
// create rect
let rect = CGRect(x: 0, y: screenHeight, width: screenWidth, height: screenHeight)
let view = UIView(frame: rect)
view.backgroundColor = .red
self.view.addSubview(view)
// rising animation
UIView.animate(withDuration: 5.0, delay: 0.0, options: .curveEaseInOut, animations: {
view.frame.origin.y = CGFloat(0)
}, completion: { finished in
})
}
// when user taps screen
#IBAction func screenTapped(_ sender: Any) {
taps += 1 // increment number of taps
print(taps)
}
}
Try this
import UIKit
class ViewController: UIViewController {
var taps = 0 // tracks number of screen taps
override func viewDidLoad() {
super.viewDidLoad()
// get screen dimensions (in points) for current device
let screenBounds = UIScreen.main.bounds
let screenWidth = screenBounds.width
let screenHeight = screenBounds.height
// create rect
let rect = CGRect(x: 0, y: screenHeight, width: screenWidth, height: screenHeight)
let view = UIView(frame: rect)
view.backgroundColor = .red
self.view.addSubview(view)
//Add Tap gesture
let tap = UITapGestureRecognizer(target: self, action: #selector(screenTapped))
self.view.addGestureRecognizer(tap)
// rising animation
UIView.animate(withDuration: 5.0, delay: 0.0, options: .curveEaseInOut, animations: {
view.frame.origin.y = CGFloat(0)
}, completion: { finished in
})
}
// when user taps screen
#objc func screenTapped() {
taps += 1 // increment number of taps
print(taps)
}
}

UIScrollview Animation Transition From View To View

I am trying to perform a transition animation whenever a user scrolls on a paginated view, i.e: From Page 1 to Page 2.
Unfortunately, I've not been able to replicate that.
Attach below is what I've done:
class OnboardingParallaxImageView: BaseUIView, UIScrollViewDelegate {
let allImages = [#imageLiteral(resourceName: "onboarding_handshake_icon"), #imageLiteral(resourceName: "onboarding_paylinc_icon")]
var activeCurrentPage = 1
let bgView: UIImageView = {
let image = #imageLiteral(resourceName: "onboard_bg_gradient")
let view = UIImageView(image: image)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let firstImageView: UIImageView = {
let view = UIImageView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let secondImageView: UIImageView = {
let view = UIImageView()
view.isHidden = true
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var firstImageHeightAnchor: NSLayoutConstraint?
var firstImageWidthAnchor: NSLayoutConstraint?
var secondImageHeightAnchor: NSLayoutConstraint?
var secondImageWidthAnchor: NSLayoutConstraint?
override func setupViews() {
super.setupViews()
addSubview(bgView)
addSubview(firstImageView)
addSubview(secondImageView)
bgView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
bgView.heightAnchor.constraint(equalTo: heightAnchor).isActive = true
bgView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
bgView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
firstImageWidthAnchor = firstImageView.widthAnchor.constraint(equalTo: widthAnchor)
firstImageWidthAnchor?.isActive = true
firstImageHeightAnchor = firstImageView.heightAnchor.constraint(equalTo: heightAnchor)
firstImageHeightAnchor?.isActive = true
firstImageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
firstImageView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
firstImageView.image = allImages[0]
secondImageWidthAnchor = secondImageView.widthAnchor.constraint(equalTo: widthAnchor)
secondImageWidthAnchor?.isActive = true
secondImageHeightAnchor = secondImageView.heightAnchor.constraint(equalTo: heightAnchor)
secondImageHeightAnchor?.isActive = true
secondImageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
secondImageView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
secondImageView.image = allImages[1]
}
override func layoutSubviews() {
super.layoutSubviews()
let frameWidth = frame.size.width
secondImageHeightAnchor?.constant = -(frameWidth - 32)
secondImageWidthAnchor?.constant = -(frameWidth - 32)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offSet = scrollView.contentOffset.x
let frameWidth = frame.size.width / 2
let toUseConstant = (CGFloat(abs(offSet)) / frameWidth)
if activeCurrentPage == 1 {
if offSet <= 0 {
firstImageHeightAnchor?.constant = 0
firstImageWidthAnchor?.constant = 0
firstImageView.isHidden = false
secondImageView.isHidden = true
} else {
firstImageHeightAnchor?.constant += -(toUseConstant)
firstImageWidthAnchor?.constant += -(toUseConstant)
firstImageView.isHidden = false
secondImageHeightAnchor?.constant += -(toUseConstant)
secondImageWidthAnchor?.constant += -(toUseConstant)
secondImageView.isHidden = false
secondImageView.alpha = toUseConstant
}
}
UIView.animate(withDuration: 0.5) {
self.layoutSubviews()
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.activeCurrentPage = scrollView.currentPage
}
}
This is the result of what I've been able to achieve:
How can I go about transitioning from A to B without any funny behaviour.
Thanks
The easiest way to achieve smooth behaviour is using UIViewPropertyAnimator.
Setup the initial values for each image view:
override func viewDidLoad() {
super.viewDidLoad()
firstImageView.image = // your image
secondImageView.image = // your image
secondImageView.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
secondImageView.alpha = 0
}
Then create property animator for each imageView
lazy var firstAnimator: UIViewPropertyAnimator = {
// You can play around with the duration, curve, damping ration, timing
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeIn, animations: {
self.firstImageView.image.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
self.firstImageView.image.alpha = 0
})
return animator
}()
and the second one
lazy var secondAnimator: UIViewPropertyAnimator = {
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeIn, animations: {
self.secondImageView.transform = CGAffineTransform(scaleX: 1, y: 1)
self.secondImageView.alpha = 1
})
return animator
}()
now on scrollViewDidScroll when you have calculated the completed percent just update the animators:
firstAnimator.fractionComplete = calculatedPosition
secondAnimator.fractionComplete = calculatedPosition
You can apply the same approach for multiple views
You might want to consider using a Framework for this instead of reinventing the wheel. Something like Hero for example.
There as some basic examples to get you started. Essentially this provides you an easy to use option to code transition behavior between two UIViewControllers by defining how each UI-Component should behave base on the transition progress.
I have achieved it using the keyframe animation on ImageView's frame. But you can also achieve it using the constraint based animation as you tried. Just replace the frames size variations that I have done here with the change in constraint's constants as per your convenience.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var imgView1: UIImageView! //HANDSHAKE image on TOP
#IBOutlet weak var imgView2: UIImageView! //CREDIT_CARD and PHONE image
#IBOutlet weak var imgView3: UIImageView! //SINGLE_HAND image
let minimumFrameSize = CGSize(width: 0.0, height: 0.0)
let meetingPointFrameSize = CGSize(width: 150.0, height: 150.0)
let maximumFrameSize = CGSize(width: 400.0, height: 400.0)
override func viewDidLoad() {
super.viewDidLoad()
let centerOfImages = CGPoint(x: self.view.frame.width/2 , y: self.view.frame.height/2)
//initial state
self.imgView1.frame.size = maximumFrameSize
self.imgView1.alpha = 1.0
self.imgView2.frame.size = minimumFrameSize
self.imgView2.alpha = 0.0
self.imgView3.frame.size = minimumFrameSize
self.imgView3.alpha = 0.0
self.imgView1.center = centerOfImages
self.imgView2.center = centerOfImages
self.imgView3.center = centerOfImages
UIView.animateKeyframes(withDuration: 5.0, delay: 2.0, options: [UIView.KeyframeAnimationOptions.repeat,
UIView.KeyframeAnimationOptions.calculationModeLinear],
animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25, animations: {
self.imgView1.frame.size = self.meetingPointFrameSize
self.imgView1.alpha = 0.3
self.imgView1.center = centerOfImages
self.imgView2.frame.size = self.meetingPointFrameSize
self.imgView2.alpha = 0.3
self.imgView2.center = centerOfImages
/*image 1 and image 2 meets at certain point where
image 1 is decreasing its size and image 2 is increasing its size simultaneously
*/
})
UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.25, animations: {
self.imgView1.frame.size = self.minimumFrameSize
self.imgView1.alpha = 0.0
self.imgView1.center = centerOfImages
self.imgView2.frame.size = self.maximumFrameSize
self.imgView2.alpha = 1.0
self.imgView2.center = centerOfImages
/* image 1 has decreased its size to zero and
image 2 has increased its size to maximum simultaneously
*/
})
UIView.addKeyframe(withRelativeStartTime: 0.4, relativeDuration: 0.1, animations: {
self.imgView2.frame.size = self.maximumFrameSize
self.imgView2.alpha = 1.0
self.imgView2.center = centerOfImages
/* Hold for a moment
*/
})
UIView.addKeyframe(withRelativeStartTime: 0.6, relativeDuration: 0.15, animations: {
self.imgView2.frame.size = self.meetingPointFrameSize
self.imgView2.alpha = 0.3
self.imgView2.center = centerOfImages
self.imgView3.frame.size = self.meetingPointFrameSize
self.imgView3.alpha = 0.3
self.imgView3.center = centerOfImages
/*image 2 and image 3 meets at certain point where
image 2 is decreasing its size and image 3 is increasing its size simultaneously
*/
})
UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25, animations: {
self.imgView2.frame.size = self.minimumFrameSize
self.imgView2.alpha = 0.0
self.imgView2.center = centerOfImages
self.imgView3.frame.size = self.maximumFrameSize
self.imgView3.alpha = 1.0
self.imgView3.center = centerOfImages
/* image 2 has decreased its size to zero and
image 3 has increased its size to maximum simultaneously
*/
})
}) { (finished) in
/*If you have any doubt or need more enhancement or in case you need my project. Feel free to ask me.
Please ping me on skype/email:
ojhashubham29#gmail.com
or connect me on Twitter
#hearthackman
*/
}
}
}

How to detect a touch on a moving (animation) UIImageView?

I'm trying to add UITapGestureRecognizer on a UIImageView while its moving but the clic is only working when the animation is static.
I've looked everywhere on the internet but couldn't find a solution.
Is there a solution to this problem?
Here's the code I'm working with: I'm creating 10 "bubbles" containing a round image. I animate them so they're moving up and fading. I'd like to be able to click on them and get their tag (int form 1 to 10).
#IBAction func startBubble(sender: AnyObject) {
//var bubble = UIView()
let screenSize = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
// Animation parameters
let duration : NSTimeInterval = 9.0
var delay : NSTimeInterval = 1.0
var minimumDelay : NSTimeInterval = 3.0
let factor : CGFloat = 1.75
let opacity : CGFloat = 0.3
let options: UIViewAnimationOptions = [.CurveEaseIn, .AllowUserInteraction]
for bubbleNumber in 1...10 {
// Bubble size
let bubbleWidth : CGFloat = CGFloat(arc4random_uniform(40) + 130)
let bubbleHeight = bubbleWidth
// Bubble start position
let startPosition : CGFloat = CGFloat(arc4random_uniform(UInt32(screenWidth - bubbleWidth)))
// Bubble delay
if bubbleNumber > 1 {
minimumDelay = minimumDelay + NSTimeInterval(3.0)
delay = minimumDelay + (NSTimeInterval(arc4random_uniform(1000)) / 1000)
}
let bubble = UIImageView(frame: CGRectMake(startPosition, screenHeight, bubbleWidth, bubbleHeight))
// Bubble definition
bubble.image = UIImage(named: "bubble.png")
bubble.tag = bubbleNumber
bubble.userInteractionEnabled = true
bubble.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "openBubble:"))
self.view.addSubview(bubble)
UIView.animateWithDuration(duration, delay: delay, options: options, animations: {
bubble.frame = CGRectMake(startPosition, 1, factor*bubbleWidth, factor*bubbleHeight)
bubble.alpha = opacity
}, completion: { animationFinished in
bubble.alpha = 0.1
bubble.removeFromSuperview()
})
}
}
func openBubble(sender:UITapGestureRecognizer){
let tappedBubble = sender.view!
print(tappedBubble.tag)
}

UIStoryboardSegue: top view is not sliding down

I have the following swift code and what i am trying to achieve is a segue that slides off the top. I want the secondVCView to be below the firstVCView and for the firstVCView to slide off displaying the secondVCView.
Currently there is no animation and it just flashes to the secondVCView.
Many Thanks
import UIKit
class TopToBottomSegue: 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, screenWidth, screenHeight)
// Access the app's key window and insert the destination view below the current (source) one.
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(secondVCView, belowSubview: firstVCView)
// Animate the transition.
UIView.animateWithDuration(0.4, animations: { () -> Void in
firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, screenHeight)
//secondVCView.frame = CGRectOffset(secondVCView.frame, -screenWidth, 0.0)
}) { (Finished) -> Void in
self.sourceViewController.presentViewController(self.destinationViewController as! UIViewController,
animated: false,
completion: nil)
}
}
}
As far as I know you need to call layoutIfNeed inside animateWithduration block in order to get a smooth animation.
Greetings
Im not sure if this is the correct way to do this but i managed to get it working by inserting both views.
L
--
import UIKit
class TopToBottomSegue: UIStoryboardSegue {
override func perform() {
// Assign the source and destination views to local variables.
let firstVCView = self.sourceViewController.view as UIView!
let secondVCView = self.destinationViewController.view as UIView!
// Get the screen width, height and status bar height.
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
let statusBarHeight = self.statusBarHeight()
// Specify the initial position of both views.
secondVCView.frame = CGRectMake(0.0, 0.0, screenWidth, screenHeight)
firstVCView.frame = CGRectMake(0.0, statusBarHeight, screenWidth, screenHeight)
// Access the app's key window and insert the destination view below the current (source) one.
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(firstVCView, aboveSubview: secondVCView)
window?.insertSubview(secondVCView, belowSubview: firstVCView)
// Animate the transition.
UIView.animateWithDuration(0.3, animations: { () -> Void in
firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, screenHeight)
}) { (Finished) -> Void in
self.sourceViewController.presentViewController(self.destinationViewController as UIViewController,
animated: false,
completion: nil)
}
}
func statusBarHeight() -> CGFloat {
let statusBarSize = UIApplication.sharedApplication().statusBarFrame.size
return Swift.min(statusBarSize.width, statusBarSize.height)
}
}

How to make a custom transition behave like a fall under the gravity?

The effect could be implemented like the following code in animateTransition method:
UIView.animateWithDuration(duration,
delay: 0,
usingSpringWithDamping: 0.3,
initialSpringVelocity: 0.0,
options: .CurveLinear,
animations: {
fromVC.view.alpha = 0.5
toVC.view.frame = finalFrame
},
completion: {_ -> () in
fromVC.view.alpha = 1.0
transitionContext.completeTransition(true)
})
But how could I implement it using gravity and collision behaviors(UIGravityBehavior, UICollisionBehavior)?
And a more general question may be "How to use the UIDynamicAnimator to customize the transitions between UIViewControllers?"
You can find the solution under the post Custom view controller transitions with UIDynamic behaviors by dasdom.
And the Swift code:
func transitionDuration(transitionContext: UIViewControllerContextTransitioning!) -> NSTimeInterval {
return 1.0
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning!) {
// 1. Prepare for the required components
let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
let finalFrame = transitionContext.finalFrameForViewController(toVC)
let containerView = transitionContext.containerView()
let screenBounds = UIScreen.mainScreen().bounds
// 2. Make toVC at the top of the screen
toVC.view.frame = CGRectOffset(finalFrame, 0, -1.0 * CGRectGetHeight(screenBounds))
containerView.addSubview(toVC.view)
// 3. Set the dynamic animators used by the view controller presentation
var animator: UIDynamicAnimator? = UIDynamicAnimator(referenceView: containerView)
let gravity = UIGravityBehavior(items: [toVC.view])
gravity.magnitude = 10
let collision = UICollisionBehavior(items: [toVC.view])
collision.addBoundaryWithIdentifier("GravityBoundary",
fromPoint: CGPoint(x: 0, y: screenBounds.height),
toPoint: CGPoint(x: screenBounds.width, y: screenBounds.height))
let animatorItem = UIDynamicItemBehavior(items: [toVC.view])
animatorItem.elasticity = 0.5
animator!.addBehavior(gravity)
animator!.addBehavior(collision)
animator!.addBehavior(animatorItem)
// 4. Complete the transition after the time of the duration
let nsecs = transitionDuration(transitionContext) * Double(NSEC_PER_SEC)
let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(nsecs))
dispatch_after(delay, dispatch_get_main_queue()) {
animator = nil
transitionContext.completeTransition(true)
}
}
A little more complicated than using animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion: method.
EDIT: Fixed a bug when 'transitionDuration' is ≤1

Resources