Programmatically selecting a segment in UISegmentedControl based on title of that segment - objective-c

I have a UISegmentedControl with several segments, each with a different "title". I want to be able to read in a NSString, and programmatically select the segment whose title matches that string. Say I start with something like:
NSString *stringToMatch = #"foo";
UISegmentedControl *seg = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:#"foo",#"bar",#"baz", nil]];
I want to do something like:
[seg selectSegmentWithTitle:stringToMatch];
But since there is no method called selectSegmentWithTitle, this doesn't work. Is anyone aware of a method that would be similar to this though?
I've also thought of looping over all the titles in seg, similar to this:
int i = 0;
for (UISegment *thisSeg in [seg allSegmentsInOrder])
{
if ([thisSeg.title isEqualToString:stringToMatch])
{
[seg setSelectedSegmentIndex:i];
break;
}
i++;
}
but to my knowledge there is no such thing as UISegment, nor is there a method allSegmentsInOrder. Again, does anyone know of any changes I could make to get this to work?
Thirdly, I could probably subclass UISegmentedControl to somehow add the methods I want it to have. I hate subclassing like that though, cause I'd have to go and re-declare all my segments and other inconvenient things like that. But it may be the only way to go...
Perhaps the way to do this is totally different from the three ideas I listed above. I'm open to whatever.

