UISegmentedControl calls action method on changing selectedSegmentIndex programmatically - objective-c

I have a a UISegmentedControl in my xib file. It is linked to an action method on value changed event in the xib file.
When I programmatically set the value of selectedSegmentIndex the action method gets called
mysegmentedcontrol.selectedSegmentIndex = index
I was expecting that the action method would only be called when the user changes the control by touching it?
This happens only for UISegmentedControl.

.h file
BOOL isProgramaticallyChanged;
.m file
- (IBAction)segmentAction:(id)sender { // valuechanged connected function
UISegmentedControl *segControll = (UISegmentedControl *)sender;
if (segControll.tag == 55) { // while create segment specify tag value to 55 (to set use via IB or Code)
if (isProgramaticallyChanged == NO) {
// your valuechanged code here
}
else {
isProgramaticallyChanged = NO; //important
}
}
else if (segControll.tag == 66) { // for many segments
}
//...like this do for all segments
}
in .m file
wherever you put this code to change programmatically do before that like this
if (mysegmentedcontrol.selectedSegmentIndex != index) {
isProgramaticallyChanged = YES;
mysegmentedcontrol.selectedSegmentIndex = index;
}

The solution is probably to link an IBAction method to a touchUpInside event and propagate the value change there if you plan to also change the selected index programmatically.
From what we can read even in Cocoa Fundamentals Guide, events coming from the UI controls should be only sent when the event is triggered in response to the user acting on the control, not from a programmatic change. It's either my misunderstanding, or it's some kind of a UISegmentedControl bug.
My solution in more detail
Connect an IBAction method to UISegmentedControl's Touch Up Inside event and forward the sender parameter to the action method handling Value Changed. That way if there's a programmatic change of the value, the control won't call the value change handler. Only when it's by immediate user action on the control.
The only thing to solve here is to detect whether the selected index actually changed.

I'll add something more to the answers which may feel a little more controllable.
Simply call removeTarget, do your programmatic update to the selected segment, re-add the target (for UIControlEventValueChanged)
I came here looking for an answer, the one provided seemed to work, but then it came to me that doing the remove/add target felt more appropriate and works.

Does not happen anymore.
The UIControlEventValueChanged action is invoked when the segment changes via a user event.

Related

NSWindow, press key ENTER: how to limit the key listening to the focused NSControl?

