UIButton with shadow rotating without shadow rotating - ios

Currently I have a button that draws its properties from a UIButton extension.
func mapControllerButtons(image: String, selector: Selector) -> UIButton {
let button = UIButton()
button.setImage(UIImage(named: image)!.withRenderingMode(.alwaysOriginal), for: .normal)
button.layer.shadowColor = UIColor.black.cgColor
button.layer.shadowOffset = CGSize(width: 0.0, height: 3.0)
button.layer.shadowRadius = 5
button.layer.shadowOpacity = 0.5
button.layer.masksToBounds = false
button.addTarget(self, action: selector, for: .touchUpInside)
return button
}
}
This creates a shadow underneath the button, however I have another function that rotates the button around, to signal to the user that data is being loaded from an API.
Currently what I am doing is creating the button with a shadow and overlaying it with a button that does not have a shadow. The shadowless button is the one that will be tapped and rotate.
I have a gut feeling that there's gotta be a better way of doing this than creating two buttons having one eat memory just so it can be a placeholder (for the shadow effect).
How can I go about creating a button that can rotate with animateWith func while the shadow stays in its place?
Here is my animation code block.
func animateRefreshButton() {
UIView.animate(withDuration: 1.0, delay: 0.0, options: .repeat, animations: {
self.refreshButton.transform = CGAffineTransform(rotationAngle: .pi)
})
}

You could try rotating the button's imageView instead of the whole button:
func animateRefreshButton() {
UIView.animate(withDuration: 1.0, delay: 0.0, options: .repeat, animations: {
self.refreshButton.imageView?.transform = CGAffineTransform(rotationAngle: .pi)
})
}

Related

How to use completion block for UIView.animate()?

I'm working on a project to learn animations and am having trouble using the completion block for the UIView.animate(withDuration:) func. MY animation is a shoebox that falls from the top of the screen, lands on a pedestal, then opens. Once the box opens I want a UIImageView to come out of the box, grow to full size of the screen and then segue to the next page, but the completion handler that the code for segueing is called before my animation completes and the UIImageView doesn't appear at all.
Here's my code:
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 1.5, delay: 0.0, options: .curveEaseInOut,
animations: {
//Opening the box
self.shoeBoxImage.shoeBox.animationImages = self.boxOpeningAnimation
self.shoeBoxImage.shoeBox.animationDuration = 1.5
self.shoeBoxImage.shoeBox.animationRepeatCount = 1
self.shoeBoxImage.shoeBox.contentMode = .scaleAspectFill
self.shoeBoxImage.shoeBox.startAnimating()
//set to the final image
self.shoeBoxImage.shoeBox.image = UIImage(named: "frame13")
},completion: {_ in
let nextPage = UIImageView()
nextPage.frame = CGRect(origin: self.shoeBoxImage.center, size: CGSize(width: 0.0, height: 0.0))
nextPage.image = UIImage(named: "FirstLoadBackgroundImg.jpeg")
nextPage.autoresizesSubviews = true
self.view.addSubview(nextPage)
self.view.bringSubviewToFront(nextPage)
UIView.animate(withDuration: 5.0, animations: {
nextPage.transform = CGAffineTransform(scaleX: 428, y: 926)
})
self.performSegue(withIdentifier: "FinishedLoading", sender: self)
})
}
This is my first time working with animations and programatically creating views so if someone could explain how to make the completion block wait for the animation to complete. In order to make the UIImageView appear and animate then once it's full screen, segue to the next page it would be very much appreciated.
The size is 0, 0. Transforming zero by any scale is still zero. I would advise you to not use transform at all, but rather just set the final frame to be what you want.
E.g.,
let startFrame = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
let endFrame = view.bounds
let imageView = UIImageView(image: ...)
imageView.contentMode = .scaleAspectFill
view.addSubview(imageView)
imageView.frame = startFrame
UIView.animate(withDuration: 3, delay: 0, options: .curveEaseInOut) {
imageView.frame = endFrame
} completion: { _ in
// do something here
}
That yields:
By the way, the performSegue probably should be inside a completion closure of the inner animate call.

UITapGestureRecognizer doesn't work after resizing UIView