So while I was typing this question up, I kept searching and realized that my second method from OP is pretty close. I figured I should still post what I came up with, in case someone else is looks for something like this in the future.
for (int i = 0; i < [seg numberOfSegments]; i++)
{
if ([[seg titleForSegmentAtIndex:i] isEqualToString:stringToMatch])
{
[seg setSelectedSegmentIndex:i];
break;
}
//else {Do Nothing - these are not the droi, err, segment we are looking for}
}
if ([seg selectedSegmentIndex] == -1)
{
NSLog(#"Error - segment with title %# not found in seg",stringToMatch);
NSLog(#"Go back and fix your code, you forgot something");
// prob should do other stuff here to let the user know something went wrong
}
This still feels a little hacky, and is probably against some best practice guide somewhere, but if there's a finite list of titles and you can be certain stringToMatch will always be on that list, I'm thinking it should be fine.

Related

Custom NSTextView insertText:replacementRange breaks Spell Checking

I have a custom NSTextView subclass, with a custom NSTextStorage component as well. The NSTextStorage modifies the text entered by the user based on context.
Because it's possible that the final text will be shorter than the text originally entered by the user, I had to override insertText:replacementRange in my NSTextView. A minimum example is:
- (void) insertText:(id)string replacementRange:(NSRange)replacementRange {
if ([self hasMarkedText]) {
[[self textStorage] replaceCharactersInRange:[self markedRange] withString:string];
} else {
[[self textStorage] replaceCharactersInRange:[self selectedRange] withString:string];
}
[self didChangeText];
}
This works fine in extensive testing over several months.... Except that automatic spell checking and correction is disabled. The "squigglies" don't appear under misspelled words, unless I stop typing, move the mouse, and switch focus to and from my app. After several seconds, the entire textview is spellcheck'ed. Because it happens after the fact, automatic correction is disabled of course.
If I disable my custom insertText:replacementRange: method, everything else works fine, and automatic spelling functionality returns. I just have to be careful not to trigger a change that results in shortening the text, as it triggers attribute out of range errors (the original reason for my custom method in the first place.)
Apparently Apple's implementation of insertText:replacementRange: does much more than mine. I have tried multiple variations on [self checkTextInRange...], [self checkTextInSelection:], etc. None of them restore proper functionality.
Searching Apple's documentation doesn't help point me towards what I am leaving out from my method that is causing spell checking to break. Any pointers or ideas would be much appreciated!!
Thanks in advance!
EDIT: Here are some examples of the sorts of behavior my NSTextStorage provides. (| represents the insertion caret)
Starting with:
* item
* |
If I hit the return key, I end up with the following (deleting *<space>):
* item
|
Another example, if "Change Tracking" is enabled:
this is thee| time
If I hit delete:
this is the|{--e--} time
As you can see, a single keystroke may result in the addition or deletion of multiple characters from the text.
EDIT 2: FYI -- the issue I have with attributes being out of range occur when the shortening happens while pressing return at the end of the document -- NSTextview attempts to set a new paragraph style only to find that the document is shorter than expected. I can find no way to change the range NSTextview targets.
I have a partial solution.
In my custom insertText:replacementRange: method, prior to didChangeText:
NSinteger wordCount;
NSOrthography * orthography;
static NSInteger theWordCount;
NSOrthography * orthography;
NSRange spellingRange = <range to check>
NSArray * results = [[NSSpellChecker sharedSpellChecker] checkString:[[self textStorage] string]
range:spellingRange
types:[self enabledTextCheckingTypes]
options:NULL
inSpellDocumentWithTag:0
orthography:&orthography
wordCount:&theWordCount];
if (results.count) {
[self handleTextCheckingResults:results forRange:spellingRange types:[self enabledTextCheckingTypes] options:#{} orthography:orthography wordCount:theWordCount];
}
However, this is incomplete:
Spell check and Grammar check works fine
Automatic spelling correction and text replacement do not work (even when enabled)
(EDITED 2018-05-30)
Updated response (2018-05-22):
This issue reared its ugly head again, and I really needed to figure it out.
My custom NSTextStorage is fundamentally the same as described, and still works.
I use a custom insertText:replacementRange: on my NSTextView, but it calls [super insertText:replacementRange:] to take advantage of Apple's behind-the-scenes work that makes spelling, etc. work better. My custom method only needs to set a boolean.
When shortening the text, I still get requests from Apple's insertText:replacementRange: for attributes in a non-existent part of the text. Previously, I would get stuck here, because everything I tried either caused a crash, or caused Apple's code to repeatedly request the non-existing attributes indefinitely.
Finally, I tried returning fake attributes with a NULL rangepointer, and this seems to make Apple's code happy:
- (NSDictionary *) attributesAtIndex:(NSUInteger)location effectiveRange:(nullable NSRangePointer)range {
if (location > _backingAttributes.length) {
// This happens if we shrink the text before the textview is aware of it.
// For example, if we expand "longtext" -> "short" in our smart string, then
// The textview may set and request attributes past the end of our
// _backing string.
// Initially this was due to error in my code, but now I had to add
// This error checking back
NSLog(#"get attributes at (%lu) in (%lu)", (unsigned long)location, (unsigned long)_backingAttributes.length);
NSLog(#"error");
// Apparently returning fake attributes satisfies [NSTextView insertText:replacementRange:]
range = NULL;
return #{
NSForegroundColorAttributeName : [BIColor redColor],
NSFontAttributeName : [BIFont fontWithName:#"Helvetica" size:14.0]
};
} else {
return [_backingAttributes attributesAtIndex:location effectiveRange:range];
}
}
With further testing, this turned out to not be quite enough. I ended up adding the following to the setter to store the invalid attributes and range that macOS was trying to set:
- (void) setAttributes:(NSDictionary<NSString *,id> *)attrs range:(NSRange)range {
if (NSMaxRange(range) > _backingAttributes.length) {
_invalidAttrs = attrs;
_invalidRange = range;
} else {
[self beginEditing];
[_backingAttributes setAttributes:attrs range:range];
[self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
[self endEditing];
}
}
I updated `attributesAtIndex:effectiveRange: to return the following when called with an invalid range, rather than returning the fake attributes above:
// Apparently returning fake attributes satisfies [NSTextView insertText]
*range = _invalidRange;
return _invalidAttrs;
This seems to work under various conditions that would previously trigger an exception or an infinite loop.

Objective-C, changing NSButton functionality?

How can I change the functionality of a button that I used previously? For example, If I had a button that did "Proceed/Cancel" and let's say you "Proceed" the button would change to something such as "View/Go Back"? Basically I want to re-use the same button for something else, but since I don't know how maybe someone can help me understand it better. Thank you.
- (IBAction)someButton:(NSButton *)sender {
if ([someString isEqualToString:someThing]) {
isAllowed = YES;
[oneButton setTitle:#"Proceed"];
[self continue];
}
else {
[oneButton setTitle:#"Cancel"];
return;
}
}
- (void)continue {
// I would like to make someButton (above) take on different functionality
// here if that's even possible. such as:
[oneButton setTitle:#"View"];
[self whatNow];
At some point in your program lifecycle you could replace the previous target and/or action of a NSButton by the desired one.
[oneButton setAction:#selector(continue)];
This will cause your continue selector to be called instead of the someButton: for the oneButton instance.
OBS: just pay attention at your selectors as the one from the NIB file has a parameter #selector(someButton:) and the one you are creating does not have any, so it stays as #selector(continue)
as seen here: Cocoa forControlEvents:WHATGOESHERE

Is it more efficient to schedule a method to spawn enemies or use the update method of an Enemy cache?

I am using Cocos2d for iPhone and I am wondering if it is more efficient to structure the logic of my code to spawn enemies using this method:
-(void) schedule:(SEL)selector interval:(ccTime)interval
or using the update in an EnemyCache class and verify each time if the time interval is met. Here is the code snippet that is called in the update method of the EnemyCache class (the relative time is an integer value that is updated by the GameScene at each update in the GameScene class - the GameScene update method call is scheduled with an interval of 1 second):
-(void) checkForPlayerCollisionsAndSpwanTime
{
int count = [elements count];
//CCLOG(#"count %i", count);
Element* element;
for(int i=0; i<count;i++){
element = [elements objectAtIndex:i];
NSAssert(element!=nil, #"Nil enemy");
if (element.visible)
{
[element justComeDown];
ShipEntity * ship = [[GameScene sharedGameScene]defaultShip];
CGRect rect = [ship boundingBox];
if (CGRectIntersectsRect([element boundingBox], rect)){
[element doWhatever];
element.visible=FALSE;
[element stopAllActions];
}
}
else{
if(element.spawnTime == relativeTime) {
[self addChild:element];
element.visible=TRUE;
}
}
}
}
The difference is that in this way at each update the checkForPlayerCollisionsAndSpwanTime method goes through the array of enemies. In the first way, via scheduling a selector to call a similar method, I could reduce the time spent by the CPU to look through the array and conditions.
I am not sure how costly is this call:
[self schedule:selector interval:interval repeat:kCCRepeatForever delay:0];
Looking through I see that calls this method (See below) but I wanted to ask in general what is your approach for this problem and whether I should keep using the EnemyCache update method or use the scheduleSelector methods.
-(void) scheduleSelector:(SEL)selector forTarget:(id)target interval:(ccTime)interval paused:(BOOL)paused repeat:(uint) repeat delay:(ccTime) delay
{
NSAssert( selector != nil, #"Argument selector must be non-nil");
NSAssert( target != nil, #"Argument target must be non-nil");
tHashSelectorEntry *element = NULL;
HASH_FIND_INT(hashForSelectors, &target, element);
if( ! element ) {
element = calloc( sizeof( *element ), 1 );
element->target = [target retain];
HASH_ADD_INT( hashForSelectors, target, element );
// Is this the 1st element ? Then set the pause level to all the selectors of this target
element->paused = paused;
} else
NSAssert( element->paused == paused, #"CCScheduler. Trying to schedule a selector with a pause value different than the target");
if( element->timers == nil )
element->timers = ccArrayNew(10);
else
{
for( unsigned int i=0; i< element->timers->num; i++ ) {
CCTimer *timer = element->timers->arr[i];
if( selector == timer->selector ) {
CCLOG(#"CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f", timer->interval, interval);
timer->interval = interval;
return;
}
}
ccArrayEnsureExtraCapacity(element->timers, 1);
}
CCTimer *timer = [[CCTimer alloc] initWithTarget:target selector:selector interval:interval repeat:repeat delay:delay];
ccArrayAppendObject(element->timers, timer);
[timer release];
}
Do you have a performance problem in your app? If not, the answer is: it doesn't matter. If you do, did you measure it and did the issue come from the method in question? If not, the answer is: you're looking in the wrong place.
In other words: premature optimization is the root of all evil.
If you still want to know, there's just one way to find out: measure both variants of the code and pick the one that's faster. If the speed difference is minimal (which I suspect it will be), favor the version that's easier for you to work with. There's a different kind of performance you should consider: you, as a human being, reading, understanding, changing code. Code readability and maintainability is way more important than performance in almost all situations.
No one can (or will) look at this amount of code and conclude "Yes, A is definitely about 30-40% faster, use A". If you are concerned about the speed of the method, don't let anyone tell you which is faster. Measure it. It's the only way you can be sure.
The reason is this: programmer's are notorious about making assumptions about code performance. Many times they're wrong, because the language or hardware or understanding of the topic have made big leaps the last time they measured it. But more likely they're going to remember what they've learned because once they've asked a question just like yours, and someone else gave them an answer which they accepted as fact from then on.
But coming back to your specific example: it really doesn't matter. You're much, much, much, much, much more likely to run into performance issues due to rendering too many enemies than the code that determines when to spawn one. And then it really, really, really, really, really doesn't matter if that code is run in a scheduled selector or a scheduled update method that increases a counter every frame. This boils down to being a subjective coding style preference issue a lot more than it is a decision about performance.

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;

Use an If statement with the button name in Objective-C (Cocoa/iPhone SDK)

I have to implement a small feature in an iPhone app and was wondering if there was a way to do use an if statement where the condition is the string of a button.
Here’s a sample of the code in question:
- (IBAction)someMethod:(id)sender{
UIButton *button = (UIButton *)sender;
if ( button.titleLabel.text == “SomeText” )
{
//do something
}
else
{
// some other thing
}
Now I can’t make it work, because I think I’m using the wrong code in button.titleLabel.text. I’ve even tried #“SomeText”), but I always end up in //some other thing.
What am I doing wrong?
Thanks.
What you're currently doing is comparing two pointers to objects, the objects button.titleLabel.text and #"SomeText". As both point to different places in the memory, the comparison will return NO.
If you want to compare the values of both NSString objects, however, you can use [button.titleLabel.text isEqualToString:#"SomeText"].
Also note that "SomeText" is not the same as #"SomeText"! The first is a regular C string, where the last one is a Cocoa NSString object.