Swift unable to animate navigation bar button item - ios

I need some help to translate the rightBarButtonItem in the UINavigationBar. I have followed this and also found similar questions which lead me to add the button as a customView of a UIBarButtonItem, however the code is still not working.
I have created a UIBarButtonItem and set a UIButton as its custom view.
let button: UIButton = {
let b = UIButton(type: .system)
b.setTitle("Test", for: .normal)
return b
}()
lazy var barButton = UIBarButtonItem(customView: button)
This method is called from viewDidLoad
func configNavBar() {
navigationItem.rightBarButtonItem = barButton
}
To animate the bar button when some action occurs:
func animateRightBarButton() {
UIView.animate(withDuration: 0.25,
delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 1.5,
options: .curveEaseInOut,
animations: {
guard let customView = self.barButton.customView else {return}
var x: CGFloat = customView.transform.tx == 0 ? 100 : 0
customView.transform = CGAffineTransform(scaleX: x, y: 0)
}, completion: nil)
}
The bar button appears as expect when the view has loaded. I want the button to slide off screen but i have noticed a couple of problems.
The button disappears from the view with no animation when this function is called.
If I print(x) (the translate x value) it remains at 100.0 and does not change back to 0 using the ternary operator. Any help appreciated to get this working, thanks.

use this code:
let bell = UIImage(named: "gift")!
let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
button.setBackgroundImage(bell, for: .normal)
button.addTarget(self, action: #selector(self.showAdd), for: .touchUpInside)
let bellButton = UIBarButtonItem(customView: button)
self.navigationItem.leftBarButtonItem = bellButton
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
if let bell = self.navigationItem.leftBarButtonItem?.customView {
let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
animation.duration = 0.6
animation.values = [-8.0, 8.0, -8.0, 8.0, -4.0, 4.0, -4.0, 4.0, 0.0 ]
bell.layer.add(animation, forKey: "shake")
}
}

Thanx to #KMI
I improved usability by carrying out the functionality of #KMI 's method in separate helper:
Swift 5
UIBarButtonItem+Helper.swift
extension UIBarButtonItem {
typealias AnimationClosure = (_ customView: UIView)->()
private var uniqueCustomViewAnimationKey: String {
"customViewAnimation"
}
func animate(with animation: CAAnimation, delay: TimeInterval = 0.5) {
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
guard let `self` = self else { return }
self.customView?.layer.add(animation, forKey: self.uniqueCustomViewAnimationKey)
}
}
}
And usage in some class:
func bellItem() -> UIBarButtonItem {
let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
button.setBackgroundImage(UIImage(named: "bell"), for: .normal)
button.addTarget(self, action: #selector(bellItemPressed), for: .touchUpInside)
let item = UIBarButtonItem(customView: button)
// create custom animation
let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
animation.duration = 0.6
animation.values = [-8.0, 7.0, -6.0, 5.0, -4.0, 3.0, -2.0, 1.0, 0.0 ]
// applying the animation
item.animate(with: animation)
return item
}
#objc private func bellItemPressed() {
// do some stuff when item pressed
}

Related

How can I start and stop impulse animation from a button in TabBarViewController