I want to programmatically move and expand the size of a UIView on tap and then, if the UIView is tapped again, animate it back to its original size and position.
I can get the UIView to expand and move on the first tap. However, taps aren't registered at all after that initial animation.
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
myView.heightAnchor.constraint(equalToConstant: 50).isActive = true
myView.widthAnchor.constraint(equalToConstant: 50).isActive = true
myView.isUserInteractionEnabled = true
myView.addGestureRecognizer(tapRecognizer)
Below is my function that is called by handleTap to perform the initial animation on the first tap. It runs fine. I translate the position of myView and then change the frame size to make it bigger.
fileprivate func expandView(_ sender: UITapGestureRecognizer) {
UIView.animate(withDuration: 0.5, animations: {
let translate = CGAffineTransform(translationX: 100, y: 50 )
sender.view?.transform = translate
sender.view?.backgroundColor = .green
}) { (_) in
UIView.animate(withDuration: 0.5, animations: {
// change frame size of view to make it bigger
sender.view?.frame = CGRect(x: 100, y: 50, width: 100, height: 100)
})
}
}
I tried commenting out the animation that changes the frame size, leaving only the XY translation. That also doesn't allow handleTap to fire again when tapping myView.

Animate specific constraint changes by layoutIfNeeded()

I have programmatically constructed the views and then animating them. There is a chain of view animation and the last one is the button to be animated.
Whilst every thing is working fine as soon as I animate the button by changing the constraints(these constraints are independent of others and no other view has any hinged to it) by calling layoutIfNeeded(), all the views changes and go to their initial position of the animation.
I even tried bunch of other methods such as changing alpha values etc or making it disappear. but every single time, it changes the whole view.
Now, I am not able to figure out the exact cause. Below is the code of it.
Is there a way to change just a specific constraint with layoutIfNeeded() or any other way to handle this functionality?
import UIKit
var nextButton: UIButton!
var nextButtonHeightConstraint: NSLayoutConstraint?
var nextButtonWidthConstraint: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
addNextButton()
}
// ..... function trigerred here.
#IBAction func triggerChange() {
performCaptionAnimation()
}
func addNextButton() {
nextButton = UIButton()
nextButton.translatesAutoresizingMaskIntoConstraints = false
nextButton.clipsToBounds = true
nextButton.setTitle("Next", for: .normal)
nextButton.backgroundColor = .white
nextButton.isUserInteractionEnabled = true
nextButton.titleLabel!.font = AvernirNext(weight: .Bold, size: 16)
nextButton.setTitleColor(.darkGray, for: .normal)
nextButton.roundAllCorners(withRadius: ActionButtonConstraint.height.anchor/2)
nextButton.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(nextButtonPressed)))
view.addSubview(nextButton)
nextButtonHeightConstraint = nextButton.heightAnchor.constraint(equalToConstant: ActionButtonConstraint.height.anchor)
nextButtonWidthConstraint = nextButton.widthAnchor.constraint(equalToConstant: ActionButtonConstraint.width.anchor)
NSLayoutConstraint.activate([
nextButton.rightAnchor.constraint(equalTo: view.rightAnchor, constant: ActionButtonConstraint.right.anchor),
nextButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: ActionButtonConstraint.bottom.anchor),
nextButtonHeightConstraint!, nextButtonWidthConstraint!
])
}
func performCaptionAnimation() {
UIView.animate(withDuration: 0.4, delay: 0.0, options: [.curveEaseInOut], animations: {
self.bannerTwoItemContainer.alpha = 1
self.bannerTwoItemContainer.frame.origin.x -= (self.bannerTwoItemContainer.frame.width/2 + 10)
}) { (done) in
if done {
UIView.animate(withDuration: 0.4, delay: 0.0, options: [.curveEaseInOut], animations: {
self.bannerOneItemContainer.alpha = 1
self.bannerOneItemContainer.frame.origin.x += self.bannerOneItemContainer.frame.width/2 + 10
}) { (alldone) in
if alldone {
// All changes here ......................
self.changeNextButtonContents()
}
}
}
}
}
// ------ This animation has issues and brings all the animation to it's initial point
func changeNextButtonContents() {
self.nextButtonHeightConstraint?.constant = 40
self.nextButtonWidthConstraint?.constant = 110
UIView.animate(withDuration: 0.4, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [.curveEaseIn], animations: {
self.nextButton.setTitleColor(.white, for: .normal)
self.nextButton.setTitle("Try Now", for: .normal)
self.nextButton.titleLabel?.font = AvernirNext(weight: .Bold, size: 18)
self.nextButton.backgroundColor = blueColor
self.nextButton.roundAllCorners(withRadius: 20)
self.view.layoutIfNeeded()
}, completion: nil)
}
The problem is that your first animations change their view’s frame, as in self.bannerTwoItemContainer.frame.origin.x. But you cannot change the frame of something that is positioned by constraints. If you do, then as soon as layout occurs, the constraints reassert themselves. That is exactly what you are doing and exactly what happens. You say layoutIfNeeded and the constraints, which you never changed, are obeyed.
Rewrite your first animations to change their view’s constraints, not their frame, and all will be well.

