I have seen similar questions on here before, but they relate to just simple buttons, not bar button items. Keep in mind- bar button items and regular buttons have different properties, thus the answers on the other posts will not work in this situation.
Is there a way, in swift, that I can make my refresh bar button item spin 360 degrees one time (to show that an action occurred)?
In order to animate a UIBarButtonItem, we need to assign it a customView and animate that instead.
What I've done below is create an ordinary UIButton, and I've assigned it's image to a refresh icon named "refresh". Next, I set my UIBarButtonItem's customView to be my new button. Now that barButton has a customView, I can call my rotateBarButton() method to perform the animation code.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var barButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(frame: CGRectMake(0, 0, 40, 40)) // Create new button & set its frame
button.setImage(UIImage(named: "refresh"), forState: .Normal) // Assign an image
barButton.customView = button // Set as barButton's customView
}
func rotateBarButton() {
// Gets you half way there //
UIView.animateWithDuration(0.05, delay: 0.0, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.barButton.customView?.transform = CGAffineTransformMakeRotation(CGFloat(M_PI))
}, completion: nil)
// Rotates all the way around //
UIView.animateWithDuration(0.5, delay: 0.5, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.barButton.customView?.transform = CGAffineTransformMakeRotation(CGFloat(M_PI * 2))
}, completion: nil)
}
}
Related
I have a set of buttons I'd like to have crossfade through a different highlighted image than normal when touched. The technique below works fine, but only after the second touch event on the same button. The first touch produces the standard "faded dark grey" on the whole button; from then on future touches it crossfades correctly through the pictures on highlight.
I've only tested this in the simulator, is that he issue? If not, how do I produce the correct highlighted state on every touch? Thanks!
#IBOutlet weak var someButton: UIButton!
#IBAction func someButton(_ sender: UIButton) {
UIView.transition(with: sender, duration: 0.3, options: .transitionCrossDissolve, animations: {
sender.setImage(UIImage(named: "My Image.png"), for: UIControl.State.highlighted)
}, completion: nil)
//...other functions
}
Set the button's highlighted image initially at the time of setting up UIButton properties or in viewDidLoad() by writing the code below.
yourButton.setImage(UIImage(named: "My Image.png"), for: UIControl.State.highlighted)
Reason for not working your code
The button action #IBAction func someButton(_ sender: UIButton) always call only on release of the user's finger so by this time UIButton doesn't has the image for the highlighted state for the first time. Later on, the image is set through animation so it is animating perfectly.
Edit
You can make transition of UIButton with firebase call or some other event action by creating a separate function like shown below. Use the UIButton's outlet to access the button.
#IBOutlet weak var someButton: UIButton!
func buttonTransition() {
UIView.transition(with: sender, duration: 0.3, options: .transitionCrossDissolve, animations: {
someButton.setImage(UIImage(named: "My Image.png"), for: UIControl.State.highlighted)
}, completion: nil)
}
So, you can call buttonTransition() function from wherever you want.
It is working on the second click because you have written the transition code inside the button click action. Just write that code in the viewDidLoad or viewWillApear and it will work before the first click and will set the transition on the button. Write something like this in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
UIView.transition(with: button, duration: 0.3, options: .transitionCrossDissolve, animations: {
self.button.setImage(UIImage(named: "image.png"), for: UIControl.State.highlighted)
}, completion: nil)
}
here button is the outlet variable of the button created in storyboard.
Sorry if any similar question(s) has been answered. But, I just can't seem to figure this one out.
I have reached my goal, to bind the "Log In" button to the keyboard, basically pushing it from the bottom of the screen to the top of the keyboard using an extension below.
picture: Initial view without keyboard.
picture: keyboard has been launched.
My UIView extension:
import UIKit
extension UIView{
func bindToKeyboard(){
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChange(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
#objc func keyboardWillChange(_ notification: NSNotification){
let duration = notification.userInfo![UIResponder.keyboardAnimationDurationUserInfoKey] as! Double
let curve = notification.userInfo![UIResponder.keyboardAnimationCurveUserInfoKey] as! UInt
let beginningFrame = (notification.userInfo![UIResponder.keyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
let endFrame = (notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
let deltaY = endFrame.origin.y - beginningFrame.origin.y
UIView.animateKeyframes(withDuration: duration, delay: 0.0, options: UIView.KeyframeAnimationOptions(rawValue: curve), animations: {
self.frame.origin.y += deltaY
}, completion: nil)
}
}
and I called bindToKeyboard() to loginBtn in the viewDidLoad() of my LoginVC like so:
loginBtn.bindToKeyboard()
The problem here is, after the first tap to the textfield (either email or password field), the button disappears. After the keyboard is closed, the button is actually back to its initial position just like in the first picture. Then calling the keyboard again by tapping one of those textfields, the button works properly. But the second and so forth tap, it does not.
The point of my question:
how can I implement the extension to be able to work properly with multiple textfields/textviews?
If that's not possible, how should I approach this problem?
I am sorry if my explanation and or English is unclear.
Thank you so much.
In this animation, if you use frame to control the position of button, the button is supposed to be free of constrains in vertical direction.
UIView.animateKeyframes(withDuration: duration, delay: 0.0, options: UIView.KeyframeAnimationOptions(rawValue: curve), animations: {
self.frame.origin.y += deltaY
}, completion: nil)
I use this animation well after removing all the constraints from UIButton. Otherwise, self.frame.origin.y += deltaY should be replaced with constraint constant.
Good lucky with moving buttons.
I had the exact same problem and initially I tried handling it via the UIKeyboardNotification method but the problem was when the different UITextField was being edited when the Keyboard had changed its state, it won't register for any state change as it was already active. Therefore after much exploring I handled it via creating an accessoryView with a new button that is a duplication of my actual button. So basically, consider you have a UIButton which is sticking at the bottom of the UIViewController and you have 2 UITextField which when switched interchangeably, cannot feel that the UIButton was ever moved elsewhere but remain stuck on the keyboard. This following piece of code explains how to cater this problem:
#IBOutlet weak var signInBtn: UIButton!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var emailTextField: UITextField!
var accessoryViewKeyboard:UIView?
var btnAccessory:UIButton?
override func viewDidLoad() {
super.viewDidLoad()
passwordTextField.delegate = self
emailTextField.delegate = self
accessoryViewKeyboard = UIView(frame: signInBtn.frame)
//Inputting the "accessoryViewKeyboard" here as the "inputAccessoryView" is of
//utmost importance to help the "signInBtn" to show up on tap of different "UITextFields"
emailTextField.inputAccessoryView = accessoryViewKeyboard
passwordTextField.inputAccessoryView = accessoryViewKeyboard
setupBtnWithKeyboard()
}
func setupBtnWithKeyboard() {
btnAccessory = UIButton(frame: CGRect(x: signInBtn.frame.origin.x, y: signInBtn.frame.origin.y, width: self.view.frame.size.width, height: signInBtn.frame.size.height))
accessoryViewKeyboard?.addSubview(btnAccessory!)
btnAccessory?.translatesAutoresizingMaskIntoConstraints = false
btnAccessory?.frame = CGRect(x: (accessoryViewKeyboard?.frame.origin.x)!,
y: (accessoryViewKeyboard?.frame.origin.y)!,
width: self.view.frame.size.width,
height: (accessoryViewKeyboard?.frame.size.height)!)
btnAccessory?.backgroundColor = UIColor(red: 31/255, green: 33/255, blue: 108/255, alpha: 1)
btnAccessory?.setTitle("Sign In", for: .normal)
btnAccessory?.titleLabel?.font = .systemFont(ofSize: 22)
btnAccessory?.titleLabel?.textColor = UIColor.white
btnAccessory?.titleLabel?.textAlignment = .center
btnAccessory?.isEnabled = true
btnAccessory?.addTarget(self, action: #selector(SignIn.signInBtnPressed), for: .touchUpInside)
NSLayoutConstraint.activate([
btnAccessory!.leadingAnchor.constraint(equalTo:
accessoryViewKeyboard!.leadingAnchor, constant: 0),
btnAccessory!.centerYAnchor.constraint(equalTo:
accessoryViewKeyboard!.centerYAnchor),
btnAccessory!.trailingAnchor.constraint(equalTo:
accessoryViewKeyboard!.trailingAnchor, constant: 0),
btnAccessory!.heightAnchor.constraint(equalToConstant: signInBtn.frame.size.height),
])
}
And you're done. This will keep the UIButton always present on the Keyboard. Important thing is no matter how many instances of UITextField you introduce, always input the accessoryViewKeyboard as its inputAccessoryView.
I have a view controller (OrangeVC) that I add to a class that contains a new keyWindow(NewKeyWindowClass). A button in a different vc is tapped and it triggers this new window to get shown over the app's main window and it animates from the right side bottom of the screen to fill to the top. The animation works fine, it starts from the bottom and fills the screen with a new vc with a orange background. The problem is once the OrangeVC is added to the NewKeyWindowClass the orangeVC's deinit keeps getting triggered.
Why is it's deinit running?
Class that goes inside Animator Class:
class OrangeController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .orange
}
deinit {
print("OrangeVC -Deinit")
}
}
AnimatorClass:
import UIKit
class NewKeyWindowClass: NSObject {
func animateOrangeVCFromBottomToTop() {
guard let keyWindow = UIApplication.shared.keyWindow else { return }
let orangeVC = OrangeController()
// 1. starting frame
orangeVC.view.frame = CGRect(x: keyWindow.frame.width - 10, y: keyWindow.frame.height - 10, width: 10, height: 10)
keyWindow.addSubview(orangeVC.view)
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
// 2. ending frame
orangeVC.view.frame = keyWindow.frame
})
}
}
Button from a different class that triggers the animation:
#IBAction func triggerAnimationButtonPressed(_ sender: UIButton) {
let newKeyWindowClass = NewKeyWindowClass()
newKeyWindowClass.animateOrangeVCFromBottomToTop()
}
I got the answer from this reddit
An iOS application must have a rootViewController, create one and set
the keyWindow.rootViewController property to it. Then present your
view controller from that. Or just the rootViewController to be your
View Controller actually.
The reason the RedVC kept running it's deinit was because the keyWindow didn't have a rootViewController. I added the RedVC's view as a subview to the keyWindow keyWindow.addSubview(orangeVC.view) instead of making it it's rootVC:
keyWindow.rootViewController = redVC
Once I added it that the RedVC's deinit no longer ran when the animation occurred.
It should be noted that although it stopped the deinit from running I lost the animation and it also made the original keyWindow disappear. I should actually add this to a different UIWindow.
I have a UIView, inside this view I have UIImageView. Also I have a button inside this UIView. What I want to do is when I click this button I want to make a flip animation and remove my UIImageView and load another view into this super view. In my button click even I did something like this
func shareClick()
{
print("SHARE CLICK")
if showingBack {
UIView.transitionWithView(shareView, duration: 1.0, options: .TransitionFlipFromRight, animations: {
self.imgVwTop.removeFromSuperview()
}, completion: nil)
showingBack=false
}
else
{
UIView.transitionWithView(imgVwTop, duration: 1.0, options: .TransitionFlipFromRight, animations: {
self.shareView.removeFromSuperview()
}, completion: nil)
showingBack=true
}
}
I'm confused with the ebhaviour and don't understand exactly how to do it. This button click event doing nothing here.
You can do the following (I assume that share view contains the image view and the button):
override func viewDidLoad()
{
super.viewDidLoad()
shareView = UIView(frame: CGRectMake(30, 100, 300, 400)) //set the frame of the holder view
flippedView = UIView(frame: shareView!.bounds) //setup flipped view
flippedView!.backgroundColor = UIColor.redColor() //for test
isFlipped = false //initially not flipped
//set up the initial view with image and button
aImageView = UIImageView(frame: shareView!.bounds)
aImageView!.image = UIImage(named: "1.jpg")
shareButton = UIButton(type: .System)
shareButton!.setTitle("share", forState: .Normal)
shareButton!.frame = CGRectMake(0, 0, 150, 50)
shareButton!.addTarget(self, action: "shareButtonAction", forControlEvents: .TouchUpInside)
//add both imageview and button to holder view
shareView!.addSubview(aImageView!)
shareView!.addSubview(shareButton!)
//finally add holder to self view
self.view.addSubview(shareView!)
}
Here, you can't remove the super view of the image view if you use the transitionWithView method. The best you can do is replace the image view with new view that you want to show after being flipped. Once again, you can flip back to the image view by adding it as subview. For example:
func shareButtonAction()
{
if (self.isFlipped! == false)
{
UIView.transitionWithView(shareView!, duration: 0.5, options:.TransitionFlipFromRight, animations: { () -> Void in
// self.aImageView!.image = UIImage(named: "2.jpg")
//hear remove the imageview add new view, say flipped view
self.aImageView!.removeFromSuperview()
self.shareView!.addSubview(self.flippedView!)
}, completion: { (Bool) -> Void in
self.isFlipped! = true
self.shareView!.bringSubviewToFront(self.shareButton!) //button should be top of the holder view
})
}
else
{
UIView.transitionWithView(shareView!, duration: 0.5, options:.TransitionFlipFromRight, animations: { () -> Void in
//move back, remove flipped view and add the image view
self.flippedView!.removeFromSuperview()
self.shareView!.addSubview(self.aImageView!)
}, completion: { (Bool) -> Void in
self.isFlipped! = false
self.shareView!.bringSubviewToFront(self.shareButton!)
})
}
}
So it looks like you have the right idea, I believe you have things set up correctly with the view you want to flip in some sort of container.
I think it would help if you instantiated both your views programmatically, in your case a UIImageView and a Button. When you transition the other view will become unloaded because they are listed as weak so best to create them on the fly.
I made up a view controller to test the idea, initially the currentView will be instantiated from the storyboard, and then after that would be created programmatically, every time the button is pressed it will create a new view that will replace the other, perform the animation and set the new view as the currentView for the next time the button is pressed.
class ViewController: UIViewController {
#IBOutlet weak var currentView: UIView!
#IBOutlet weak var container: UIView!
var flipped = false
#IBAction func buttonPressed(sender: AnyObject) {
let new: UIView!
if flipped {
new = UIImageView(frame: container.bounds)
new.backgroundColor = UIColor.redColor()
flipped = false
}
else {
new = UIButton(frame: container.bounds)
new.backgroundColor = UIColor.blueColor()
flipped = true
}
let options: UIViewAnimationOptions = [.TransitionFlipFromLeft, .AllowUserInteraction, .BeginFromCurrentState]
UIView.transitionFromView(currentView, toView: new, duration: 0.5, options: options, completion: nil)
self.currentView = new
}
}
Hope this helps :)
This piece of code works...
self.debug.hidden = true
let image = UIImage(data: data!)
self.debug.image = image
if (swipe.direction == UISwipeGestureRecognizerDirection.Left) {
UIView.transitionWithView(self.debug, duration: 1.0, options: [.TransitionFlipFromRight], animations: {
self.debug.hidden = false
}, completion: { _ in })
}
I load a new image into my image after I hide it.
i want to create a slide out menu which has a normal width of about 50 pixels, and if the user press the expand button i will also show the labels for the button.
Like in this example:
What is the correct way to create such a menu? I though about using 2 views and set the size of contentview to width-50pixels.
But i am unable to change the frame of my UIView in the ViewDidLoad function. (this is an example)
override func viewDidLoad() {
super.viewDidLoad()
self.view.frame = CGRect(x:50, y:0, width:974, height:768)
sidebarIsOpen = false
}
And if the user click on the expand Button
#IBAction func expandButtonClicked(sender : AnyObject) {
var x = self.sidebarIsOpen! ? 50 : 300
UIView.animateWithDuration(0.2, animations: {
self.view.frame = CGRect(x:x, y:0, width:300, height:768)
}, completion: { _ in
self.sidebarIsOpen = !(self.sidebarIsOpen!)
})
}
If i click the button again, everything is fine. But on ViewDidLoad i am unable to move the contentview to right.
Thanks in advance
Ill found already the solution by myself.
Created 2 views and animate the "contentview" on button click.
#IBOutlet weak var menuView: UIView!
#IBAction func toggleMenuButton(sender: UIBarButtonItem) {
var x = self.sidebarIsOpen! ? 50 : 200
UIView.animateWithDuration(0.2, animations: {
self.contentView.frame = CGRect(x:x, y:0, width:974, height:768)
}, completion: { _ in
self.sidebarIsOpen = !(self.sidebarIsOpen!)
})
}