There is a UIImage inside the MainViewController as seen in the picture. There is also a button in TabBarViewController. When I press the button, I want the impulse animation I wrote for the UIView to start and stop.When i click to play button animation is not started. How can I do this?
Thanks.
enter image description here
Here is my code;
class MainViewObject: UIView {
//Center Image View Container
lazy var centerContainerView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.layer.cornerRadius = 113
view.backgroundColor = Colors.lightOrangeColor
return view
}()
}
class TabbarViewController: UITabBarController {
var object: MainViewObject!
//Create Play Button
let middleButton: UIButton = {
let btn = UIButton(frame: CGRect(x: (UIScreen.main.bounds.width / 2) - 32, y: -8, width: 65, height: 65))
btn.setImage(UIImage(named: "play"), for: UIControl.State.normal)
btn.layer.shadowColor = UIColor.black.cgColor
btn.layer.shadowOpacity = 0.1
btn.layer.shadowOffset = CGSize(width: 4, height: 4)
btn.backgroundColor = Colors.mainColor
btn.layer.cornerRadius = 32
btn.tintColor = UIColor.clear
btn .addTarget(self, action:#selector(menuButtonAction), for: .touchUpInside
return btn
}()
//Play Pause Button Function
#objc func menuButtonAction(sender: UIButton) {
sender.isSelected = !sender.isSelected
if sender.isSelected {
middleButton.setImage(UIImage(named: "pause"), for: UIControl.State.normal)
playRadio()
pulseAnimationStart()
}else{
middleButton.setImage(UIImage(named: "play"), for: UIControl.State.normal)
player!.pause()
pulseAnimationStop()
}
}
}
extension TabbarViewController {
//Main Page Main Image Pulse Animation Start
func pulseAnimationStart() {
let fadingAnimation = CABasicAnimation(keyPath: "opacity")
fadingAnimation.fromValue = 1
fadingAnimation.toValue = 0
let expandingAnimation = CABasicAnimation(keyPath: "transform.scale")
expandingAnimation.fromValue = 1
expandingAnimation.toValue = 1.1
let animationGroup = CAAnimationGroup()
animationGroup.animations = [expandingAnimation,
fadingAnimation]
animationGroup.duration = 1.5
animationGroup.repeatCount = .infinity
self.object.centerContainerView.layer.add(animationGroup, forKey: "pulse")
}
//Main Page Main Image Pulse Animation Stop
func pulseAnimationStop() {
UIView.animate(withDuration: 0.0) {
self.object.centerContainerView.layer.removeAllAnimations()
}
}
}

If Button clicked - do animation to image Swift Playgrounds

My goal is simple. When a button is clicked, the button should fade out and so should another image on the screen. I am using Swift Playgrounds. I have already got the button to fade out when clicked working. The main problem is getting the image on the screen to fade out once the button is clicked.
import PlaygroundSupport
import AVFoundation
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let title = UIImage(named: "Picture1")
let imageView = UIImageView(image: title) //this is the image I want to fade out when the button is clicked
imageView.image = title
imageView.frame.size.width = 570
imageView.frame.size.height = 140
imageView.frame.origin.x = 100
imageView.frame.origin.y = 0
imageView.alpha = 0
let button = UIButton(type: UIButton.ButtonType.custom) as UIButton
let image = UIImage(named: "startbutton4.png") as UIImage?
button.frame = CGRect(x: 265, y: 300, width: 249, height: 85)
button.setImage(image, for: [])
button.contentMode = .center
button.imageView?.contentMode = .scaleAspectFit
button.imageView?.contentMode = .scaleAspectFit
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.view.addSubview(button)
view.addSubview(imageView)
}
#objc func buttonAction(sender: UIButton) {
sender.isHighlighted = false
UIView.animate(withDuration: 1, delay: 1, options: .curveEaseOut, animations: {
sender.alpha = 0
}, completion: nil) // <--- this right here is the button fading out
}
}
let master = MyViewController()
PlaygroundPage.current.liveView = master
If anyone has any idea how to do this, please let me know.
What i understand is you want to hide button ... once button is hidden you want to show image .. here is the code for that.. i hope it will help
func buttonAction(sender: UIButton) {
sender.isHighlighted = false
UIView.animate(withDuration: 1, delay: 1, options: .curveEaseOut, animations: {
sender.alpha = 0
}, completion: { success in
if success {
UIView.animate(withDuration: 1, delay: 1, options: .curveEaseOut, animations: {
imageView.alpha = 1
})
}
})
}

on single tap UIBarButton display toast and on double tap back action need to perform

