How can i set the same IBAction to multiple buttons? - swift

I'm trying to link multiple buttons with the same IBAction, to run similar but different code. The code is to set an image that was clicked on another view controller into the UIImageView under the button.
All the buttons link to the same view controller but with a different segue.
I tried to write a if statements but I didn't seem to have it right. I have named each corresponding UIImage view: technologyImageViewTwo, technologyImageViewThree ...etc
below is the code I used for the first button which works with the corresponding UIImageView named technologyImageView
#IBAction func setTechnology(segue:UIStoryboardSegue) {
dismiss(animated: true) {
if let technology = segue.identifier{
self.persona.technology = technology
self.technologyView.technologyImageView.image = UIImage(named: technology)
}
//animating scale up of image
let scaleUp = CGAffineTransform.init(scaleX: 0.1, y:0.1)
self.technologyView.technologyImageView.transform = scaleUp
self.technologyView.technologyImageView.alpha = 0
//animating bounce effect
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.7, options: [], animations: {
self.technologyView.technologyImageView.transform = .identity
self.technologyView.technologyImageView.alpha = 1
}, completion: nil)
}
I expect that each button should go to the segued view controller and the image selected will show up under the corresponding button. E.g if I click on the button 'Technology 2' and choose an image, the image shows up in the UIImageview named technologyImageViewTwo.

There are a couple of options, that you can choose from:
Option 1:
This option would be more preferred, that is to use the tag property on the component, this will allow you to identify the index of the button when it has been actioned.
https://developer.apple.com/documentation/uikit/uiview/1622493-tag
#IBAction func action(_ sender: Any) {
dismiss(animated: true) {
var imageView: UIImageView!
let index = (sender as? UIView)?.tag ?? 0
switch index {
case 1:
persona.technology = <#T##String#>
imageView = technologyView.technologyImageViewTwo
case 2:
persona.technology = <#T##String#>
imageView = technologyView.technologyImageViewThree
default:
persona.technology = <#T##String#>
imageView = technologyView.technologyImageView
}
if let technology = persona.technology {
imageView.image = UIImage(named: technology)
}
//animating scale up of image
let scaleUp = CGAffineTransform.init(scaleX: 0.1, y:0.1)
imageView.transform = scaleUp
imageView.alpha = 0
//animating bounce effect
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.7, options: [], animations: {
imageView.transform = .identity
imageView.alpha = 1
})
}
}
Option 2:
If you want to continue with the code you have then you could use a segue identifier that contains components that you can be split out and enable you to identify them more efficiently:
segueIdentifier-1, segueIdentifier-2, segueIdentifier-3
func setTechnology(segue: UIStoryboardSegue) {
dismiss(animated: true) {
var imageView: UIImageView!
let identifierComponents = segue.identifier?.components(separatedBy: "-")
let index = Int(identifierComponents?.last ?? "0")
switch index {
case 1:
imageView = technologyView.technologyImageViewTwo
case 2:
imageView = technologyView.technologyImageViewThree
default:
imageView = technologyView.technologyImageView
}
if let technology = identifierComponents?.first {
self.persona.technology = technology
imageView.image = UIImage(named: technology)
}
//animating scale up of image
let scaleUp = CGAffineTransform.init(scaleX: 0.1, y:0.1)
imageView.transform = scaleUp
imageView.alpha = 0
//animating bounce effect
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.7, options: [], animations: {
imageView.transform = .identity
imageView.alpha = 1
})
}
}

You could use three separate IBActions that all call the same function with the UIImageView number as a parameter.
func setTechnology(segue:UIStoryboardSegue, imageViewNumber: Int) {
dismiss(animated: true) {
var imageView: UIImageView!
if imageViewNumber == 0 {
imageView = self.technologyView.technologyImageView
} else if imageView == 1 {
imageView = self.technologyView.technologyImageViewTwo
} else {
imageView = self.technologyView.technologyImageViewThree
}
if let technology = segue.identifier{
self.persona.technology = technology
imageView.image = UIImage(named: technology)
}
//animating scale up of image
let scaleUp = CGAffineTransform.init(scaleX: 0.1, y:0.1)
imageView.transform = scaleUp
imageView.alpha = 0
//animating bounce effect
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.7, options: [], animations: {
imageView.transform = .identity
imageView.alpha = 1
}, completion: nil)
}
And then call this function from inside the three separate IBActions.
#IBAction func tappedButtonZero(segue:UIStoryboardSegue) {
self.setTechnology(segue: segue, imageViewNumber: 0)
}
#IBAction func tappedButtonOne(segue:UIStoryboardSegue) {
self.setTechnology(segue: segue, imageViewNumber: 1)
}
#IBAction func tappedButtonTwo(segue:UIStoryboardSegue) {
self.setTechnology(segue: segue, imageViewNumber: 2)
}

