comparing 2 UIButtons - objective-c

I have an array full of buttons and when a use clicks one I would like to search the array for it.
Yes, I have given the buttons tags, but they are used for another purpose. So I am hoping that there is another way to check for equality.
I was hoping that I would be able to do something like button1.frame.origin == button2.frame.origin, but the compiler doesnt like that.

You can use the method bool CGRectEqualToRect(CGRect rect1, CGRect rect2). Just pass in both of the button's frames as parameters into this method and it'll return a bool stating whether they are equal.

UIButton (and UIView) inherits from NSObject so you should just be able to do isEqual.
if([button1 isEqual:button2])
{
// do whatever
}

you can compare memory addressed of those objects, same as isEqual method is using
NSArray *buttons=#[button1,button2,button3,button4];//your array of buttons
UIButton *b = (UIButton *)sender;//button to search
[buttons enumerateObjectsUsingBlock:^(UIButton * button, NSUInteger idx, BOOL *stop) {
if (button==b) {
//do your thing here...
*stop=TRUE;
}
}];

Related

NSTextField Autocomplete / Suggestions

Since some days I am trying to code an autocompletion for a NSTextField. The autocompletion should look that way, that when the user clicks on the NSTextfield a list should be displayed under the TextField which possibilities are available. After typing one letter or number the list should refresh with the possibilities.
The suggestions in this list should come from an NSMutableArrayor a NSMutableDictionary
This autocomplete / autosuggestion method should be for a MAC application.
Just adding to #AbcdEfg's answer, to make NSComboBox case-insensitive, you can subclass it and override it's [completedString:] method like this:
- (NSString *) completedString:(NSString *)string {
NSUInteger l = [string length];
if (!!l)
for (NSString *match in [self objectValues])
if ([[match commonPrefixWithString:string options:NSCaseInsensitiveSearch] length] == l)
return [match stringByReplacingCharactersInRange:NSMakeRange(0, l) withString:string];
return nil;
}
You can use NSComboBox for that matter. You also need to set its Autocompletes attribute in IB or [comboBox setCompletes:YES] in code.
Keep in mind that it is case-sensitive.
However if you need it to be done in the exact way that you described, you need to make the list by subclassing NSWindowController and NSTableView and change them to look like a list or menu to show under you NSTextField. Set the NSTextField's delegate and do the search and list update on text changes.
Avoid the NSMenu in this case as it will take away the focus from the text field while you are typing.
Apple addressed it in WWDC 2010 Session 145.
They explained about a text field with suggestions menu and how to get it to work. They also provide the sample codes in their website.
You can find the sample code here.

NSPopupButton Bindings with Value Transformer

