I have lots and lots of UIButtons, kept in a UIScrollView, all of which are dynamically tagged.
I can retrieve the button's properties, such as its tag, outside of its creation method by creating a reference to it. However, I am unable to set properties on the button, i.e. more specifically, I can't seem to setHighlighted.
Here is an example:
//UIButton is created elsewhere, i.e. UIButton *createdButton... createdButton.tag = 101
//Trigger method with createdButton as SENDER
- (void)highlightButton:(id)sender {
UIButton *buttonInstance = (UIButton *)sender;
int tag = buttonInstance.tag //use this to perform button-specific code
[buttonInstance setHighlighted:YES]; // <-- Not setting
}
Any ideas?
highlighted is typically a transient state, used to indicate that the user is touching the button. As such, it will often be re-set to NO by UIKit in the normal course of touch handling events. This seems particularly likely in your case since the method is called by an action on the button itself.
If you want to persistently change the appearance of a button, you should set selected rather than highlighted. You can set titles, images etc. for this control state (UIControlStateSelected) in the same way as you are setting them for UIControlStateHighlighted.
Related
I have a Button1 which has IBAction. Also I set target and action for my button
- (void)setTarget:(id)target action:(SEL)action {
[self.Button1 addTarget:target action:action
forControlEvents:UIControlEventTouchUpInside];
}
So when I pressed the button firstly IBAction did what he should, and than action that I set to button. Is that order always be like that ?
If you are loading you view or view controller from a nib file then yes the pattern will always be the IBAction even first followed by the target you have added to the button.
In effect adding an IBAction in Interface Builder is really just telling IB to call ["UIControl" addTarget:"id" forControlEvents:"UIControlEvent"], and you can add multiple targets to a UIButton.
In effect your code will load everything from the NIB file first (if you are using initWithNib:named:), so this will call the addTarget function on the button first with the action you have specified in Interface Builder, then at some later point the setTarget function you have above will get called, which will add another target action to the button. A UIControls targets are stored in an array which is accessed in order and will trigger if control events are met in the order they were created in.
If you look in the header file for UIControl (the super class for UIButton) you will see that NSMutableArray* _targetActions is an array. So the order is guaranteed to fire like this unless you reorder this array after it is created at some point.
I'm sure there are a lot of reasons why someone would like to have more than one button accept touches at the same time. However, most of us only need one button to be pressed at one time (for navigation, for something to be presented modally, to present a popover, a view, etc.).
So, why would Apple set the exclusiveTouch property of UIButton to NO by default?
Very old question, but deserves clarification IMO.
Despite the very misleading method documentation from Apple a view "A" with exclusiveTouch set will prevent other views from receiving events so long as A is processing some event itself (e.g. set a button with exclusiveTouch and put a finger on it, this will prevent other views in the window from being interacted with, but interaction with them will follow the usual pattern once the finger from the exlusiveTouch-item is removed).
Another effect is preventing view A from receiving events as long as some other view is interacted with (keep a button without exclusiveTouch set pressed, and the ones with exclusiveTouch will not be able to receive events as well).
You can still set a button in your view to exclusiveTouch and interact with the others, just not at the same time, as this simple test UIViewController will prove (once the correct bindings in the IB are set for both Outlets and Actions):
#import "FTSViewController.h"
#interface FTSViewController ()
- (IBAction)button1up:(id)sender;
- (IBAction)button2up:(id)sender;
- (IBAction)button1down:(id)sender;
- (IBAction)button2down:(id)sender;
#property (nonatomic, strong) IBOutlet UIButton *button1, *button2;
#end
#implementation FTSViewController
- (IBAction)button1up:(id)sender {
NSLog(#"Button1 up");
}
- (IBAction)button2up:(id)sender {
NSLog(#"Button2 up");
}
- (IBAction)button1down:(id)sender {
NSLog(#"Button1 down");
}
- (IBAction)button2down:(id)sender {
NSLog(#"Button2 down");
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Guarantees that button 1 will not receive events *unless* it's the only receiver, as well as
// preventing other views in the hierarchy from receiving touches *as long as button1 is receiving events*
// IT DOESN'T PREVENT button2 from being pressed as long as no event is being intercepted by button1!!!
self.button1.exclusiveTouch = YES;
// This is the default. Set for clarity only
self.button2.exclusiveTouch = NO;
}
#end
In light of this, the only good reason IMHO for Apple not to set exclusiveTouch to YES for every UIView subclass is that it would have made the implementation of complex gestures a real PITA, including probably some of the gestures we are already accustomed to in composite UIView subclasses (like UIWebView), as setting selected views to exclusiveTouch=NO (like button) is faster than doing a recursive exclusiveTouch=YES on pretty much everything just to enable multitouch.
The drawback of this is that in many cases the counter intuitive behaviour of UIButtons and UITableViewCells (among others...) might introduce weird bugs and make testing more tricky (as it happened to me like... 10 minutes ago? :( ).
Hope it helps
the UIView property exclusiveTouch means the view (button) is the ONLY thing in that window that can be interacted with if it is set to YES. As stated in the docs: Setting this property to YES causes the receiver to block the delivery of touch events to other views in the same window. The default value of this property is NO.
Therefore, it is the common behavior that you might have multiple buttons or interaction controls/views in a window and want exclusiveTouch set to NO.
If you set this property to YES for any UIView subclass in a window, you can not interact with anything else in the window for as long as that property is set to YES. That means if you initialize a button with exclusiveTouch = YES, but also have a table view or another button or a scroll view or any other view that is based on interaction, it will not respond to any touches.
exclusiveTouch simply means that any view underneath your UIButton will not receive the touch events.
It's set to no by default because you typically want the view underneath to receive these events. For example, if you have a UIButton on top of a scroll view and the user wants to scroll. You want the scrollView to scroll even if they begin with their finger on the UIButton.
I was just reading release notes for iOS 5 and from this version the exclusiveTouch will be set to YES by default. So just keep in mind that it will change with the new version of iOS.
Suppose I have a button that I am adding to an annotation object in a mapview:
AnnotationButton* rightButton = [AnnotationButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self
action:#selector(showDetails:)
forControlEvents:UIControlEventTouchUpInside];
You will notice that the button calls the function showDetails when it is clicked.
Show details is defined as - (void)showDetails:(id)sender; and takes a sender. Is there a way to send more variables, or associate a different sender? The reason is that I want the button clicked to tell me which annotation is associated with that button. Consider the annotation to be some other object which is available during the context where the button is created.
I thought about subclassing the UIButton class, and then storing additional information within it, but that seems like a hack.
Any ideas?
If this button is being used for the rightCalloutAccessoryView or leftCalloutAccessoryView of a MKAnnotationView, your map's delegate should receive the message mapView:annotationView:calloutAccessoryControlTapped: when the button is tapped. This hands you the MKAnnotationView instance that was tapped, which has an annotation property to give you the corresponding annotation. You should make use of that instead of trying to use an action on the button directly.
No, there is no way to change what is sent to the action message. You can ask for two arguments, but they will be the button and the event that triggered it. To get what you want, you have two options (that I can think of now).
Use the button's tag property. You can give each button a unique tag which identifiies the annotation, such as the index of the annotation in an array. Then it is easy to get the annotation in your showDetails: method.
Subclass UIButton. There is nothing wrong with adding functionality to built in objects. All you need to add is a property to hold some object. Bonus: If you use a generic id type for the property and give it a generic name, such as representedObject, you can use it in other projects in the future too.
from Anomie Use objc_setAssociatedObject to add a value to the buttons without subclassing. You will probably want to add a category to UIButton to make it easier to use.
When I create UITextField inside Interface Builder, I can access Events tab for it, which has events like Value changed, Touch cancel, Touch drag, etc. I can assign my own methods to every of those events. How can I do the same, when I create UITextField programmatically with alloc?
Refer to Apple documentation for UIControl. After initializing your textField, call addTarget:action:forControlEvents:
example for the touch event ending an edit session
[textField addTarget:self action:#selector(handleTouchValueChanged:) forControlEvents: UIControlEventEditingDidEnd]
Instead of UIControlEventValueChanged, you should use UIControlEventEditingChanged:
[_titleTextField addTarget:self action:#selector(handleTitleValueChanged:) forControlEvents:UIControlEventEditingChanged];
UIControlEventEditingChanged fires whenever user changes value [synchronous with typing or keyup]
which could cause extra hits to your data handler routine, if you're saving values based on that event, expecting it to be some kind of final value from user input...
So i've created a custom TableViewCell, and in the nib I put a UISwitch. Before I had even hooked it up to anything, if I ran it in the simulator and clicked on it, it would switch from off to on with the animation and such.
I'm trying to add a feature where the user is only allowed to change from off to on when a certain condition is true. I want it so, when the user touches the switch, it checks if the condition is true, and if it isn't it the switch doesn't move.
I've set up an IBAction where if the user Touches Up Inside, it'll run my function. My function is this:
if([on_switch isOn])
{
if([my_switch canSwitchOn])
{
NSLog(#"SWITCHED ON SUCCESSFULLY");
[on_switch setOn:TRUE animated:TRUE];
}
else
{
NSLog(#"SWITCHED ON UNSUCCESSFULLY");
//Put in popup here
}
}
else
{
[[ClassesSingleton sharedSingleton] classSwitchedOff:cell_index];
[on_switch setOn:FALSE animated:TRUE];
}
However, no matter what I do, that switch will flip, even though I gave it no directions to do so. I'm pretty sure it's the auto-flip that cause it to do so even before I'd hooked anything up to it. Is there a way to turn that off?
Thanks!
What you need to do is set userInteractionEnabled property to False on your UISwitch.
If you had allready made the connection in Interface Builder to an IBOutlet you delared in your owning class, you would be able to set it in code like this:
mySwitch.userInteractionEnabled = NO;
You could also set the property directly in Interface Builder by selecting the checkbox, as shown below (However in your app, you are going to need to wire the button up to an IBOutlet anyway to implement your conditional logic.)
alt text http://www.clixtr.com/photo/ef06c1f7-8cca-40cd-a454-5ca534ceb9fe
I think you will need something like the following:
// Somewhere just after the creation of _switch
[_switch addTarget:self action:#selector(switchValueDidChange:) forControlEvents:UIControlEventValueChanged];
// Target/Action method
- (void)switchValueDidChange:(UISwitch *)sender {
if(sender.on && [self canSwitchOn] == NO){
[sender setOn:NO animated:YES];
// Popup
}
}
Problem you're having is that the switch has already committed it's on state on touch up. When that's not the case (I'm not sure, never tested) you have to check whether the switch is currently not on. This bit of code will revert the state when the user was not allowed to switch the switch.
A better way is to disable the control, but maybe that's not what you want in this case.