Animate the 'backgroundColor' for a UIButton in iOS using Swift3

I would like to animate the backgroundColor property of my UIButtons but I'm not sure where to begin. I've never used Core Animation, I'm not sure if I even need it for something so basic?
I've styled the buttons using a class and a method that looks something like this:
redTheme.swift
func makeRedButton() {
self.layer.backgroundColor = myCustomRedcolor
}
You can do it with UIView Animation as the following :
Note: Simply don't forget to set the background to clear before you perform the animation otherwise it won't work. That's because animation need a starting and ending point, starting point is clear color to Red. Or alpha 0 to alpha 1 or the other way around.
let button = UIButton(frame: CGRect(x: 0, y: 100, width: 50, height: 50))
button.layer.backgroundColor = UIColor.clear.cgColor
self.view.addSubview(button)
func makeRedButton() {
UIView.animate(withDuration: 1.0) {
button.layer.backgroundColor = UIColor.red.cgColor
}
}
Swift 4 - A simple way to animate any change on your UIButton or any other UIView:
UIView.transition(with: button, duration: 0.3, options: .curveEaseInOut, animations: {
self.button.backgroundColor = .red
self.button.setTitle("ERROR", for: .normal)
self.button.setTitleColor(.white, for: .normal)
})
For some reason animating button.layer.backgroundColor
had zero effect for my custom uibutton hence I animated alpha like so:
func provideVisualFeedback(_ sender:UIButton!)
{
sender.backgroundColor = UIColor.whatever
sender.alpha = 0
UIView .animate(withDuration: 0.1, animations: {
sender.alpha = 1
}, completion: { completed in
if completed {
sender.backgroundColor = UIColor.white
}
})
}
and then
#IBAction func buttonAction(_ sender:NumberPadButton!)
{
provideVisualFeedback(sender)
do your thing once animation was setup with the helper above

Set a button as an image in SpriteKit

I currently have a UIButton as a restart button for a game within SpriteKit and I'd like to make it an image. I have declared the button as:
var RateBtn: UIButton!
Here's current code for button:
RestartBtn = UIButton(frame: CGRect(x: 0, y: 0, width: view!.frame.size.width / 3, height: view!.frame.size.height / 3))
RestartBtn.center = CGPointMake(view!.frame.size.width / 2, view!.frame.size.height)
RestartBtn.setTitle("RESTART", forState: UIControlState.Normal)
RestartBtn.setTitleColor(UIColor.whiteColor(), forState: UIControlState.Normal)
RestartBtn.addTarget(self, action: Selector("restart"), forControlEvents: UIControlEvents.TouchUpInside)
self.view?.addSubview(RestartBtn)
UIView.animateWithDuration(1.0, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: nil, animations: ({
self.RestartBtn.center.y = self.view!.frame.size.height / 1.4
}), completion: nil)
So I have it set out with a spring effect but I now want it as an image instead of font. How do I do this?
Make the button a SKSpriteNode.
Detect touch on it by:
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if button.containsPoint(location) {
//enter restart function here!
}
}

Resources