i'm working on a UIButton animation where it moves along x-axis while it scales to its original size from the initial size. when i tried the code below it doesnt move to the point where it should go and doesn't scale up to its original size.
this is my code for initializing the button:
_testBtn1.layer.anchorPoint = CGPointMake(0.5f, 0.5f);
scaleBtn = CGAffineTransformMakeScale(0.2, 0.2);
[_testBtn1 setTransform: scaleBtn];
and this is my code for the moving/translation and scaling:
CGAffineTransform translate = CGAffineTransformMakeTranslation(50.0f, 0.0f);
CGAffineTransform animate = CGAffineTransformConcat(scaleBtn, translate);
[_testBtn1 setTransform:animate];
any help, suggestion, advice will be appreciated. i'm new to iOS..thanks!
You can just create a custom type UIButton (either with IB by changing the type to Custom, or programmatically with
UIButton *aButton = [UIButton buttonWithType:UIButtonTypeCustom];//set the button's image with
[aButton setImage:[UIImage imageNamed:#"abc" forState:UIControlStateNormal];
To move it, just animate its position normally...
[UIView animateWithDuration:0.5 animations:^{aButton.center = newCenter;}];
Or
CGRect originalFrame = aButton.frame;
aButton.frame = CGRectMake(originalFrame.origin.x, originalFrame.origin.y, originalFrame.size.width, 0);
[UIView animateWithDuration:0.5 animations:^{aButton.frame = originalFrame;}];
OR Reffer this link
http://objectiveapple.blogspot.in/2011/10/23-quizapp-16-animate-scale-uibutton.html
This should be easily done with Core Animation, take a look the the most basic one CABasicAnimation
A simple scale(bouncy) animation with completion handler implementation in swift.
Hope it'll help you or anyone else in some way.
viewToanimate.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
UIView.animate(withDuration: 0.7,
delay: 0,
usingSpringWithDamping: 0.2,
initialSpringVelocity: 6.0,
animations: { _ in
viewToAnimate.transform = .identity
},
completion: { _ in
//Implement your awesome logic here.
})
I made this animation:
With this reusable class:
class AnimatedButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
addTargets()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
addTargets()
}
func addTargets() {
self.addTarget(self, action: #selector(scaleDownAnimated), for: .touchDown)
self.addTarget(self, action: #selector(scaleBackToNormalAnimated), for: [.touchUpOutside, .touchCancel, .touchUpInside])
}
#objc func scaleDownAnimated() {
UIView.animate(withDuration: 0.25, delay: 0, options: .allowUserInteraction) {
self.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
} completion: { _ in }
}
#objc func scaleBackToNormalAnimated() {
UIView.animate(withDuration: 0.2, delay: 0, options: .allowUserInteraction) {
self.transform = CGAffineTransform(scaleX: 1, y: 1)
} completion: { _ in }
}
}
Then just make the class of your button AnimatedButton like below (if you are using Storyboards) and use the button normally:
Related
I'm building a calculator app, when I tap on the buttons there is a short animation. The problem is the buttons do not respond while they are animating, which makes the app feel laggy.
I found some solutions for Objective-C using:
UIViewAnimationOptionAllowUserInteraction
but nothing for Swift 3, my code looks like this:
func myButton () {
sender.layer.backgroundColor = firstColor
sender.setTitleColor(UIColor.white, for: UIControlState.normal)
UIView.animate(withDuration: 0.5) {
sender.layer.backgroundColor = secondColor
}
}
Call different method of UIView:
func myButton () {
sender.layer.backgroundColor = firstColor
sender.setTitleColor(UIColor.white, for: UIControlState.normal)
UIView.animate(withDuration: 0.5,
delay: 0.0,
options: .allowUserInteraction,
animations: {
sender.layer.backgroundColor = secondColor },
completion: nil)
}
UIControlEventTouchDragExit is
"an event where a finger is dragged from within a control to outside
its bounds"
UIControlEventTouchDragEnter is
"an event where a finger is dragged into the bounds of the control"
If I simulate a constant drag down, essentially exiting the bounds of the control once, why are touchDragExit and touchDragEnter being called multiple times?
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let btn = CustomButton(frame: CGRect(x: 100, y: 100, width: 100, height: 100), image:UIImage())
btn.setTitle("", for: .normal)
btn.backgroundColor = UIColor.green
self.view.addSubview(btn)
}
}
class CustomButton: UIButton {
init(frame: CGRect, image:UIImage?) {
super.init(frame: frame)
self.addTargets()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
private func addTargets() {
self.addTarget(self, action: #selector(self.touchDown), for: UIControlEvents.touchDown)
self.addTarget(self, action: #selector(self.touchUpInside), for: UIControlEvents.touchUpInside)
self.addTarget(self, action: #selector(self.touchDragExit), for: UIControlEvents.touchDragExit)
self.addTarget(self, action: #selector(self.touchDragEnter), for: UIControlEvents.touchDragEnter)
self.addTarget(self, action: #selector(self.touchCancel), for: UIControlEvents.touchCancel)
}
func touchDown() {
print("touched down")
UIView.animate(withDuration: 0.05, animations: {
self.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
},completion: nil)
}
func touchUpInside() {
print("touch up inside")
UIView.animate(withDuration: 0.7, delay: 0.0, usingSpringWithDamping: 0.2, initialSpringVelocity: 9.0, options: [.curveEaseInOut, .allowUserInteraction], animations: {
self.transform = CGAffineTransform.identity
}, completion: nil)
}
func touchDragExit() {
print("touch drag exit")
UIView.animate(withDuration: 0.7, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: [.curveEaseInOut], animations: {
self.transform = CGAffineTransform.identity
}, completion: nil)
}
func touchDragEnter() {
print("touch drag enter")
UIView.animate(withDuration: 0.05, animations: {
self.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
},completion: nil)
}
func touchCancel() {
print("touch canceled")
UIView.animate(withDuration: 0.05) {
self.transform = CGAffineTransform.identity
}
}
}
Consider the size of the end of your finger compared to the size of a pixel (particularly on a Retina display). There's a lot of room for error in that relative difference. The OS has to make some estimations to figure out exactly where your finger is "pointing" on the screen and as you wiggle your finger that estimation might change slightly. As a result figuring out whether your finger is inside or outside of a one pixel boundary can be a bit tough and some fluctuation is reasonable.
I have an image of what symbolises a heartbeat
and I'm trying to loop it in my subview so it continuously appears on my view if you understand what i mean. So far I have got it to appear and move across infinitely on my subview however it isn't continuous as it only appears again from the left after it has completely gone out of the view on the right where as I want it to appear again as soon as it has completely entered the view so it symbolises an actual heartbeat. My code looks something like this on my viewDidLoad:
self.heartbeatLoading.frame.origin = CGPoint(x: 0 - heartbeatLoading.frame.size.width, y: 10)
let animation: UIViewAnimationOptions = [.repeat, .curveLinear]
UIView.animate(withDuration: 5.0, delay: 0, options: animation, animations: {
self.heartbeatLoading.frame = self.heartbeatLoading.frame.offsetBy(dx: self.view.frame.width + self.heartbeatLoading.frame.width, dy: 0.0)
}, completion: nil)
Take one UIView and set this class as a super view. Call start animation and stop the animation whenever required.
#IBDesignable class Pulse : UIView
{
var animatingView : UIView?
#IBInspectable var pulseImage: UIImage = UIImage(named: "defaultImage")!{
didSet {
animatingView = UIView(frame: self.bounds)
let imgView = UIImageView(frame : self.bounds)
animatingView?.clipsToBounds = true
animatingView?.addSubview(imgView)
self.addSubview(animatingView!)
}
}
func startAnimationWith(duration : Double = 0.5)
{
if animatingView != nil
{
animatingView?.frame = CGRect(x: 0, y: 0, width: 0, height: (animatingView?.frame.size.height)!)
UIView.animate(withDuration: duration, delay: 0, options: .repeat, animations: {
self.animatingView?.frame = CGRect(x: 0, y: 0, width: (self.animatingView?.frame.size.width)!, height: (self.animatingView?.frame.size.height)!)
}, completion: { (isDone) in
})
}
}
func stopAnimation()
{
if animatingView != nil
{
self.animatingView!.layer.removeAllAnimations()
}
}
override init(frame : CGRect) {
super.init(frame : frame)
}
convenience init() {
self.init(frame:CGRect.zero)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
I have a UIViewController subclass that contains a UITableView, a subclass of UIView called ICYCollapsableInputView, and another UIView subclass called ICYHeaderVIew.
ICYHeaderView is the rounded off looking UIView shown in the screenshots. It contains a round button - the one with the green "add" (+) button.
ICYCollapsableInputView is a view that will for now have a UITextField and a Save button. I want to be able to expand and collapse this view from the UIViewController by calling the appropriate functions of the ICYCollapsableInputView.
When the user taps the round button on the ICYHeaderView, Ithe ICYCollapsableInputView is supposed to expand in height, pushing down the UITableView. I have the ICYCollapsableInputView expanding and collapsing, but I can't figure out how to get the UITableView to respond to the changes. (See Animation)
I colored the main view in the xib with a RED background and the view I added in it with a BLUE background (see screenshot)
I can resize the ICYCollapsableInputView (as seen in the code and animation) using its own height constraint, but the UITableView constraint seems to ignore it.
Here is the code from ICYCollapsableInputView:
class ICYCollapsableInputView: UIView {
var view: UIView!
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
#IBInspectable public var collapsedHeight: CGFloat = 150 {
didSet {
self.heightConstraint.constant = collapsedHeight
}
}
#IBInspectable public var expandedHeight: CGFloat = 250 {
didSet {
self.heightConstraint.constant = expandedHeight
}
}
// MARK: - Init
func loadFromNib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: "ICYCollapsableInputView", bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView
return view
}
override init(frame: CGRect) {
// 1. setup any properties here
// 2. call super.init(frame:)
super.init(frame: frame)
// 3. Setup view from .xib file
xibSetup()
}
required init?(coder aDecoder: NSCoder) {
// 1. setup any properties here
// 2. call super.init(coder:)
super.init(coder: aDecoder)
// 3. Setup view from .xib file
xibSetup()
}
func xibSetup() {
view = loadFromNib()
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
self.heightConstraint.constant = self.collapsedHeight
var rect = self.frame
rect.size.height = self.collapsedHeight
self.frame = rect
self.view.frame = rect
}
func revealInputView() {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
var rect = self.frame
rect.size.height = self.expandedHeight
self.frame = rect
self.heightConstraint.constant = self.expandedHeight
self.view.layoutIfNeeded()
}, completion: nil)
}
func closeInputView() {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
self.heightConstraint.constant = self.collapsedHeight
var rect = self.frame
rect.size.height = self.collapsedHeight
self.frame = rect
self.view.layoutIfNeeded()
}, completion: nil)
}
}
All you need ia just changing the height constraints.
func revealInputView() {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
self.heightConstraint.constant = self.expandedHeight //or 250
self.view.layoutIfNeeded()
}, completion: nil)
}
func closeInputView() {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
self.heightConstraint.constant = self.collapsedHeight //or 150
self.view.layoutIfNeeded()
}, completion: nil)
}
And Constraints in Storyboard should be following
for inputVW
Trailaing space to superview - 0 , leading space to superview - 0, top space to superview - 0 , bottom space to tableview - 0
for TableView
top space to inputVw - 0, leading space to superview - 0, trailing space to super view - 0, bottom space to superview - 0
I have a view with a scroll view in my app (a percentage calculator). I am trying to animate the view to fly out and then fly in again from the left albeit with different contents, on the press of a button.
Here's my code:
func next()
{
UIView.animateWithDuration(0.5, animations: {
self.mainView.frame = CGRectMake(-500, self.view.bounds.height/2-self.mainHeight/2, self.mainWidth, self.mainHeight)
self.mainView.center.x -= 600
}) { (success) in
self.mainView.center.x += 1200
}
UIView.animateWithDuration(0.5, animations: {
self.mainView.frame = CGRectMake(self.view.bounds.width/2-self.mainWidth/2, self.view.bounds.height/2-self.mainHeight/2, self.mainWidth, self.mainHeight)
}) { (success) in
print("Animation Successful!")
self.drawView()
}
}
I don't understand why, but the animation doesn't occur, the view disappears completely and I see "Animation Successful" in the logs.
To be clear, I have created the view programmatically.
Here's the code in viewDidLoad(); the view displays correctly when it's called:
mainHeight = self.view.bounds.height * 0.85
mainWidth = self.view.bounds.width * 0.85
let viewHeight = self.view.bounds.height
let viewWidth = self.view.bounds.width
mainView.frame = CGRectMake(viewWidth/2-mainWidth/2, viewHeight/2-mainHeight/2, mainWidth, mainHeight)
mainView.layer.cornerRadius = 8
mainView.layer.masksToBounds = true
mainView.backgroundColor = getColor(0, green: 179, blue: 164)
self.view.addSubview(mainView)
scrollView.frame = CGRectMake(0, 0, mainWidth, mainHeight)
scrollView.contentSize = CGSizeMake(mainWidth, 800)
self.mainView.addSubview(scrollView)
contentView.frame = CGRectMake(0, 0, mainWidth, 800);
contentView.backgroundColor = getColor(0, green: 179, blue: 164)
contentView.layer.masksToBounds = true
contentView.clipsToBounds = true
self.scrollView.addSubview(contentView)
Place self.layoutViewIfNeeded() before frame/center changes.
Additionally you may need to put your next animation within a completion handler of the next animation.
func next() {
UIView.animateWithDuration(0.5, animations: {
self.view.layoutIfNeeded() // add this
self.mainView.frame = CGRectMake(-500, self.view.bounds.height/2-self.mainHeight/2, self.mainWidth, self.mainHeight)
self.mainView.center.x -= 600
}) { (success) in
self.view.layoutIfNeeded() // add this
self.mainView.center.x += 1200
// your next animation within a completion handler of previous animation
UIView.animateWithDuration(0.5, animations: {
self.mainView.frame = CGRectMake(self.view.bounds.width/2-self.mainWidth/2, self.view.bounds.height/2-self.mainHeight/2, self.mainWidth, self.mainHeight)
}) { (success) in
print("Animation Successful!")
self.drawView()
}
}
}
I haven't tested it yet, perhaps some changes or lines ordering is needed.
In Swift 3, I had to set the frame and then call layoutIfNeeded().
For example, an animation with spring:
UIView.animate(withDuration: 0.3, delay: 0.1, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
self.subView.frame = CGRect(x: 0, y: 0, width: 500, height: 200)
self.view.layoutIfNeeded()
}) { (_) in
// Follow up animations...
}