I have an NSWindow with a main "OK" button. This button has as "key equivalent" property in interface builder, the key ENTER i.e ↵.
It works good, but now I have a new NSComboBox, which is supposed to invoke a method when the user selects a list item, or he preses Enter / ↵.
However, when I press Enter, the main Button receive the notification and the window close. How to prevent this?
This is the normal behavior what you are getting, but you can hack a bit, by removing and adding the key-equivalent.
Add following delegates of NSComboBox:
- (void)comboBoxWillPopUp:(NSNotification *)notification;{
[self.closeButton setKeyEquivalent:#""];
}
- (void)comboBoxWillDismiss:(NSNotification *)notification;{
[self.closeButton setKeyEquivalent:#"\r"];
}
One way you can workaround for prevent enter notification is like that below:-
//Connect this action method to your combobbox and inside that set one BOOL flag to yes
- (IBAction)comBoxItm:(id)sender
{
self.isEnterCalled=YES;
}
//Now check this flag to your some method where close window is called
-(void)someMethod
{
//Check the flag value if it is yes then just ignore it
if (!self.isEnterCalled)
{
//Close window logic
}
self.isEnterCalled=NO;
}
Ran into the same problem. Had "hot key" which I'd like to switch off while editing some text fields. I found solution for myself. There's no need in override lots of NSTextField base methods.
Firstly, I removed all the "key equivalents". I used to detect Enter key down with the + (void)addLocalMonitorForEventsMatchingMask:(NSEventMask)mask handler:(NSEvent *(^)(NSEvent *))block class method of NSEvent. You pass block as a parameter, where you can check for some conditions. The first parameter is the event mask. For your task it would be NSKeyDownMask, look for other masks at the NSEvent Reference Page
The parameter block will perform each time the user pushes the button. You should check if it is right button pushed, and - generally - if the current window first responder isn't some editable control. For that purposes we need NSWindow category class just not to implement this code each time we deal with NSKeyDownMasked local monitors.
NSWindow+Responders class listing:
#interface NSWindow (Responders)
- (BOOL)isEditableFirstResponder;
#end
#implementation NSWindow (Responders)
- (BOOL)isEditableFirstResponder
{
if (!self.firstResponder)
return NO; // no first responder at all
if ([self.firstResponder isKindOfClass:[NSTextField class]]) // NSComboBox is NSTextField subclass
{
NSTextField *field=(NSTextField *)self.firstResponder;
return field.isEditable;
}
if ([self.firstResponder isKindOfClass:[NSButton class]]) // yep, buttons may be responders
return YES;
return NO; // the first responder is not NSTextField or NSButton subclass - not editable
}
#end
Don't know if there's another way to check if we are now editing some text field or combo box. So, there's at least the part you add the local monitor somewhere in your class (NSWindow, NSView, some controller etc.).
- (void)someMethod
{
id monitor=[NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:(NSEvent *)^(NSEvent *theEvent){
if (theEvent.keyCode==/*Enter key code*/ && ![self.window.isEditableFirstResponder]) // you should check the key modifiers too
{
// your code here
}
return theEvent; // you may return the event to pass the key to the receiver
}];
}
Local monitors is safe remedy about the Apple rules. It works only inside your application. For global key down events you may use addGlobalMonitor but Apple may reject your app from the AppStore.
And don't forget to remove the monitor when there's no need in it.
- (void)viewControllerShutdownMethod
{
[NSEvent removeMonitor:monitor];
}
Good luck.

Disable menu highlight when pressing shortcut key

I would like to disable the Application "menu highlight" that happens when you press a shortcut key assigned to an NSMenuItem that belongs to the specific menu in question.
The issue is that in the application you use the keyboard quite a bit and having the menus becoming highlighted all the time becomes a bit annoying but I still want to have the menus (including the shortcuts) there as it shows the user which actions that can be used.
Declare a custom NSMenuItem subclass and start using that custom class instead of NSMenuItem.
In this class you should override this method:
- (BOOL)isHighlighted
{
return NO;
}
This way you will not have the menu item highlighted.
EDIT
Try this:
[item setOnStateImage: item.offStateImage];
FFR: Look up the following methods in the docs:
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
Will work for both selecting the menu item and the associated command key.
Within your NSDocument provide a body for validateMenuItem
such as,
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
SEL theAction = [menuItem action];
if (theAction == #selector(openPreferencesPanel:)) {
return !_isCurrentlyModal; //A BOOL in MyDocument
}
return [super validateMenuItem:menuItem]; // Keep this for proper cut, paste, etc validation
}
In your case, the above selector might be highlight:. Check the nib/xib and inspect it. It might be attached to the First Responder. Copy the method name.
Also have a gander at for more general items (buttons, etc) and also includes menu items.
- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem

What ordes does the IBActions fire in when multiple IBActions supplied for same event

As per my header, I am building a view with some UIButtons in it.
I was thinking about hooking up 2 actions to one UIButton event, since that button needs to do two things, but I need to do them in a certain order.
No matter how I try, I can't seem to change the order in which the Actions fire. Is there a way for me to decide this, or is it random?
When I right-click the UIButton in Interface Builder, I see both Actions added to the same event, but no matter in which order they appear does the firing order change.
Hoping some of you out there can help me.
UIButtons are subclasses of UIControl, which use the target-action pattern to communicate events. The programatic interface for adding targets is exposed via the addTarget:action:forControlEvents: method. Hooking up IBActions in interface builder is just a visual way of using this same interface; the bundle loader will call that method on the unarchived button when loading the .xib file that contains it.
The key point is that the target argument in that method is added to an NSSet (or mutable subclass) internally by the UIControl. NSSets are, by definition, unordered. This means that when the button needs to enumerate its set of targets to dispatch events, the order of enumeration is not well defined.
You will not get answer to this question in any book or even googling.
I have tried that personally and it work in alphabetical order according to IBActions Names. Strange but its a fact!!
Tested Example Below :
helloworld.h
-(IBAction)a:(id)sender;
-(IBAction)b:(id)sender;
-(IBAction)c:(id)sender;
-(IBAction)d:(id)sender;
-(IBAction)e:(id)sender;
-(IBAction)f:(id)sender;
Action)g:(id)sender;
**HelloWorld.m file**
-(IBAction)a:(id)sender
{
NSLog(#"I clicked a");
}
-(IBAction)f:(id)sender
{
NSLog(#"I clicked f");
}
-(IBAction)b:(id)sender
{
NSLog(#"I clicked b");
}
-(IBAction)c:(id)sender
{
NSLog(#"I clicked c");
}
-(IBAction)d:(id)sender
{
NSLog(#"I clicked d");
}
-(IBAction)e:(id)sender
{
NSLog(#"I clicked e");
}
Now link these actions to your Button in [Touch Up Inside] event in any order. You will find the same output as shown below :
Output
2012-03-19 15:35:34.548 I clicked a
2012-03-19 15:35:34.554 I clicked b
2012-03-19 15:35:34.564 I clicked c
2012-03-19 15:35:34.566 I clicked d
2012-03-19 15:35:34.568 I clicked e
2012-03-19 15:35:34.572 I clicked f
Cheers!!
Happy Coding!!
Since there's no clear way to indicate the order in which the actions should be sent, you should not rely on the order. Instead, create a separate action that calls the two actions that you care about in the desired order:
- (IBAction)doFooAndBar:(id)sender
{
[self foo:sender];
[self bar:sender];
}

Toggle-style button : how to toggle title in this case?

I have a CoreData app presenting data with a TableView, textfields, buttons... It deals with people situations and one of the button is a toggle-style button with title "Close". When we consider the user's case closed, we press and it changes the state of a boolean-type attribute in the entity, representing the closed/open state of the case, using a simple binding to the attribute value. The button title also becomes "Reopen" as the case may be reopened in the future.
Then additional things had to be done with the data on pressing the button, so I had to create an IBAction method instead of simply use the former binding. Problem: when button is pressed, the action is done, but the button title is not toggled. It makes sense since nothing tells it to toggle anymore.
I decided to remove the action on the boolean from the IBAction and use again the value binding, so the boolean change is performed by the binding and the other operations are performed by the IBAction. Problem: it modifies the data unexpectedly, sometimes working fine, sometimes not doing all things in a coherent way as expected.
So I'm back with all changes handled by the IBAction and this time, I'm using the Title/Alternate title bindings instead of the value binding. Now the button title toggles, but instead of displaying the word "Close" and "Reopen", it displays the boolean values "0" and "1".
I should perhaps handle the button title change in the IBAction as well, using "setTitle", but then I see a new problem coming. On app start-up, how will it pick the appropriate entity record for reference? And what if the table is in a "No Selection" situation? Looks like a quite extensive piece of code to handle such a small issue...
Any advice on a short, more direct way of handling this is welcome. Thanks.
Sounds like you probably have a couple of different options.
This first option is a bit more involved than the others, but is still good to know for the record. This option basically would not use bindings for the close/re-open button, but instead, set the title, etc. programmatically. The basic game plan would be as follows:
The close/re-open button's initial title when the document opens is set in the nib file (e.g. Close).
Optionally, when the document opens, you could disable the button in -awakeFromNib, since the table view will have no initial selection.
The only actions that would necessitate the button's title or enabled state to be changed is 1) when the tableview's selection is changed, and 2) when you've clicked the close/re-open button to toggle the case's state.
To achieve the desired result, you'd create an IBOutlet to the close/re-open button if you don't already have one. You'd then connect up the tableview's delegate outlet to your controller class, which will let your controller class know when the tableview's selection has been changed (via the - (void)tableViewSelectionDidChange:(NSNotification *)notification <NSTableViewDelegate> protocol method). You would also need to update the close/reopen IBAction method to make sure it switches the title on the button after being clicked (since the tableview selection wouldn't change during that operation). The controller class's code might look something like this:
// add this declaration to avoid compiler warnings:
#interface LHController (LHPrivate)
- (void)updateCloseReopenButtonTitle;
#end
- (void)awakeFromNib {
[self updateCloseReopenButtonTitle];
}
- (void)tableViewSelectionDidChange:(NSNotification *)notification {
[self updateCloseReopenButtonTitle];
}
- (IBAction)toggleCloseReopenButton:(id)sender {
// do your existing code here and then add:
[self updateCloseReopenButtonTitle];
}
- (void)updateCloseReopenButtonTitle {
// assuming 'casesController' is an IBOutlet to your NSArrayController
NSArray *selectedObjects = [casesController selectedObjects];
// loop through the selected cases to determine whether
// they're all closed, all open, or mixed to set
// the title of the button appropriately
BOOL allClosed = YES;
for (LHCase *case in selectedCases) {
if ([case isOpen]) {
allClosed = NO;
break;
}
}
[closeReopenButton setTitle:(allClosed ? #"Reopen" : #"Close")];
[closeReopenButton setEnabled:[selectedObjects count] > 0];
}
Before bindings came along, this is how we used to have to do things.
Another option might be reconsidering your user interface: maybe rather than a push/toggle button whose title you need to toggle, you could instead just have a checkbox titled Closed, which would signify whether the selected cases were closed or not. You could use bindings for the checkbox's state, like shown in the image below:
You could then have an IBAction method that handles the extra stuff that needs processing. You can ask the casesController for the -selectedObjects and then loop through them. As long as you make sure the checkbox Allows Mixed states, it has the added advantage of better representing mixed-case scenarios, in case of a selection of cases with mixed open/closed states (the checkbox a dash instead of a full check).
Another option if you want to stick with a toggle button is to create and specify a custom NSValueTransformer for the title and alternate title bindings. This value transformer would take in the boolean closed/open state of the case and turn it into a string more fitting than just 0 or 1 (which is what's being displayed now). It might look something like this:
+ (Class)transformedValueClass {
return [NSString class];
}
+ (BOOL)allowsReverseTransformation {
return NO;
}
- (id)transformedValue:(id)value {
BOOL isClosed;
if (value == nil) return nil;
// Attempt to get a reasonable value from the
// value object.
if ([value respondsToSelector: #selector(boolValue)]) {
isClosed = [value boolValue];
} else {
[NSException raise: NSInternalInconsistencyException
format: #"Value (%#) does not respond to -boolValue.",
[value class]];
}
return (isClosed ? #"Reopen" : #"Close");
}

UIPageControl value changed not firing

I have an issue with the UIPageControl. I simplified the issue for clarity below:
I put a button and a UIPageControl in my app with Interface builder and put the following in the code:
- (IBAction)tappedButton:(id)sender{
self.pageControl.currentPage = 3;
NSLog(#"Tapped Button");
}
- (IBAction)changePage:(id)sender{
NSLog(#"PAGE Changed!!!!");
}
I attached the Value Changed action to the pageController via Interface Builder.
When I tap on the button, I see the output "Tapped Button" and the third dot is highlighted... but the changePage method is never called.
Any ideas?
The changePage: method will only be called when the users request a change of page using the UIPageControl. It won't be called when the user taps the button. If you want to call the changePage: method when the button is pressed called it explicitly:
[self changePage: nil];
UIPageControl doesn't actually change pages for you. You have to write that code, inresponse to the valueChanged event. So, you want to connect that event of your UIPageControl object to your output (tappedButton: I guess), and then call your changePage: method to actually change the UI. Does that help?
There are some example projects referenced from the Apple docs that use UIScrollView and UIPageControl together. Check 'em out.