I have an app with some buttons, when those buttons are pressed the image on them should change. I assume that the TouchUpInside runs when you tap and remove the finger while still holding inside the area of the element, however it only works rarely and I'm not sure why.
The reason I use TouchUpInside instead of TouchDown is because I want the user to be able to cancel the action.
I'm sorry if I've misunderstood anything about those events and if this has already been asked. I couldn't find an answer to my problem searching the web.
//The IBAction is set to trigger on TouchUpInside
#IBAction func action11(sender: UIButton) {
setTile(sender)
}
func setTile(sender: UIButton) {
if turn {
print("O's turn")
sender.setImage(xTile, forState: .Normal)
turn = false
}
}
EDIT: Added the necessary code
There are some properties of UIButtons which you can use to achieve what you want.
You can use Default and selected state of uibutton to set two different images.
In XIB select state "Default" and assign default image to that state again select state to "Selected" and assign image which you want after button section.
and add following line in button selection method.
-(IBAction)buttonTapped:(UIButton *)sender{
sender.selected = !sender.selected;
}
Your understanding is correct, you need to use touchUpInside.
I assume you are trying to create a button that has a toggle function. On one touch you want the button to have the value Say "X" and when touched again the button has a value "O".
Take a look at this code below, this should do the job.
class ViewController: UIViewController {
var isButtonPressed = false{
// Adding a Property Observer, that reacts to changes in button state
didSet{
if isButtonPressed{
// Set the Value to X.
}else{
// Set the Value to O.
}
}
}
#IBAction func changeButtonValue(sender: UIButton) {
// Toggle the button value.
isButtonPressed = !isButtonPressed
}
}
If you don't set turn=true after the first time, this code is executed it will be executed only one.
if turn {
print("O's turn")
sender.setImage(xTile, forState: .Normal)
turn = false
}
Check if the button frame is large enough to get finger touch.
Apple says at least 35x35 pixel.
Related
I'm now learning to make an audio recorder app. Basically, I want to add a tap gesture to a UIView to trigger/stop the recording. After triggering my recorder, the image should be changed to another one. My question is:
Am I supposed to control drag two #IBAction functions or only one? How to distinguish the "trigger" and "stop" action? My guess is I may only need one function, and at the beginning of it, I check the image name: if it's the recording icon or the stop icon, and do something accordingly. However, I cannot find the property of UIImage to identify the image it contains.
I'm new to ios so this may not be a good question, but please bear with me. By the way, I'm doing this using interface builder.
You do this by using highlightedImage and isHighlighted properties of UIImageView. And you can do this by using single IBAction
In the viewDidLoad method :
yourImageView.image = UIImage(name:"RecordImage/TriggerImage")
yourImageView.highlightedImage = UIImage(name:"StopImage")
You can set these images in the interface builder too. As you can see in the following image you need set images for both Image and Highlighted properties.
Inside your action method:
yourImageView.isHighlighted = !yourImageView.isHighlighted
if yourImageView.isHighlighted
{
//so now the UIImageView shows stop image that means we are in recording mode
// do the actions that are to be done in recording mode like start recording updating other UI etc
}
else
{
//so now the UIImageView shows Record/Trigger image that means are in normal mode or not recording
// do the actions that are to be done in normal mode like stop recording (if required )updating other UI etc
}
You only need one IBAction for the same and you can check for the image through following code inside the action:
if yourImageView.image == UIImage(named: "yourImageName") {
//perform some action here
} else {
//perform some action here
}
Step1 - declare a property in ViewController class to check the status of button
if is checked = true //recording is going on
if false //recording is stopped
//declaration in ViewController class
var isChecked = false
Step 2 - add a button imageView and Assign its button action as below
#IBAction func BottomImageBtnAction(_ sender: UIButton) {
isChecked = !isChecked
if isChecked {
//here şet image as stop
}
else {
//here set image as Start
}
}
I work on Swift 3, and I'm trying to make checkboxes that behave like radio buttons. I explain myself : I have 8 buttons ; each has an "unchecked" image and a "checked" image. I want only 1 button to be selected (so, showing ITS "checked" image) at the time. The principle of radio buttons.
So I tried using the #IBAction with each button, I've this :
#IBAction func tona1_changed(_ sender: Any) {
if test_tona1.isSelected == true
{
test_tona1.setBackgroundImage(UIImage(named: "0b_unchecked"), for: UIControlState.normal)
test_tona1.isSelected = false
}
else
{
test_tona1.setBackgroundImage(UIImage(named: "0b_checked"), for: UIControlState.normal)
test_tona1.isSelected = true
}}
It actually make the image switch between "uncheck" and "check" but I don't know at all how to make it interact with the other buttons.
I tried using an array with the buttons's tags inside but I didn't have something working. I also tried to make a class but I don't know how it can work.
Thanks!
You can achieve that in several ways. One of the most trivial would be to:
Have a datasource. Most of the time an array.
Create an UIButton for each item in your datasource, and insert each button into another array. Each button would have a tag corresponding at the
index of the array. Also set different images for selected state and normal.
Add an action for each button, with the target function being something like:
func buttonPressed(sender:UIButton) {
for button in buttons {
button.isSelected = false
// deselect your model datasource[button.tag]
}
sender.isSelected = true
// select your model datasource[button.tag]
}
I leave the abstraction / improvements / safety to you. The key point is just to use a collection of buttons, iterate through them, and select / deselect them accordingly, and not using a big if/else checking for the button tag everytime.
I added a today extension to my app. I edited the vanilla widget a little bit and made it look like this:
Please notice the UIButton titled "Went 1st". When it gets pressed (touched up inside to be exact), it triggers this action:
#IBAction func coinChanged(sender: UIButton) {
if sender.titleLabel?.text == "Went 1st" {
coin = true
sender.titleLabel!.text = "Went 2nd"
} else {
coin = false
sender.titleLabel!.text = "Went 1st"
}
}
It basically alters between two states, changing its title and a variable accordingly.
Here is the problem though - when I press it, it indeed changes its title, but immediately changes it back, ending up on the same title as it had initially. My first thought was the action gets called twice after a press, but when I checked with print I found out it gets called only once. Sometimes the prints didn't even show up in console, but that's a different story.
So, that's one problem. There's one more, though - when I press the button, the whole widget gets misplaced. To know what I mean, look at the first picture (that's the widget before any presses) and now on this one (after the button gets pressed):
You can see that the borders are now on the very edge of TodayView. For reference, here are the constraints of the first segmented control:
Edit: Here are the constraints for "Went 1st/2nd" button:
Edit 2: Be sure to tell me what's wrong if you downvote, so I can avoid making the same mistakes next time
The problem is you should not be setting the button text like that. The title label is primarily used to set text size, font, color, etc. To set the title use something like this:
sender.setTitle("Button Title", forState: UIControlState.Normal)
So the new ib action should look like:
#IBAction func coinChanged(sender: UIButton) {
if sender.titleLabel?.text == "Went 1st" {
coin = true
sender.setTitle("Went 2nd", forState: UIControlState.Normal)
} else {
coin = false
sender.setTitle("Went 1st", forState: UIControlState.Normal)
}
}
I have a #IBAction function that accepts a sender: UIButton! as a parameter.
#IBAction func buttonPress(sender: UIButton!)
At some point in the function, I am copying the sender to another variable which was previously declared as a UIButton()
anotherVar = sender
I understand this is a reference to the original sender given UIButton is a class
However at some point of the code, I want to break the reference to sender and "reset" anotherVar to a plain vanilla UIButton(). How would I do this?
EDIT:
I feel I should expand on what I'm doing, perhaps I'm going about this the wrong way...
I have eight buttons all calling the same #IBAction function called buttonPress(). The idea is for the user to tap a button, see an image and then tap another button (out of the remaining seven) to find the matching image. When buttonPress() gets called, the code:
1. Checks to see if this is a first button being tapped
- if it is, it shows the button image and then assigns sender to anotherVar;
- if it is the second button being pressed (i.e. another button was previously clicked), the code runs a match to compare the sender's image to anotherVar's image which was set above
2. If there is a match, I "lock" the buttons so the matching logic doesn't get executed if the user taps the buttons again
3. If there is no match, I want to "clear out" anotherVar ready for another matching task. I don't want to "lock" the buttons as the same button may still need to be clicked.
Here's the full code:
#IBAction func buttonPress(sender: UIButton!) {
var buttonImage = UIImage()
buttonImage = UIImage(named: listOfImages[sender.tag])!
if (!imageIsDone[sender.tag] && (sender.tag != buttonToCompare.tag)) {
// Only execute button logic if match for image not already found and the user isn't tapping the same image
if (imageAwaitingCheck) {
// User has made their first image selection, do matching logic on image clicked
sender.setImage(buttonImage, forState: .Normal)
if (sender.currentImage == buttonToCompare.currentImage) {
// Tapped image macthes previously clicked image
println("Match")
// "Lock" the buttons as they've been matched
imageIsDone[sender.tag] = true
imageIsDone[buttonToCompare.tag] = true
imageAwaitingCheck = false
}
else {
// Tapped image does not match previously clicked image
println("No match")
imageAwaitingCheck = false
buttonToCompare.tag = 100
// ********ERROR IS HERE*********I forced this so that
// (sender.tag != buttonToCompare.tag) is true above when
// the user taps on the first button again after no match is found.
// However, this is a REFERENCE to the original sender and sets the
// button tag to 100 which causes the condition to fail and hence
// tapping button 1, then button 2, no match, then clicking button 1
// again doesn't execute any of this logic
}
}
else {
// User has selected this as the first image, simply show it
sender.setImage(buttonImage, forState: .Normal)
imageAwaitingCheck = true
buttonToCompare = sender // I am copying sender to buttonToCompare. Ideally this would create a copy but because UIButton is a class, this is creating a buttonToCompare as a reference
}
}
}
As long as anotherVar is assigned as var, not let, you can simply do the following when you're done with the sender:
anotherVar = UIButton()
This will "overwrite" the previous value of anotherVar and "reset" it to a new instance of UIButton.
However, you probably don't need to do this at all if the only place you're accessing anotherVar is this function - as long as you call anotherVar = sender, that will also replace the reference to the previous button with the new sender button.
To achieve what you describe after your edit, you don't really need to make a lot of changes. In this if-statement: if (sender.currentImage == buttonToCompare.currentImage) add buttonToCompare = UIButton() at the very end. In the else-statement, do the same instead of changing it's tag.
Alternatively, if you want the sender to be copied, you can do that like this:
let archivedData = NSKeyedArchiver.archivedDataWithRootObject(button)
let buttonCopy = NSKeyedUnarchiver.unarchiveObjectWithData(archivedData)
I have a UIButton on a form, and want to put it in a disabled state when the form is incomplete. However, I still want to be able to detect if a user attempts to press the button even in its disabled state so that the interface can let the user know that certain required fields on the form are not filled-in yet (and perhaps scroll to that field and point it out, etc.).
There doesn't seem to be any straightforward way to do this. I tried simply attaching a UITapGestureRecognizer to the UIButton but it doesn't respond when the button is in a disabled state.
I'd like to avoid subclassing UIButton if possible, unless it's the only way.
Create a fallback button. Put it behind the main button. Set its background and text colors to [UIColor clearColor] to ensure it won't show up. (You can't just set its alpha to 0 because that makes it ignore touches.) In Interface Builder, the fallback button should be above the main button in the list of subviews, like this:
Give it the same frame as the main button. If you're using autolayout, select both the main and fallback buttons and create constraints to keep all four edges equal.
When the main button is disabled, touches will pass through to the fallback button. When the main button is enabled, it will catch all the touches and the fallback button won't receive any.
Connect the fallback button to an action so you can detect when it's tapped.
Based on #rob idea, I sub-class a UIButton, and add a transparent button before someone addSubview on this button.
This custom UIButton will save many time about adjusting the UI components on the storyboard.
Update 2018/08
It works well, and add some enhanced detail to this sub-class. I have used it for 2 years.
class TTButton : UIButton {
// MARK: -
private lazy var fakeButton : UIButton! = self.initFakeButton()
private func initFakeButton() -> UIButton {
let btn = UIButton(frame: self.frame)
btn.backgroundColor = UIColor.clearColor()
btn.addTarget(self, action: #selector(self.handleDisabledTouchEvent), forControlEvents: UIControlEvents.TouchUpInside)
return btn
}
// Respect this property for `fakeButton` and `self` buttons
override var isUserInteractionEnabled: Bool {
didSet {
self.fakeButton.isUserInteractionEnabled = isUserInteractionEnabled
}
}
override func layoutSubviews() {
super.layoutSubviews()
// NOTE: `fakeButton` and `self` has the same `superView`.
self.fakeButton.frame = self.frame
}
override func willMoveToSuperview(newSuperview: UIView?) {
//1. newSuperView add `fakeButton` first.
if (newSuperview != nil) {
newSuperview!.addSubview(self.fakeButton)
} else {
self.fakeButton.removeFromSuperview()
}
//2. Then, newSuperView add `self` second.
super.willMoveToSuperview(newSuperview)
}
#objc private func handleDisabledTouchEvent() {
//NSLog("handle disabled touch event. Enabled: \(self.enabled)")
self.sendActionsForControlEvents(.TouchUpInside)
}
}
You have a great misunderstanding of user experience.
If a button is disabled, it is meant to be non-interactable.
You can not click on a disabled button, that is why it is disabled.
If you want to warn users about something when that button is clicked (e.g. form not filled correctly or completely), you need to make that button enabled. And just warn users when they click on it, instead of proceeding further with app logic.
Or you can keep that button disabled until form criteria are met, but show what is wrong with the form using another way, like putting exclamation marks near text fields, changing text field colors to red, or something like that...
But never try to add gesture recognizers, or hidden fallback buttons to a disabled button.
Check those and let me know if you see a disabled button:
https://airbnb.com/signup_login
https://spotify.com/us/signup/
https://netflix.com/signup/regform
https://reddit.com/register/
https://twitter.com/signup
https://facebook.com/r.php
https://appleid.apple.com/account
https://accounts.google.com/SignUp
https://login.yahoo.com/account/create
https://signup.live.com/signup
All the proceed buttons on these websites are always enabled, and you get feedback about what is wrong when you try to continue.
And here is really good answer: https://ux.stackexchange.com/a/76306
Long story short: disabled UI elements meant to be not-interactable.
Trying to make them interactable while they are disabled is the same to making them enabled in the first place.
So, for your question's case, it is just a styling issue. Just try styling your button, instead of making it disabled/enabled.