You could drag multiple buttons to your #IBAction and assign a unique tag value to each button. Then, use a switch statement to do whatever's unique to the button you pressed.
#IBAction func tappedButton(_ sender: UIButton) {
switch sender.tag {
case 1:
print("one")
case 2:
print("two")
case 3:
print("three")
default:
break
}
}

Related

Custom segue class crashes with Thread 1: EXC_BAD_ACCESS (code=2, address=0x16ecb3ff0)

I'm trying to create an app that has a universal background image. Since most of my ViewController backgrounds are set to clear color, this presents a problem for most transitions. My solution was to try and create this custom segue class that pushes the source VC to the left and pulls the new VC from the right. However I'm getting this error:
Thread 1: EXC_BAD_ACCESS (code=2, address=0x16ecb3ff0
In interface builder, Storyboard Segue: I have the custom class linked, and set the kind to "Custom". Animates is checked on. This segue works from my login screen VC to Forgot Password VC but does not work if I use it on my contacts VC to add contact VC.
Not sure why it works on one and not the other? Am I just doing this all wrong? (I'm new to iOS dev and learning)
class PushSegue: UIStoryboardSegue {
override func perform() {
push()
}
func push () {
let toViewController = self.destination
let fromViewController = self.source
let containerView = fromViewController.view.superview
let originalCenter = fromViewController.view.center
fromViewController.view.transform = CGAffineTransform(translationX: 0, y: 0)
fromViewController.view.center = originalCenter
toViewController.view.transform = CGAffineTransform(translationX: 600, y: 0)
toViewController.view.center = originalCenter
containerView?.addSubview(toViewController.view)
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: { toViewController.view.transform = CGAffineTransform.identity
fromViewController.view.transform = CGAffineTransform (translationX: -600, y: 0)
}, completion: {success in
fromViewController.present(toViewController, animated: false, completion: nil) //Crashes here
})
}
}
class UnwindPushSegue: UIStoryboardSegue {
override func perform() {
push()
}
func push () {
let toViewController = self.destination
let fromViewController = self.source
fromViewController.view.superview?.insertSubview(toViewController.view, at: 0)
toViewController.view.superview?.insertSubview(toViewController.view, at: -600)
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: { fromViewController.view.transform = CGAffineTransform.init(translationX: 600, y: 0)
toViewController.view.transform = CGAffineTransform.init(translationX: 0, y: 0)
}, completion: {success in
fromViewController.dismiss(animated: false, completion: nil)
})
}
}
I'd expect the source view controller to move to the left, however it does not move left. The new VC comes in from the right but I can still see the source view controller underneath.

Black edges on return transition