I don't know if what I see with a popup button populated by bindings with a value transformer is the way it's supposed to be or not -- the unusual thing I'm seeing (at least with respect to what I've seen with value transformers and table views) is that the "value" parameter in the transformedValue: method is the whole array bound to the array controller, not the individual strings in the array. When I've done this with table views, the transformer is called once for each displayed row in the table, and the "value" parameter is whatever object is bound to that row and column, not the whole array that serves as the content array for the array controller.
I have a very simple app to test this. In the app delegate there is this:
+(void)initialize {
RDTransformer *transformer = [[RDTransformer alloc] init];
[NSValueTransformer setValueTransformer:transformer forName:#"testTransformer"];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.theData = #[#{#"name":#"William", #"age":#"24"},#{#"name":#"Thomas", #"age":#"23"},#{#"name":#"Alexander", #"age":#"64"},#{#"name":#"James", #"age":#"47"}];
}
In the RDTransformer class is this:
+ (Class)transformedValueClass {
return [NSString class];
}
+(BOOL)allowsReverseTransformation {
return NO;
}
-(id)transformedValue:(id)value {
NSLog(#"%#",value);
return value;
}
In IB, I added an NSPopupButton to the window and an array controller to the objects list. The content array of the controller is bound to App Delegate.theData, and the Content Values of the popup button is bound to Array Controller.arrangedObjects.name with the value transformer, testTransformer.
When I run the program, the log from the transformedValue: method is this:
2012-09-19 20:31:39.975 PopupBindingWithTransformer[793:303] (
)
2012-09-19 20:31:40.019 PopupBindingWithTransformer[793:303] (
William,
Thomas,
Alexander,
James
)
This doesn't seem to be other people's experience from what I can see on SO. Is there something I'm doing wrong with either the bindings or the value transformer?
Unfortunately, this is how NSPopUpButton works. The problem is not limited to that control. If you try binding an NSArrayController.contentArray to another NSArrayControllers.arrangedObject.someProperty you will get the same problem. Here is a simple workaround that I use in all my value transformers, which makes them work with both tables and popups:
You can modify your value transformer in the following way:
-(id)transformedArrayValue:(NSArray*)array
{
NSMutableArray *result = [NSMutableArray array];
for (id value in array)
[result addObject:[self transformedValue:value]];
return result;
}
-(id)transformedValue:(id)value
{
if ([value isKindOfClass:[NSArray class]])
return [self transformedArrayValue:value];
// Do your normal-case transform...
return [value lowercaseString];
}
It's not perfect but it's easy to replicate. I actually put the transformedArrayValue: in a class category so that I don't need to copy it everywhere.

Adding a searchBar to your TableView

I'd like to add search functionality to a TableView in my app. I populate a table with an NSArray which has x amount of Objects that contain 3 NSStrings. Here's how I construct that NSArray:
First I create a class Code.h:
#import <Foundation/Foundation.h>
#interface Code : NSObject
#property (nonatomic, strong) NSString *codeName;
#property (nonatomic, strong) NSString *codeNumber;
#property (nonatomic, strong) NSString *codeDesc;
#end
Next, I synthesize these NSStrings in Code.m.
Now in my SearchViewController.m, Here's how I create my dataset:
NSMutableArray *codes;
codes = [[NSMutableArray alloc] init];
Code *c = [[Code alloc] init];
[c setCodeNumber:#"1"];
[c setCodeName:#"First Title Here"];
[c setCodeDesc:#"I might write a desc in here."];
[codes addObject:c];
c = [[Code alloc] init];
[c setCodeNumber:#"2"];
[c setCodeName:#"Second Title Here"];
[c setCodeDesc:#"2nd desc would be written here."];
[codes addObject:c];
and so on...
Here is how I display it: cellForRowAtIndexPath:
Code *c = [codes objectAtIndex:indexPath.row];
NSString *fused = [NSString stringWithFormat:#"%# - %#",[c codeNumber],[c codeName]];
cell.textLabel.text = fused;
return cell;
So now that you know how my data is structured and displayed, do you have an idea of how to search either the NSArray or possibly (preferably) the TableCells that have already been created?
I have been through the few tutorials online regarding Adding a Search Bar to a TableView, but all of them are written for using arrays setup using simple arrayWithObjects.
SIDETHOUGHT: Is it possible for me to construct an arrayWithObjects:#"aaa-1",#"bbb-2",#"ccc-3"... from my data? If i can manage that, I can use those tutorials to populate my cells and search them!
UPDATE:
Your second answer makes plenty more sense to me! Thanks for that. I beleive I have followed your instruction, but I am getting a "-[Code search:]: unrecognized selector sent to instance 0x6a2eb20` when that line is hit.
I added #property (nonatomic, strong) NSString *searchString; to Code.h and synthesized it in Code.m
I added NSMutableSet *searchResults; to SearchViewController.h's #interface
I added your methods performSearchWithString and matchFound to SearchViewController.m
Directly under those I added this to call performSearchWithString
x
- (void)searchBar:(UISearchBar *)theSearchBar textDidChange:(NSString *)searchString {
NSLog(#"%#",searchString); //Just making sure searchString is set
[self performSearchWithString:searchString];
[self.tableView reloadData];
}
The error hits when [codes makeObjectsPerformSelector:#selector(search:) withObject:self]; runs. I am confused b/c it sounds like Code doesn't recognize searchString, but I know I added it in Code.h.
UPDATE:
In order to store objects in searchResults, I had to change searchResults from a NSMutableSet to a NSMutableArray and modify - (void)matchFound:(Code *) matchingCode {} to this:
-(void) matchFound:(Code *) matchingCode {
Code *match = [[Code alloc] init];
if (searchResults.count == 0) {
searchResults = [[NSMutableArray alloc] init];
[match setCodeName:[matchingCode codeName]];
[match setCodeNumber:[matchingCode codeNumber]];
[match setCodeDesc:[matchingCode codeDesc]];
[searchResults addObject:match];
}
else
{
match = [[Code alloc] init];
[match setCodeName:[matchingCode codeName]];
[match setCodeNumber:[matchingCode codeNumber]];
[match setCodeDesc:[matchingCode codeDesc]];
[searchResults addObject:match];
}
With a few other tweeks, I've got a working searchbar for my tableView. Thanks Tim Kemp!
Oh, also case insensitive search was what I was looking for. NSRange rangeName = [codeName rangeOfString: searchString options:NSCaseInsensitiveSearch];
I hope this question and answer will be helpful to the next developer learning objective-c with this question!
Simpler approach
You asked for a simpler solution. This one isn't nearly as flexible, but it will achieve the same things as my earlier answer for this specific case.
Once again we are going to ask Code to search its strings for us. This time, we are going to skip the SearchRequest and the block callback and implement it directly.
In your SearchViewController you will create two methods. One to do the search, and one callback to process any results as they come back. You will also need a container to store matching Code objects (more than one might match, presumably.) You will also need to add a method to Code to tell it what the search string is.
Add an ivar NSMutableSet called searchResults to SearchViewController.
Add a property of type NSString * called searchString to Code
Add the search method to SearchViewController. This is what you'll call when you want to initiate a search across all your codes:
-(void) performSearchWithString:(NSString *) searchString {
// Tell each Code what string to search for
[codes makeObjectsPerformSelector:#selector(setSearchString:) withObject:searchString];
// Make each code perform the search
[codes makeObjectsPerformSelector:#selector(search:) withObject:self];
}
Then you will also need a callback in SearchViewController. This is so that your Code objects can tell the SearchViewController that they have found a match:
-(void) matchFound:(Code *) matchingCode {
[searchResults addObject:matchingCode];
// do something with the matching code. Add it to a different table
// view, or filter it or whatever you need it to do.
}
However do note that you don't have to use the searchResults mutable set; you may well want to just call another method to immediately add the returned result to some other list on screen. It depends on your app's needs.
In Code, add a search method a bit like we had before, but instead of the SearchRequest parameter we'll pass in a reference to the SearchViewController:
- (void) search:(SearchViewController *) searchVC {
// Search each string in turn
NSRange rangeNum = [codeNumber rangeOfString : searchString];
NSRange rangeName = [codeName rangeOfString : searchString];
NSRange rangeDesc = [codeDesc rangeOfString: searchString];
if (rangeNum.location != NSNotFound || rangeName.location != NSNotFound || rangeDesc.location != NSNotFound) {
[searchVC matchFound:self];
}
}
Do you see how that works? If there's a match in any of the strings (|| means 'or') then pass self (which means exactly what it sounds like: the current object that's running this code right now) back to a method in the view controller called searchVC. This is called a callback because we are "calling back" to the object which originally sent us the message to do the search. We have to use callbacks rather than simple return types because we have used makeObjectsPerformSelector to tell every single Code in the codes array to do a search. We never explicitly called the search method ourselves, so we have no way to capture the return value from each search. That's why its return type is void.
You can extend matchFound to take an additional parameter which identifies which string the match was in (i.e. çodeNumber, codeName or codeDesc.) Look into enums as one good approach to pass around that kind of data.
Hope that's bit simpler.
Here is a link to an excellent language introduction/tutorial which will eliminate much confusion.
EDIT In your last comment you said that searchResults was null. I said to add it as an ivar somewhere in SearchViewController. In your initialiser method for SearchViewController you should call
searchResults = [[NSMutableSet alloc] initWithCapacity:50]` // Choose some sensible number other than 50; enough to hold the likely number of matching Code objects.
Alternatively you could 'lazy initialise' it in matchFound:
- (void) matchFound:(Code *) matchingCode {
if (!searchResults)
searchResults = [[NSMutableSet alloc] initWithCapacity:50];
[searchResults addObject:matchingCode];
}
Though if you do this you should be aware that anywhere else you access searchResults may find that it's null if matchCode: has never previously been called.
Original, flexible and more complicated answer
I'm a little unclear as to what you're trying to do, so I'm going with your title, "Searching each string in each object of an array." In your case, your Codes have three strings and your array has multiple Codes. I assume that you need a way to tell the caller - the code that wants to do the search - which Code matches.
Here is one approach. There are easier ways but this technique is quite flexible. Broadly, we are going to make the Code object do the work of searching its own strings. We are then going to give the Code object the ability to tell the caller (i.e. the object that owns the codes array, presumably your table view controller) whether any of its strings match the search string. We will then use NSArray's method makeObjectsPerformSelector to have to tell all of its Code objects to search themselves. We will use a block for a callback.
Firstly, add a search method to Code (in the interface, or as a category depending on your design), something like this:
-(void) search:(SearchRequest *) request {
// Search using your favourite algorithm
// eg bool matches = [searchMe [request searchString]];
if (matches) {
[request foundMatch:self];
}
}
SearchRequest is new. It's a place to tie together a search string and a callback block. It looks something like this:
#interface SearchRequest
#property (retain) NSString * searchString;
#property (copy) void (^callback)(Code *);
- (id) initWithSearchString:(NSString *) search callback:(void (^)(Code *)) callback;
- (void) foundMatch:(Code *) matchingCode;
#end
#implementation SearchRequest
// synthesize...
// initialiser sets ivars
- (void) foundMatch:(Code *) matchingCode {
callback(matchingCode);
}
The callback block is our way of communicating back to the caller.
When you want to perform a search, construct a SeachRequest object with the string you're searching for and a block which contains the method to call when you get a match.
That would look like this, in the caller:
- (void) performASearchWithString:(NSString *) searchForMe {
SearchRequest * req = [[SearchRequest alloc] initWithSearchString:searchForMe
callback:^(Code * matchingCode) {
[self foundAHit:matchingCode];
}];
[codes makeObjectsPerformSelector:#selector(search:) withObject:req];
}
You then need to implement foundAHit in your caller, which takes the matching Code and does something with it. (You don't have to use a block: you could store a reference to the caller and a selector to call on it instead. I won't go into the arguments for either case here. Other answerers can propose alternatives.)

How to choose a method based on an element of an NSArray (Objective-C)

I'm writing a sort of calculator app. I have a UIPickerView (1 column) loading data from an NSArray of strings. The user will select one of these (it's selecting which type of calculator to use -- each uses a different method to calculate). The user inputs some things into some UITextFields and then presses a UIButton to do the calculations.
My NSArray is this:
calcNames = [NSArray alloc] initWithObjects:#"first", #"second", #"third", nil];
And my methods are called firstCalc(input1, input2, input3), secondCalc(input1, input2, input3), and so on. (The inputs are coming from the UITextFields.)
When I press the button, I would like to tell it to look at what the selection in the UIPickerView is and run the corresponding method without just typing an if-then statement for each one (it's very inconvenient to do this for reasons specific to my app, which are beyond the scope of this discussion).
So I have already defined a way to determine what the selected calc is:
selectedCalc = [[NSString alloc] initWithString:[calcNames objectAtIndex:row]]
where 'row' is the current selection in the UIPickerView.
Now I have a doCalculations method for when someone presses the UIButton:
-(IBAction)doCalculations:(id)sender {
// save the data input
double input1 = [input1Field.text doubleValue];
double input2 = [input2Field.text doubleValue];
double input3 = [input3Field.text doubleValue];
// do the calculations
int i;
for (i = 0; i < [calcNames count]; i++) {
if (selectedCalc == [calcNames objectAtIndex:i]) {
// do calculations here
double numResult = ??????
// if selectedCalc is "first", I want it to do firstCalc(input 1, input 2, input 3)
// if selectedCalc is "second", I want it to do secondCalc(input 1, input 2, input 3), and so on
// the rest is just for displaying the result
NSString* result = [NSString stringWithFormat:#"The answer is %f", numResult];
[resultLabel setText:result];
}
}
}
So basically, it runs a for loop until it finds which calculator is selected from the UIPickerView and when it finds it, runs the calculations and displays them.
I've been trying to understand if maybe function pointers or selectors (NSSelectorFromString?) are the right things to use here and how to use them, but I'm really struggling to understand where to go after a couple days of reading Apple's documentation, Stack Overflow questions, playing with sample code, and tinkering with my own code.
Sorry if the question is too lengthy, I thought it may be more helpful to others looking for assistance in the future to see the full idea. (At least I know sometimes I'm lost with these question pages.)
I would be very grateful for any assistance,
Ryan
You can dynamically invoke a method using a selector. You could for example have a secondary array to calcNames with selector called calcSelectors:
SEL calcSelectors[] = (SEL[3]){
#selector(first:arg:),
#selector(second:arg:),
#selector(third:arg:)};
Calling the right method would then be as simple as:
[self performSelector:calcSelectors[calcIndex] withObject:arg1 withObject:arg2];
If you need more then 2 arguments, then you also need to mess a bit with a NSInvocation instance to setup the call.
Example 1:
NSString *method=[calcNames objectAtIndex:0];//here play with objectatindex
SEL s=NSSelectorFromString(method);
[self performSelector:s];
which will call this method
-(void)first{
NSLog(#"first");
}
-----------------------------------------
Example 2:
NSString *totalMethodName;
totalMethodName=#"vijay";
totalMethodName=[totalMethodName stringByAppendingString:#"With"];
totalMethodName=[totalMethodName stringByAppendingString:#"Apple"];
SEL s=NSSelectorFromString(totalMethodName);
[self performSelector:s];
will call
-(void)vijayWithApple{
NSLog(#"vijayWithApple called");
}
You can make use of NSInvocation to dynamically bind multiple arguments to a selector. Follow this post to learn it.
If you are going to use NSInvocation you have to define your methods in the objective-C way something like the following.
- (double)firstCalcWithInput1:(double)input1 input2:(double)input2 andInput3:(double)input3;
- (double)secondCalcWithInput1:(double)input1 input2:(double)input2 andInput3:(double)input3;

Dynamic button selection

Suppose I have two buttons that are ivar outlets. One is called "Blue" and the other "Red." Now, I have an NSString with the value of "Red." I want to set the button identified by the NSString to a selected state, without using if.
I do not want to do this:
NSString *button=#"Red";
if ([button isEqualtoString:#"Blue")
self.blue.selected=YES; //self.blue and self.red are UIButtons
else
self.red.selected=YES;
This is fine if you have two buttons, but I have quite a lot more than that, and it would be quite inelegant and cumbersome to do this for like 30 buttons.
I'd much rather find a way to directly link the name of a UIButton ivar to the value of an NSString.
Put the string lower case and
you can use KVC for that :
[self setValue:[NSNumber numberWithBool:YES]
forKeyPath:[NSString stringWithFormat:#"%#.selected", button]];
You can put the buttons in a dictionary :
[NSDictionaryName addObject: UIButtonName forKey: #"blue"];
or something like that :
[NSDictionaryName objectForKey:#"blue"].selected = YES;