I have an UIBarButton in navigation bar, while clicking on back button (first tap) i need to display toast (like warning), on double tap i need to exit from the page in swift,
Following codes used for displaying toast, its working fine,
let toastLabel = UILabel(frame: CGRect(x: 20, y: self.view.frame.size.height-100, width: 350, height: 35))
toastLabel.backgroundColor = UIColor.black.withAlphaComponent(0.6)
toastLabel.textColor = UIColor.white
toastLabel.textAlignment = .center;
toastLabel.font = UIFont(name: "Montserrat-Light", size: 12.0)
toastLabel.text = "demo"
toastLabel.alpha = 1.0
toastLabel.layer.cornerRadius = 10;
toastLabel.clipsToBounds = true
self.view.addSubview(toastLabel)
UIView.animate(withDuration: 2.0, delay: 0.1, options: .curveEaseOut, animations: {
toastLabel.alpha = 0.0
}, completion: {(isCompleted) in
toastLabel.removeFromSuperview()
})
please guide me to acheive this task.
Add an target-action on UIButton for the control event UIControlEventTouchDownRepeat, and do action only when the touch's tapCount is 2. Like below in Swift 3
button.addTarget(self, action: #selector(multipleTap(_:event:)), for: UIControlEvents.touchDownRepeat)
And Then add selector as below
func multipleTap(_ sender: UIButton, event: UIEvent) {
let touch: UITouch = event.allTouches!.first!
if (touch.tapCount == 2) {
// do action.
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true
let button = UIButton(type: .system)
button.frame = CGRect(x: 0, y: 0, width: 80, height: 40)
button.setTitle("Back", for: .normal)
//Gesture Recognizer
let singleTap = UITapGestureRecognizer(target: self, action: #selector(handleSingleTap))
singleTap.numberOfTapsRequired = 1
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap))
doubleTap.numberOfTapsRequired = 2
button.addGestureRecognizer(singleTap)
button.addGestureRecognizer(doubleTap)
singleTap.require(toFail: doubleTap)
let barButton = UIBarButtonItem(customView: button)
self.navigationItem.leftBarButtonItem = barButton
}
#objc func handleSingleTap(sender: UITapGestureRecognizer? = nil) {
let toastLabel = UILabel(frame: CGRect(x: 20, y: self.view.frame.size.height-100, width: 350, height: 35))
toastLabel.backgroundColor = UIColor.black.withAlphaComponent(0.6)
toastLabel.textColor = UIColor.white
toastLabel.textAlignment = .center;
toastLabel.font = UIFont(name: "Montserrat-Light", size: 12.0)
toastLabel.text = "demo"
toastLabel.alpha = 1.0
toastLabel.layer.cornerRadius = 10;
toastLabel.clipsToBounds = true
self.view.addSubview(toastLabel)
UIView.animate(withDuration: 2.0, delay: 0.1, options: .curveEaseOut, animations: {
toastLabel.alpha = 0.0
}, completion: {(isCompleted) in
toastLabel.removeFromSuperview()
})
print("Single Tap detected")
}
#objc func handleDoubleTap(sender: UITapGestureRecognizer? = nil) {
print("Double Tap detected")
}
Use UIControlEventTouchDownRepeat event, and do action when tapCount is 2.

Last Button in UIScrollView add target doesn't effect