I have implemented the following animated transition:
class PopInAndOutAnimator: NSObject, UIViewControllerAnimatedTransitioning {
fileprivate let _operationType : UINavigationControllerOperation
fileprivate let _transitionDuration : TimeInterval
init(operation: UINavigationControllerOperation) {
_operationType = operation
_transitionDuration = 0.4
}
init(operation: UINavigationControllerOperation, andDuration duration: TimeInterval) {
_operationType = operation
_transitionDuration = duration
}
//MARK: Push and Pop animations performers
internal func performPushTransition(_ transitionContext: UIViewControllerContextTransitioning) {
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else {
// Something really bad happend and it is not possible to perform the transition
print("ERROR: Transition impossible to perform since either the destination view or the conteiner view are missing!")
return
}
let container = transitionContext.containerView
guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as? CollectionPushAndPoppable,
let fromView = fromViewController.collectionView,
let currentCell = fromViewController.sourceCell else {
// There are not enough info to perform the animation but it is still possible
// to perform the transition presenting the destination view
container.addSubview(toView)
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
return
}
// Add to container the destination view
container.addSubview(toView)
// Prepare the screenshot of the destination view for animation
let screenshotToView = UIImageView(image: toView.screenshot)
// set the frame of the screenshot equals to the cell's one
screenshotToView.frame = currentCell.frame
// Now I get the coordinates of screenshotToView inside the container
let containerCoord = fromView.convert(screenshotToView.frame.origin, to: container)
// set a new origin for the screenshotToView to overlap it to the cell
screenshotToView.frame.origin = containerCoord
// Prepare the screenshot of the source view for animation
let screenshotFromView = UIImageView(image: currentCell.screenshot)
screenshotFromView.frame = screenshotToView.frame
// Add screenshots to transition container to set-up the animation
container.addSubview(screenshotToView)
container.addSubview(screenshotFromView)
// Set views initial states
toView.isHidden = true
screenshotToView.isHidden = true
// Delay to guarantee smooth effects
let delayTime = DispatchTime.now() + Double(Int64(0.08 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: delayTime) {
screenshotToView.isHidden = false
}
UIView.animate(withDuration: _transitionDuration, delay: 0, usingSpringWithDamping: 0, initialSpringVelocity: 0, options: [], animations: { () -> Void in
screenshotFromView.alpha = 0.0
screenshotToView.frame = UIScreen.main.bounds
screenshotToView.frame.origin = CGPoint(x: 0.0, y: 0.0)
screenshotFromView.frame = screenshotToView.frame
}) { _ in
screenshotToView.removeFromSuperview()
screenshotFromView.removeFromSuperview()
toView.isHidden = false
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
internal func performPopTransition(_ transitionContext: UIViewControllerContextTransitioning) {
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else {
// Something really bad happend and it is not possible to perform the transition
print("ERROR: Transition impossible to perform since either the destination view or the conteiner view are missing!")
return
}
let container = transitionContext.containerView
guard let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) as? CollectionPushAndPoppable,
let toCollectionView = toViewController.collectionView,
let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let fromView = fromViewController.view,
let currentCell = toViewController.sourceCell else {
// There are not enough info to perform the animation but it is still possible
// to perform the transition presenting the destination view
container.addSubview(toView)
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
return
}
// Add destination view to the container view
container.addSubview(toView)
// Prepare the screenshot of the source view for animation
let screenshotFromView = UIImageView(image: fromView.screenshot)
screenshotFromView.frame = fromView.frame
// Prepare the screenshot of the destination view for animation
let screenshotToView = UIImageView(image: currentCell.screenshot)
screenshotToView.frame = screenshotFromView.frame
// Add screenshots to transition container to set-up the animation
container.addSubview(screenshotToView)
container.insertSubview(screenshotFromView, belowSubview: screenshotToView)
// Set views initial states
screenshotToView.alpha = 0.0
fromView.isHidden = true
currentCell.isHidden = true
let containerCoord = toCollectionView.convert(currentCell.frame.origin, to: container)
UIView.animate(withDuration: _transitionDuration, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0, options: [], animations: { () -> Void in
screenshotToView.alpha = 1.0
screenshotFromView.frame = currentCell.frame
screenshotFromView.frame.origin = containerCoord
screenshotToView.frame = screenshotFromView.frame
}) { _ in
currentCell.isHidden = false
screenshotFromView.removeFromSuperview()
screenshotToView.removeFromSuperview()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
//MARK: UIViewControllerAnimatedTransitioning
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return _transitionDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if _operationType == .push {
performPushTransition(transitionContext)
} else if _operationType == .pop {
performPopTransition(transitionContext)
}
}
}
The issue that I'm having is when my collection view cell returns. There is a small delay where the bottom of the cell has a black line. I'm not sure how to remove that delay or make that line white so that it isn't noticeable.
A quick hack is to set the background view color to white.

Multiple transforms UIView

I want to transform my image view like this:
I use landscape mode, I want the panda begin at the center vertically from the left, then it will rotate 360 again and again with become bigger and bigger, and go to the center vertically to the right.
I have tried like this:
import UIKit
class ViewController: UIViewController {
#IBOutlet var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let scale = CGAffineTransformMakeScale(3.0, 3.0)
let rotate = CGAffineTransformMakeRotation(180*CGFloat(M_PI)/180)
let translate = CGAffineTransformMakeTranslation(UIScreen.mainScreen().bounds.width - 100, 0)
UIView.animateWithDuration(3.0, delay: 0, options: [.Repeat, .Autoreverse, .CurveEaseInOut], animations: { () -> Void in
let mixTransform = CGAffineTransformConcat(scale, translate)
self.imageView.transform = mixTransform
self.view.layoutIfNeeded()
}, completion: nil)
UIView.animateWithDuration(1.0, delay: 0, options: [.Repeat, .Autoreverse, .CurveEaseInOut], animations: { () -> Void in
self.imageView.transform = rotate
}, completion: nil)
}
}
But it doesn't work well.
Your transforms obviously dont fit your requirements.
If you want image to scale proportionally, why you set different scales for X and Y then?
Again, if you want image to move along X axis, why you adjust Y position of it in transforms? Simply fix your transform and you are ready to go.
In particular:
let scale = CGAffineTransformMakeScale(3.0, 3.0)
...
let translate = CGAffineTransformMakeTranslation(UIScreen.mainScreen().bounds.width - 400, 0)
Here is my answer:
import UIKit
import QuartzCore
class ViewController: UIViewController {
#IBOutlet var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let kRotationAnimationKey = "com.myapplication.rotationanimationkey"
func rotateView(view: UIView, duration: Double = 1) {
if view.layer.animationForKey(kRotationAnimationKey) == nil {
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotationAnimation.fromValue = 0.0
rotationAnimation.toValue = Float(M_PI * 2.0)
rotationAnimation.duration = duration
rotationAnimation.repeatCount = Float.infinity
view.layer.addAnimation(rotationAnimation, forKey: kRotationAnimationKey)
}
}
func stopRotatingView(view: UIView) {
if view.layer.animationForKey(kRotationAnimationKey) != nil {
view.layer.removeAnimationForKey(kRotationAnimationKey)
}
}
var transform = CGAffineTransformMakeTranslation(UIScreen.mainScreen().bounds.width - 200, 0)
transform = CGAffineTransformScale(transform, 3.0, 3.0)
UIView.animateWithDuration(1.0, delay: 0, options: [.Repeat, .Autoreverse, .CurveEaseInOut], animations: { () -> Void in
self.imageView.transform = transform
rotateView(self.imageView)
self.view.layoutIfNeeded()
}) { (_) -> Void in
stopRotatingView(self.imageView)
}
}
}
Result:

Word by Word UILabel animation from left to right in swift

I have a uilabel with a text suppose " This is a label." I want this label to be displayed one word at a time flying from outside screen to the UILable position..
something like
label.
a label.
is a label.
This is a label.
How can i get such animation
I have found a way to do as i wished.
class ViewController: UIViewController {
#IBOutlet var sampleLabel: UILabel!
var slogan = "This is a slogan."
var xdir = 250
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let sampleLabelFrame = sampleLabel.frame
let ypos = sampleLabelFrame.origin.y
var sloganArray = slogan.componentsSeparatedByString(" ")
sloganArray = sloganArray.reverse()
var i = 0.0
for word in sloganArray{
let label: UILabel = UILabel(frame: CGRect(x: -100, y:ypos, width: 60, height: 20))
label.text = word
view.addSubview(label)
let width = label.intrinsicContentSize().width
var labelFramewidth = label.frame
labelFramewidth.size.width = width
label.frame = labelFramewidth
self.xdir = self.xdir - Int(width)-4
UIView.animateWithDuration(0.7, delay: i, options: .CurveEaseOut, animations: {
var labelframe = label.frame
labelframe.origin.x = CGFloat(self.xdir)
label.frame = labelframe
}, completion: { finished in
})
i+=0.5
}
}
}
hope this helps others in need of something like this.
This might help you on the way to achieve what you want:
http://helpmecodeswift.com/animation/creating-animated-labels
EDIT:
Flying label code:
meme1.text = "Brace yourself!"
meme1.font = UIFont.systemFontOfSize(25)
meme1.textColor = UIColor.whiteColor() // This is all just styling the Label
meme1.sizeToFit()
meme1.center = CGPoint(x: 200, y: -50) // Starting position of the label
view.addSubview(meme1) // Important! Adding the Label to the Subview, so we can actually see it.
//Animation options. Play around with it.
UIView.animateWithDuration(0.9, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.0, options: .CurveLinear, animations: {
self.meme1.center = CGPoint(x: 200, y:50 ) // Ending position of the Label
}, completion: nil)
UIView.animateWithDuration(0.6, delay: 1.1, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.0, options: .CurveLinear, animations: {
self.meme1.center = CGPoint(x: 200, y:150+90 )
}, completion: nil)
I have no experience with it myself, but it seems to get your job done. You can eventually se the starting point outside the screen and let it animate over to the screen. That should give the desired effect.

