how to disable a button dynamically - objective-c

How to disable a button after entering a particular letter in a textfield?

Bind the text field's value to one of your object's properties and ensure to check the "updates continuously" box in Interface Builder. For this example, the property will be called theText. Then, bind the enabled state of the button using a key-value path of say containsLetterA, then in your object put the method
- (BOOL) containsLetterA
{
NSRange rangeOfLetterA = [[self theText] rangeOfString:#"A"];
return rangeOfLetterA.location != NSNotFound;
}
Then, also in your object, add the class method:
+ (NSSet *) keyPathsForValuesAffectingValueForContainsLetterA
{
return [NSSet setWithObjects:#"theText", nil];
}

Related

Subclassing NSPopUpButton to add a bindable property

I'm trying to add a bindable property to a custom NSPopUpButton subclass.
I've created a "selectedKey" property, which is meant to store a NSString associated with selected menu item.
In control init, I set self as button target and an action for the button (valueChanged:), which in turn sets "selectedKey" in accordance with user selection:
#interface MyPopUpButton : NSPopUpButton {
NSMutableDictionary *_items;
NSString *_selectedKey;
}
#property(nonatomic, readwrite, copy) NSString* selectedKey;
- (void)addItemWithTitle:(NSString *)title andKey:(NSString *)key;
#end
#implementation MyPopUpButton
- (instancetype)initWithFrame:(NSRect)frameRect {
self = [super initWithFrame:frameRect];
if (self) {
_items = [NSMutableDictionary new];
[NSObject exposeBinding:#"selectedKey"];
[super setTarget:self];
[super setAction:#selector(valueChanged:)];
}
return self;
}
- (void)addItemWithTitle:(NSString *)title andKey:(NSString *)key {
[super addItemWithTitle:title];
[_items setValue:title forKey:key];
}
- (void)valueChanged:(id)sender {
for (NSString *aKey in [_items allKeys]) {
if ([[_items valueForKey:aKey] isEqualToString:[self titleOfSelectedItem]]) {
self.selectedKey = aKey;
}
}
}
- (void)setSelectedKey:(NSString *)selectedKey {
[self willChangeValueForKey:#"selectedKey"];
_selectedKey = selectedKey;
[self didChangeValueForKey:#"selectedKey"];
[self selectItemWithTitle:[_items valueForKey:selectedKey]];
}
#end
This seems to work as expected: "selectedKey" property is changed when user changes PopUpButton selection.
Unfortunately, trying to bind this property, doesn't work.
[selectButton bind:#"selectedKey" toObject:savingDictionary withKeyPath:key options:#{NSContinuouslyUpdatesValueBindingOption : #YES }]
When selection is changed bind object is not updated accordingly.
What am I doing wrong?
I've created a "selectedKey" property, which is meant to store an NSString associated with selected menu item.
Bindings is definitely the way to go here, but your use of bind:toObject:withKeyPath:options is incorrect.
The value that you pass to the first argument must be one of the predefined values made available by Apple for that particular control. For NSPopUpButton objects, the available values are documented in the NSPopUpButton Bindings Reference. When you look through this document you'll see that there is no selectedKey option. There is however a selectedValue which has the following description:
An NSString that specifies the title of the selected item in the NSPopUpButton.
Thus the correct way to set up the binding is as follows:
[self.btn bind:#"selectedValue"
toObject:self
withKeyPath:#"mySelectedString"
options:nil];
This is all you need to do: when the action selector is fired the property stored at the keyPath you passed in as the third argument will already have been updated. This means that you can (i) get rid of the setSelectedKey method entirely, (ii) remove exposeBinding line, and (iii) remove the code within valueChanged: - Cocoa has already done this bit.
The example below implements just two methods, but, if I've understood your intentions, they should be all you need:
- (void)awakeFromNib {
self.btn.target = self;
self.btn.action = #selector(popUpActivity:);
[self.btn bind:#"selectedValue"
toObject:self
withKeyPath:#"mySelectedString"
options:nil];
// I've added a couple of additional bindings here; they're
// not required, but I thought they'd be instructive.
[self.btn bind:#"content"
toObject:self
withKeyPath:#"myItems"
options:nil];
[self.btn bind:#"selectedIndex"
toObject:self
withKeyPath:#"mySelectedIndex"
options:nil];
// Now that you've set the bindings up, use them!
self.myItems = #[#"Snow", #"Falling", #"On", #"Cedars"];
self.mySelectedIndex = #3; // "Cedars" will be selected on startup
// no need to set value of mySelectedString, because it will be
// updated automatically by the selectedIndex binding.
NSLog("%#", self.mySelectedString) // -> "Cedars"
}
- (void)popUpActivity:(id)sender {
NSLog(#"value of <selectedIndex> -> %#", self.mySelectedIndex);
NSLog(#"value of <selectedString> -> %#", self.mySelectedString);
}
A final point worth making is that none of the above should be a part of an NSPopUpButton subclass. It looks like you can - and therefore should - do everything you need to do without a custom subclass of this control. In my demo-app the code above belongs to the ViewController class, you should try doing this also.

Enable a button based on a value in the NSTableview

I have a NSTableview. I need to enable the button based on a value of a column in the tableview. For instance, In the table view i have a column, Status. I have 2 kinds of status, Withdrawn and Booked. If i click on a row which has the status as Withdrawn, i need to disable the withdraw button.
Can i be able to do it through binding? How could i do it? Pls help me out. Thanks.
Provided you create a custom NSValueTransformer, you can enable or disable the button using bindings.
You can bind the Enabled property of the button as follows:
Bind to: arrayController
Controller Key: selection
Model Key Path: status
Value Transformer: MDStatusValueTransformer
NOTE: in place of arrayController, you should select whatever the name of your array controller is in the nib file. In place of MDStatusValueTransformer, you should specify whatever class name you end up naming the class I've provided below.
As I mentioned, you'll need to create a custom NSValueTransformer. The enabled property expects a BOOL wrapped in an NSNumber, but your status property is an NSString. So, you'll create a custom NSValueTransformer that will examine the incoming status NSString, and return NO if status is equal to #"Withdrawn".
The custom NSValueTransformer should look something like this:
MDStatusValueTransformer.h:
#interface MDStatusValueTransformer : NSValueTransformer
#end
MDStatusValueTransformer.m:
#implementation MDStatusValueTransformer
+ (Class)transformedValueClass {
return [NSNumber class];
}
+ (BOOL)allowsReverseTransformation {
return NO;
}
- (id)transformedValue:(id)value {
if (value == nil) return nil;
if (![value isKindOfClass:[NSString class]]) return nil;
if ([value isEqualToString:#"Withdrawn"]) {
return [NSNumber numberWithBool:NO];
}
return [NSNumber numberWithBool:YES];
}
#end

Setting/getting global variables in objective-C

I am writing an app which is a sort of dictionary - it presents the user with a list of terms, and when clicked on, pops up a dialog box containing the definition. The definition itself may also contain terms, which in turn the user can click on to launch another definition popup.
My main app is stored in 'myViewController.m'. It calls a custom UIView class, 'CustomUIView.m' to display the definition (this is the dialog box that pops up). This all works fine.
The text links from the CustomUIView then should be able to launch more definitions. When text is tapped in my CustomUIView, it launches another CustomUIView. The problem is, that this new CustomUIView doesn't have access to the hash map which contains all my dictionary's terms and definitions; this is only available to my main app, 'myViewController.m'.
Somehow, I need to make my hash map, dictionaryHashMap, visible to every instance of the CustomUIView class. dictionaryHashMap is created in myViewController.m when the app opens and doesn't change thereafter.
I don't wish to limit the number of CustomUIViews that can be opened at the same time (I have my reasons for doing this!), so it would be a little resource intensive to send a copy of the dictionaryHashMap to every instance of the CustomUIView. Presumably, the solution is to make dictionaryHashMap a global variable.
Some of my code:
From myViewController.m:
- (void)viewDidLoad
{
self.dictionaryHashMap = [[NSMutableDictionary alloc] init]; // initialise the dictionary hash map
//... {Code to populate dictionaryHashMap}
}
// Method to pop up a definition dialog
- (void)displayDefinition:(NSString *) term
{
NSArray* definition = [self.dictionaryHashMap objectForKey:term]; // get the definition that corresponds to the term
CustomUIView* definitionPopup = [[[CustomUIView alloc] init] autorelease]; // initialise a custom popup
[definitionPopup setTitle: term];
[definitionPopup setMessage: definition];
[definitionPopup show];
}
// Delegation for sending URL presses in CustomUIView to popupDefinition
#pragma mark - CustomUIViewDelegate
+ (void)termTextClickedOn:(CustomUIView *)customView didSelectTerm:(NSString *)term
{
myViewController *t = [[myViewController alloc] init]; // TODO: This instance has no idea what the NSDictionary is
[t displayDefinition:term];
}
From CustomUIView.m:
// Intercept clicks on links in UIWebView object
- (BOOL)webView: (UIWebView*)webView shouldStartLoadWithRequest: (NSURLRequest*)request navigationType: (UIWebViewNavigationType)navigationType {
if ( navigationType == UIWebViewNavigationTypeLinkClicked ) {
[myViewController termTextClickedOn:self didSelectTerm:request];
return NO;
}
return YES;
}
Any tips on how to make the dictionaryHashMap visible to CustomUIView would be much appreciated.
I have tried making the dictionaryHashMap global by doing the following:
Changing all instances of 'self.dictionaryHashMap' to 'dictionaryHashMap'
Adding the line 'extern NSMutableDictionary *dictionaryHashMap;' to CustomUIView.h
Adding the following outside of my implementation in myViewController.m: 'NSMutableDictionary *dictionaryHashMap = nil;'
However, the dictionaryHashMap remains invisible to CustomUIView. As far as I can tell, it actually remains a variable which is local to myViewController...
It's not resource-intensive to pass around the reference (pointer) to dictionaryHashMap. A pointer to an object is only 4 bytes. You could just pass it from your view controller to your view.
But I don't know why you even need to do that. Your view is sending a message (termTextClickedOn:didSelectTerm:) to the view controller when a term is clicked. And the view controller already has a reference to the dictionary, so it can handle the lookup. Why does the view also need a reference to the dictionary?
Anyway, if you want to make the dictionary a global, it would be more appropriate to initialize it in your app delegate, in application:didFinishLaunchingWithOptions:. You could even make the dictionary be a property of your app delegate and initialize it lazily.
UPDATE
I didn't notice until your comment that termTextClickedOn:didSelectTerm: is a class method. I assumed it was an instance method because myViewController starts with a lower-case letter, and the convention in iOS programming is that classes start with capital letters. (You make it easier to get good help when you follow the conventions!)
Here's what I'd recommend. First, rename myViewController to MyViewController (or better, DefinitionViewController).
Give it a property that references the dictionary. Whatever code creates a new instance of MyViewController is responsible for setting this property.
Give CustomUIView properties for a target and an action:
#property (nonatomic, weak) id target;
#property (nonatomic) SEL action;
Set those properties when you create the view:
- (void)displayDefinition:(NSString *)term {
NSArray* definition = [self.dictionaryHashMap objectForKey:term];
CustomUIView* definitionPopup = [[[CustomUIView alloc] init] autorelease]; // initialise a custom popup
definitionPopup.target = self;
definitionPopup.action = #selector(termWasClicked:);
...
In the view's webView:shouldStartLoadWithRequest: method, extract the term from the URL request and send it to the target/action:
- (BOOL)webView: (UIWebView*)webView shouldStartLoadWithRequest: (NSURLRequest*)request navigationType: (UIWebViewNavigationType)navigationType {
if ( navigationType == UIWebViewNavigationTypeLinkClicked ) {
NSString *term = termForURLRequest(request);
[self.target performSelector:self.action withObject:term];
return NO;
}
return YES;
}
In the view controller's termWasClicked: method, create the new view controller and set its dictionary property:
- (void)termWasClicked:(NSString *)term {
MyViewController *t = [[MyViewController alloc] init];
t.dictionary = self.dictionary;
[t displayDefinition:term];
}
Create a class that will be used as singleton. Example.
You Should always keep your data in separate class as the mvc pattern suggest and that could be achieved by using a singleton class for all your dictionary terms and accesing them from every custom view when needed.

How to pass in a a parameter on a button click in Objective C?

So I am trying pass in parameters to my "buttonClicked" function so that I can dynamically control what happens when you click the button. Every way that I try to do it on my own just breaks my code. I have searched many different sites and answers on stackOverflow, but I can't seem to find an answer. I am fairly new to objective C, especially with functions, So I could really use some help figuring out how to do this. Thanks in advance
Here is my code thus far and what I am trying to do:
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
NSLog(#"Hi 1!");
[button addTarget:self action:#selector(buttonClicked:buttonType:buttonID:) forControlEvents:UIControlEventTouchUpInside];
button.frame = CGRectMake(buttonViewXvalue, buttonViewYvalue, buttonViewWidth, buttonViewLength);
[self.view addSubview:button];
Then the Declaration in the Header File:
- (IBAction)buttonClicked:(id)sender theButtonType: (int)buttonType: theButtonID: (int) buttonID;
and the implementation:
- (IBAction)buttonClicked:(id)sender theButtonType: (int)buttonType: theButtonID: (int) buttonID
{
//Here I would use the buttonType and buttonID to create a new view.
NSLog(#"Hi!");
}
You can't use multi-parameter methods with addTarget:action:forControlEvents:. Instead you might set the button's tag, then look up information later based on the tag.
The action you add to UIButton (or any UIControl for that matter) must have a signature like (void)actionName or (void)actionName:(id)sender; as defined by target-action design pattern.
That gives you two simple solutions. One is that each of your buttons calls different (void)actionName-like method, which then calls a more complex method on self and passes the required parameters. Other way is to give each of your buttons a tag property and have them call (void)actionName:(id)sender-like method (they can all call the same one) and then you call the right method with right parameters depending on this tag:
- (void)actionName:(UIButton)sender;
{
if (sender.tag == 1) {
[self firstMethodWithString:someString andNumber:someNumber];
} else if (sender.tag == 2) {
[self secondMethodWithArray:someArray dictionary:someDictionary andColor:someColor];
} // and so on
}
Notice how I changed sender from id to UIButton here. This enables you to call tag without casting and not get a compiler warning, because compiler know you only expect UIButton instances to call this method.
If you really wanted to you could create a separate callback for each button, like:
// In your Whatever.h file
- (IBOutlet)actionButton1:(UIButton *)sender;
- (IBOutlet)actionButton2:(UIButton *)sender;
// In your Whatever.m file
- (IBOutlet)actionButton1:(UIButton *)sender
{
// do button 1 specific stuff
}
- (IBOutlet)actionButton2:(UIButton *)sender
{
// do button 2 specific stuff
}
// etc you get the idea
Then from Interface Builder look at your Whatever.xib file. You can link the "Sent Event" (drag from right side column) of "Touch Up Inside" to any of those actions above which will pop up in "File's Owner" (left column, when you release drag). You can do a different one for each button.
I should mention that generally if these are variants of the same functionality it makes more sense to use the tag property of UIButton.
I have solved this problem with an array of objects: all the parameters are stored in one object, then the object is inserted in the array, finally, the index of the object in the array is passed in the TAG property of the button. This technique works for one or many buttons. I did it for an app that had a list of contacts, where you were able to accept or reject them with buttons, and this buttons were calling the same action method, thanks to the TAG it was possible to know what parameters send to database. Steps:
1- Create a new class : New File, Objective-C class, name it, subclass of NSOBJECT, save it.
2- In the header of this new class declare one property for each parameter.
3- Now go to the IMPLEMENTATION file of the class of the viewcontroller where your button belongs to.
4- Import your new class :
#import "new_class.h"
5- Declare the array in the INTERFACE section :
#interface my_viewcontroller ()
{ NSMutableArray * my_array; }
6- In the VIEWDIDLOAD method create the array as empty :
my_array = [ [ NSMutableArray alloc ] initWithObjects : nil ];
7- In the method where you get the data for the parameters, declare an object of the new class, instantiate it and fill the properties :
new_class * nc;
nc = [ [ new_class alloc ] init ];
nc.param1 = #"abc";
nc.param2 = 123;
nc.param3 = true;
8- Now insert it in the array :
[ my_array addObject : nc ];
9- Store the value 0 in the TAG of the button. You will use this value as index to access the parameters in the my_array[ 0 ] position.
If there are more buttons, for example, from a web service that returns JSON data, just loop through the data creating more instances of the new class and inserting them in the array. Later, for example, in a tableview with dynamic cells and a template cell with buttons, in the method CELLFORROWATINDEXPATH, you will be able to store the INDEXPATH value in the TAG of every button, so these buttons will access their own parameters.

How do I pass a value to a method in Objective C

I'm new to Obj-C and I have run in to a very, very basic mental block that's making me feel a little stupid. I want to pass a variable from one method to another in an purely objective-C way. So far I'm stuck and have resorted to writing it in C. In C it goes as follows;
//detect the change in the segmented switch so we can; change text
- (IBAction)switchChanged:(id)sender
{
NSLog(#"Switch change detected");
//grab the info from the sender
UISegmentedControl *selector = sender;
//grab the selected segment info from selector. This returns a 0 or a 1.
int selectorIndex = [selector selectedSegmentIndex];
changeText (selectorIndex);
}
int changeText (int selectorPosition)
{
NSLog(#"changeText received a value. Selector position is %d", selectorPosition);
//Idea is to receive a value from the segmented controler and change the screen text accordingly
return 0;
}
This works perfectly well, but I want to learn how to do this with objects and messages. How would I rewrite these two methods to do this?
Cheers
Rich
Actually, you will only need to rewrite one of them since - (IBAction)switchChanged:(id)sender is an objective c method.
once you have your class definition, you can rewrite the changeTextFunction as:
//detect the change in the segmented switch so we can; change text
- (IBAction)switchChanged:(id)sender
{
NSLog(#"Switch change detected");
//grab the info from the sender
UISegmentedControl *selector = sender;
//grab the selected segment info from selector. This returns a 0 or a 1.
int selectorIndex = [selector selectedSegmentIndex];
[self changeText:selectorIndex];
}
-(int) changeText:(int) selectorPosition
{
NSLog(#"changeText received a value. Selector position is %d", selectorPosition);
//Idea is to receive a value from the segmented controler and change the screen text
return 0;
}
Also note that you will should add to your header file:
-(int) changeText:(int) selectorPosition;
Also note that this is for adding the changeText method to the class that has the switchChanged method. Tip: use command+option+up to jump to the header file directly.