I have a list of category add I created buttons for each category in a scroll view
and a last button is ALL button for all products. But some how it didn't have any action I added.
this is my code
First I make a function to create buttons to add to view
func catButtonView(buttonSize:CGSize) -> UIView {
let buttonView = UIView()
buttonView.backgroundColor = UIColor(colorLiteralRed: 1, green: 1, blue: 1, alpha: 0)
buttonView.frame.origin = CGPoint(x: 0,y: 0)
let padding = CGSize(width:0, height:0)
buttonView.frame.size.width = (buttonSize.width + padding.width) * CGFloat(categories.count)
buttonView.frame.size.height = (buttonSize.height + 2.0 * padding.height )
//add buttons to the view
var buttonPosition = CGPoint(x:padding.width * 0.5, y:padding.height)
let buttonIncrement = buttonSize.width + padding.width
let hueIncrement = 1.0 / CGFloat(categories.count)
var newHue = hueIncrement
for i in 0...(categories.count) {
let button = UIButton.init(type: .custom) as UIButton
button.frame.size = buttonSize
button.frame.origin = buttonPosition
button.setBackgroundImage(R.image.button(), for: .normal)
button.setBackgroundImage(R.image.button_selected(), for: .selected)
if(i==categories.count) {
button.setTitle("ALL", for: .normal)
} else {
button.setTitle(categories[i].name, for: .normal)
}
button.setTitleColor(UIColor.white, for: .normal)
buttonPosition.x = buttonPosition.x + buttonIncrement
newHue = newHue + hueIncrement
button.tag = i+1
button.addTarget(self, action: #selector(catButtonPressed(sender:)), for: .touchUpInside)
buttonView.addSubview(button)
}
return buttonView
}
this is action func
func catButtonPressed(sender:UIButton){
print(sender.tag)
if(self.selectedCat != sender.tag-1) {
let lastBtn = catScrollView.viewWithTag(selectedCat+1) as? UIButton
lastBtn?.setBackgroundImage(R.image.button(), for: .normal)
self.selectedCat = sender.tag-1
self.itemsCollection.reloadData()
sender.setBackgroundImage(R.image.button_selected(), for: .normal)
}
}
then add to scroll view
let catScrollingView = catButtonView(buttonSize: CGSize(width: 200.0, height:50.0))
catScrollView.contentSize = catScrollingView.frame.size
catScrollView.subviews.forEach({ $0.removeFromSuperview() })
catScrollView.addSubview(catScrollingView)
catScrollView.showsHorizontalScrollIndicator = true
catScrollView.indicatorStyle = .default
self.itemsCollection.reloadData()
Anyone know what I missed?
It is because the if statement in your catButtonPressed function is false with the last button's tag, which makes the code inside never be executed.
Let's say you have 5 categories so your categories.count = 5. So your self.selectedCat ranges from 0 to 4. In your for i in 0...(categories.count) the last button's tag is button.tag = 5
Now in your catButtonPressed function. If the last button is the last category, then your self.selectedCat will be 4 and sender.tag will be 5
func catButtonPressed(sender:UIButton){
print(sender.tag)
if(4 != 5-1) {
// Any code in here will never be executed because 4 != 4 is false
}
}

Selector method of UIButton not called during animation

I have dynamically created buttons on a dynamic view and later I add that dynamic view to main view.
Everything is working well except that the selector method of UIButton is not called.
var baseView:UIView?
override func viewDidLoad() {
super.viewDidLoad()
randomizeButtons()}
func randomizeButtons(){
baseView = UIView(frame:CGRectMake(view.bounds.origin.x + 10, view.bounds.origin.y + 50, view.bounds.size.width, view.bounds.size.height-20))
let viewWidth = baseView.bounds.size.width;
let viewHeight = baseView.bounds.size.height;
baseView.userInteractionEnabled = true
let i = GlobalArrayofUsers.count
var loopCount = 0
while loopCount < i{
let userBaseView = UIView(frame: CGRectMake(abs(CGFloat(arc4random()) % viewWidth - 90) , abs(CGFloat(arc4random()) % viewHeight - 120), 90, 90))
print(userBaseView.frame)
userBaseView.backgroundColor = UIColor.redColor()
userBaseView.userInteractionEnabled = true
userBaseView.layer.cornerRadius = 45
userBaseView.clipsToBounds = true
let button = UIButton(frame: userBaseView.bounds)
button.backgroundColor = UIColor.greenColor()
button.showsTouchWhenHighlighted = true
button.addTarget(self, action: #selector(ViewController.handleTapEventofBtn(_:)), forControlEvents: .TouchUpInside)
button.setTitle("hello", forState: .Normal)
button.userInteractionEnabled = true
userBaseView.addSubview(button)
baseView.addSubview(userBaseView)
print("button frame is\(button.frame)")
baseView.bringSubviewToFront(userBaseView)
loopCount += 1
}
}
baseView.subviews.forEach { (users) in
UIView.animateWithDuration(1, delay: 0, options: [.Autoreverse,.Repeat], animations: {
users.center.y += 10
}, completion: nil)
}
view.insertSubview(baseView, atIndex: view.subviews.count)
}
func handleTapEventofBtn(sender: UIButton!) {
// handling code
}
Any idea why my selector method is not called? It is due to animation. How can i handle it.
SOLUTION: Allow userinteraction in UIAnimationOptions
The problem is that you are trying to click an object that is being animated. From the docs:
During an animation, user interactions are temporarily disabled for
the views being animated. (Prior to iOS 5, user interactions are
disabled for the entire application.)
But worry not, the solution is very easy, just call your animation with the option .AllowUserInteraction. For example (with your code):
UIView.animateWithDuration(1, delay: 0, options: [.Autoreverse,.Repeat,.AllowUserInteraction], animations: {
users.center.y += 10
}, completion: nil)
Have you tried this way:
for _ in 0...5{
// generate new view
let userBaseView = UIView(frame: CGRectMake(abs(CGFloat(arc4random()) % viewWidth - 90) , abs(CGFloat(arc4random()) % viewHeight - 120), 90, 90))
userBaseView.backgroundColor = UIColor.redColor()
// add button to view
let button = UIButton(frame: userBaseView.bounds)
button.backgroundColor = UIColor.yellowColor()
button.showsTouchWhenHighlighted = true
button.addTarget(self, action: #selector(self.handleTapEventofBtn(_:)), forControlEvents: .TouchUpInside)
button.setTitle("hello", forState: .Normal)
userBaseView.addSubview(button)
}
self.view.addSubview(userBaseView)
All you need is to create userBaseView before cycle, then create buttons in cycle and add them to userBaseView, after that just add userBaseView to self.view or where you need to add it. In your example you create userBaseView in every iteration of the loop.
I think the problem could be here:
userBaseView.addSubview(button)
// add new view containing button to self.view
self.view.addSubview(userBaseView)
}
userBaseView.addSubview(button)
baseView.addSubview(userBaseView)
First you add userBaseView to self.view in the loop:
// add new view containing button to self.view
self.view.addSubview(userBaseView)
then add it to baseView:
baseView.addSubview(userBaseView)
Can you post how it looks like in IB?
The action has a parameter so the selector signature is actually takeActionBtn:
(note the colon at the end).
You need to change that or the button will try to call a version of the function with no parameter which doesn't exist.
button.addTarget(self, action: "button:",forControlEvents: UIControlEvents.TouchUpInside)
func button(sender: UIButton!) {
// handling code
print("1")
}
// hi. your problem is in the selector line. try this:
for _ in 0...5 {
// generate new view
let userBaseView = UIView(frame: CGRectMake(abs(CGFloat(arc4random()) % self.view.frame.size.width - 90) , abs(CGFloat(arc4random()) % self.view.frame.size.height - 120), 90, 90))
userBaseView.backgroundColor = UIColor.redColor()
// add button to view
let button = UIButton(frame: userBaseView.bounds)
button.backgroundColor = UIColor.yellowColor()
button.showsTouchWhenHighlighted = true
button.addTarget(self, action: "buttonAction", forControlEvents: UIControlEvents.TouchUpInside)
button.setTitle("hello", forState: .Normal)
userBaseView.addSubview(button)
self.view.addSubview(userBaseView)
}
// tested with xcode 7.2
func buttonAction() {
print("working")
}

Resources