UIButton Heartbeat Animation

I've created a heart beat animation for a UIButton. However, there is no way to stop this animation as it's an endless code loop. After tinkering with numerous UIView animation code blocks I've been unable to get UIViewAnimationOptions.Repeat to produce what I need. If I could do that I could simply button.layer.removeAllAnimations() to remove the animations. What is a way to write this that allows for removal of the animation? I'm thinking a timer possibly but that could be kind of messy with multiple animations going on.
func heartBeatAnimation(button: UIButton) {
button.userInteractionEnabled = true
button.enabled = true
func animation1() {
UIView.animateWithDuration(0.5, delay: 0.0, options: [], animations: { () -> Void in
button.transform = CGAffineTransformMakeScale(2.0, 2.0)
button.transform = CGAffineTransformIdentity
}, completion: nil)
UIView.animateWithDuration(0.5, delay: 0.5, options: [], animations: { () -> Void in
button.transform = CGAffineTransformMakeScale(2.0, 2.0)
button.transform = CGAffineTransformIdentity
}) { (Bool) -> Void in
delay(2.0, closure: { () -> () in
animation2()
})
}
}
func animation2() {
UIView.animateWithDuration(0.5, delay: 0.0, options: [], animations: { () -> Void in
button.transform = CGAffineTransformMakeScale(2.0, 2.0)
button.transform = CGAffineTransformIdentity
}, completion: nil)
UIView.animateWithDuration(0.5, delay: 0.5, options: [], animations: { () -> Void in
button.transform = CGAffineTransformMakeScale(2.0, 2.0)
button.transform = CGAffineTransformIdentity
}) { (Bool) -> Void in
delay(2.0, closure: { () -> () in
animation1()
})
}
}
animation1()
}
This works perfectly. The damping and spring need to be tweaked a little bit but this solves the problem. removeAllAnimations() clears the animation and returns the button to it's normal state.
button.userInteractionEnabled = true
button.enabled = true
let pulse1 = CASpringAnimation(keyPath: "transform.scale")
pulse1.duration = 0.6
pulse1.fromValue = 1.0
pulse1.toValue = 1.12
pulse1.autoreverses = true
pulse1.repeatCount = 1
pulse1.initialVelocity = 0.5
pulse1.damping = 0.8
let animationGroup = CAAnimationGroup()
animationGroup.duration = 2.7
animationGroup.repeatCount = 1000
animationGroup.animations = [pulse1]
button.layer.addAnimation(animationGroup, forKey: "pulse")
This post was very helpful: CAKeyframeAnimation delay before repeating
Swift 5 code, works without the pause between pulses:
let pulse = CASpringAnimation(keyPath: "transform.scale")
pulse.duration = 0.4
pulse.fromValue = 1.0
pulse.toValue = 1.12
pulse.autoreverses = true
pulse.repeatCount = .infinity
pulse.initialVelocity = 0.5
pulse.damping = 0.8
switchButton.layer.add(pulse, forKey: nil)
I created something like this:
let animation = CAKeyframeAnimation(keyPath: "transform.scale")
animation.values = [1.0, 1.2, 1.0]
animation.keyTimes = [0, 0.5, 1]
animation.duration = 1.0
animation.repeatCount = Float.infinity
layer.add(animation, forKey: "pulse")
On your original question you mentioned that you want the animation to stop on command. I assume you would like it to start on command too. This solution will do both and it is quite simple.
func cutAnim(){
for view in animating {
///I use a UIView because I wanted the container of my button to be animated. UIButton will work just fine too.
(view.value as? UIView)?.layer.removeAllAnimations()
}
}
func pulse(button: UIButton, name: String){
///Here I capture that container
let container = button.superview?.superview
///Add to Dictionary
animating[name] = container
cutAnim()
UIView.animate(withDuration: 1, delay: 0.0, options:[UIViewAnimationOptions.repeat, UIViewAnimationOptions.autoreverse, .allowUserInteraction], animations: {
container?.transform = CGAffineTransform(scaleX: 1.15, y: 1.15)
///if you stop the animation half way it completes anyways so I want the container to go back to its original size
container?.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
}, completion: nil)
}
Call cutAnim() anywhere to stop an animation, inside a timer if you want.
To start the animation use a regular button action
#IBAction func buttonWasTappedAction(_ sender: Any) {
pulse(button: sender as! UIButton, name: "nameForDictionary")
}
Hope this helps.