UIAlertView showing twice - objective-c

I have an application that, in part, loops through the contents of an NSSet and displays a UIAlertView for each item found in the set. When there is only one item in the set, the UIAlertView behaves itself properly. However, if there's more than one, the first view flashes up (normally with the contents of the last item in the set) and then disappears without any user intervention. The first item in the NSSet will then display and wait for a response, before displaying the next item in the NSSet and so on.
It is the same experience as is being described in this unresolved question: IPHONE: UIAlertView called twice in a custom function/IBAction
Here's the code:
#import "CalcViewController.h"
#interface CalcViewController()
#property (nonatomic) int variablesCount;
#property (nonatomic, strong) NSMutableDictionary *variablesSet;
#end
#implementation CalcViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.variablesSet = [[NSMutableDictionary alloc] init];
}
- (IBAction)variablePressed:(UIButton *)sender
{
[[self calcModel] setVariableAsOperand:sender.titleLabel.text];
self.expressionDisplay.text = [[self calcModel] descriptionOfExpression:self.calcModel.expression];
}
- (IBAction)solveExpressionPressed:(UIButton *)sender {
self.variablesCount = 0;
[self.variablesSet removeAllObjects];
NSSet *variablesCurrentlyInExpression = [[NSSet alloc] initWithSet:[CalcModel variablesInExpression:self.calcModel.expression]];
self.variablesCount = [variablesCurrentlyInExpression count];
if (variablesCurrentlyInExpression){
for (NSString *item in variablesCurrentlyInExpression) {
UIAlertView *alertDialog;
alertDialog = [[UIAlertView alloc] initWithTitle:#"Enter value for variable"
message:item
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
alertDialog.alertViewStyle=UIAlertViewStylePlainTextInput;
UITextField * alertTextField = [alertDialog textFieldAtIndex:0];
alertTextField.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
[alertDialog show];
}
}
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (buttonIndex == 0){
if ([[alertView textFieldAtIndex:0] text]){
self.variablesSet[alertView.message] = [[alertView textFieldAtIndex:0] text];
}
}
if ([self.variablesSet count] == self.variablesCount){
NSLog(#"time to solve");
[[self calcDisplay] setText:[NSString stringWithFormat:#"%g", [CalcModel evaluateExpression:self.calcModel.expression usingVariableValues:self.variablesSet]]];
}
}
I've checked the IBActions behind the button that triggers the solveExpressionPressed method and that is the only one that exists. I've also placed some logging before the [alertDialog show]; line and it is only called twice when the variablesCurrentlyInExpression NSSet contains two values, yet the UIAlertView appears three times (flashing up once).
Finally, i've tried it without the following code:
UITextField * alertTextField = [alertDialog textFieldAtIndex:0];
alertTextField.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
and the problem still occurs, so I don't think it's that.
I've been stuck on this a while and haven't figured it out (hence the post!!), so any help would be greatly appreciated.
Thanks

Try showing the first UIAlertView and then showing the second after the first is dismissed.
What's happens is if an app or the OS calls [alert show] and a UIAlertView is already being displayed, the original alertView gets placed in a queue and the new one is presented. When the new one is dismissed, the original UIAlertView is re-shown.
Hope this helps

Easily fixed with a boolean flag that you set to YES when the first alert is shown. Then when the second match is found and the boolean is already YES because the alert is visible you won't show it. Then again you might want to know the exact amount of matches in the NSSet. In that case you keep track with a counter and show the alert after the match function has been done and the counter is not 0.
Avoid showing the alert inside the method of the button trigger. Instead split every function into different sets of methods. Not just for making your function work but maintainability of the code later.

To get this done, you'll need to keep some extra state in your class, like this...
#property (strong, nonatomic) NSMutableSet *promptVariables;
#property (strong, nonatomic) NSString *promptVariable;
#property (strong, nonatomic) NSMutableDictionary *promptResults;
You can probably get away with less by keeping some in your model as it is (or hiding a little in the alert view message as you cleverly do currently), but I'll use all new variables for clarity.
When you want to make several prompts, set up your state like this...
self.promptVariables = [[NSSet alloc] initWithSet:[CalcModel variablesInExpression:self.calcModel.expression]];
[self promptForVariables];
Define promptForVariables to bail if it has no work to do (promptVariables is empty) or remove one and do the alert for it.
- (void)promptForVariables {
if (![self.promptVariables count]) return;
self.promptResults = [NSMutableDictionary dictionary];
self.promptVariable = [self.promptVariables anyObject];
[self.promptVariables removeObject:self.promptVariable];
// do your alert here, I won't repeat your code
}
Then when the alert is done, process the result as you have and call promptForVariables again. The next time, since you've changed state, it has less work to do.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (buttonIndex == 0){
if ([[alertView textFieldAtIndex:0] text]){
[self.promptResults setValue:[[alertView textFieldAtIndex:0] text] forKey:self.promptVariable];
}
[self performSelector:#selector(promptForVariables) withObject:nil afterDelay:0.0];
}
}
When this is done, promptResults will contain variable names as keys and user inputs as values.

Related

Core Data / NSTextView outlets not updating after fetching (OS X)

I am new to this, so if I come off a tad "dense" I apologize.
I'm writing a core data application. On my .xib, I have several outlets including text fields, date pickers and "Text Views" using the NSTextContainers. These are set up correctly.
My application is a makeshift database of sorts. When a user saves a record, clears the fields, and immediately tries to reload the record, all of the fields populate except for the NSTextView. Now, if I close the application out completely, restart it, and then retrieve the record, the NSTextContainers (NSTextView) outlets operate correctly and show the correct data. Its only when you immediately try to load a record back that there is a problem and the outlets appear blank. I set up some NSLog statements to see if the data was being retrieved and lo and behold, the fetch is not getting the NSTextView object data. It's coming back blank.
Here are some code snippets. I would appreciate ANY help as this has been driving me crazy.
#property (weak) IBOutlet NSTextField *clientnameField;
#property (unsafe_unretained) IBOutlet NSTextView *clientaddressField;
#property (weak) IBOutlet NSTextField *clientphoneField;
#property (unsafe_unretained) IBOutlet NSTextView *clientnotesField;
- (IBAction)saveButton:(id)sender {
AppDelegate *appDelegate = (AppDelegate*) [[NSApplication sharedApplication]delegate];
NSEntityDescription *entity = [NSEntityDescription insertNewObjectForEntityForName:#"Team" inManagedObjectContext:appDelegate.managedObjectContext];
[entity setValue:self.clientnameField.stringValue forKey:#"clientname"];
[entity setValue:self.clientaddressField.string forKey:#"clientaddress"];
[entity setValue:self.clientphoneField.stringValue forKey:#"clientphone"];
[entity setValue:self.clientnotesField.string forKey:#"clientnotes"];
NSError *error;
BOOL isSaved = [appDelegate.managedObjectContext save:&error];
}
//Fetching the Data Back
- (void)alertDidSearch:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
if (returnCode == NSAlertSecondButtonReturn) {
AppDelegate *appDelegate = (AppDelegate*) [[NSApplication sharedApplication]delegate];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Team" inManagedObjectContext:appDelegate.managedObjectContext];
NSFetchRequest *fetchRqst = [[NSFetchRequest alloc] init];
[fetchRqst setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(clientname == %#)", self.clientnameField.stringValue];
[fetchRqst setPredicate:predicate];
NSMutableArray *array = [[appDelegate.managedObjectContext executeFetchRequest:fetchRqst error:nil]mutableCopy];
if ([array count] == 0) {
NSAlert *notFoundAlert;
notFoundAlert = [[NSAlert alloc] init];
[notFoundAlert addButtonWithTitle:#"OK"];
[notFoundAlert setMessageText:#"RECORD NOT FOUND!!"];
[notFoundAlert setInformativeText:#"The client was not found. Check the name and spelling and try again."];
[notFoundAlert setAlertStyle:NSWarningAlertStyle];
[notFoundAlert beginSheetModalForWindow:[self window] modalDelegate:self didEndSelector:#selector(alertDidEndOK:returnCode:contextInfo:) contextInfo:nil];
} else {
for (NSManagedObject *obj in array)
{
self.clientnameField.stringValue = [obj valueForKey:#"clientname"];
self.clientaddressField.string = [obj valueForKey:#"clientaddress"];
self.clientphoneField.stringValue = [obj valueForKey:#"clientphone"];
self.clientnotesField.string = [obj valueForKey:#"clientnotes"];
}
}
}
The client address field and client notes fields (outlets) are not being retrieved.
I'm not sure what I'm doing wrong. Again, the data IS being saved, because when I immediately retrieve the record, every field populates except the clientnotes and the clientaddress fields which are again, NSTextView outlets. Those do not show data until I exit out, restart the program and then retrieve the record. If I had to guess, and I am guessing, I assure you, it seems like the persistent store isn't being updated with respect to any .string(s). In fact, if I add multiple records and try to retrieve multiple records before exiting the program, they all exhibit the same issue. Closing the program, restarting it, and re-fetching the records and it's all fine.
Thank you so much in advance for the help. I am stumped. Again, I apologize if this is a stupid question... Please have mercy on me! And an apology for the long winded explanation, but I wanted to be as clear as possible.
EDIT:
It definitely appears as if the NSTextView data isn't being written until the file is closed out when the program terminates.
How can I fix this? My project is at a standstill...... :-(

ShareKit: Customizing text for different sharers (SHKActionSheet)

According to the official FAQ from ver.2 to customize your text/content depending on what sharer was selected by the user, you need:
subclass from SHKActionSheet and override
dismissWithClickedButtonIndex
set your new subclass name in
configurator (return it in (Class)SHKActionSheetSubclass;).
It doesn't work for me. But even more: I put
NSLog(#"%#", NSStringFromSelector(_cmd));
in (Class)SHKActionSheetSubclass to see if it's even got called. And it's NOT ;(( So ShareKit doesn't care about this config option...
Has anybody worked with this before?
thank you!
UPD1: I put some code here.
Here's how my subclass ITPShareKitActionSheet looks like. According to the docs I need to override dismissWithClickedButtonIndex:animated:, but to track if my class gets called I also override the actionSheetForItem::
+ (ITPShareKitActionSheet *)actionSheetForItem:(SHKItem *)item
{
NSLog(#"%#", NSStringFromSelector(_cmd));
ITPShareKitActionSheet *as = (ITPShareKitActionSheet *)[super actionSheetForItem:item];
return as;
}
- (void)dismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animate
{
NSLog(#"%#", NSStringFromSelector(_cmd));
NSString *sharersName = [self buttonTitleAtIndex:buttonIndex];
[self changeItemForService:sharersName];
[super dismissWithClickedButtonIndex:buttonIndex animated:animate];
}
And here's what I do in code to create an action sheet when user presses 'Share' button:
- (IBAction)shareButtonPressed:(id)sender
{
// Create the item to share
SHKItem *item = [SHKItem text:#"test share text"];
// Get the ShareKit action sheet
ITPShareKitActionSheet *actionSheet = [ITPShareKitActionSheet actionSheetForItem:item];
// Display the action sheet
[actionSheet showInView:self.view]; // showFromToolbar:self.navigationController.toolbar];
}
When I run this code, press 'Share' button and select any sharer I expect to get two lines in log:
actionSheetForItem: - custom action sheet got created
dismissWithClickedButtonIndex:animated: - custom mechanics to
process action sheet's pressed button got called.
But for some reason I get only the first line logged.
I was having the same issues but I've suddenly got it to call my Subclass successfully.
Firstly My Configurator is setup as so:
-(Class) SHKActionSheetSubclass{
return NSClassFromString(#"TBRSHKActionSheet");
}
Now My Subclass:
.h File
#import "SHKActionSheet.h"
#interface TBRSHKActionSheet : SHKActionSheet
#end
.m implementation override:
#import "TBRSHKActionSheet.h"
#import "SHKActionSheet.h"
#import "SHKShareMenu.h"
#import "SHK.h"
#import "SHKConfiguration.h"
#import "SHKSharer.h"
#import "SHKShareItemDelegate.h"
#implementation TBRSHKActionSheet
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
+ (SHKActionSheet *)actionSheetForItem:(SHKItem *)i
{
NSLog(#"%#", NSStringFromSelector(_cmd));
SHKActionSheet *as = [self actionSheetForType:i.shareType];
as.item = i;
return as;
}
- (void)dismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animated
{
NSInteger numberOfSharers = (NSInteger) [sharers count];
// Sharers
if (buttonIndex >= 0 && buttonIndex < numberOfSharers)
{
bool doShare = YES;
SHKSharer* sharer = [[NSClassFromString([sharers objectAtIndex:buttonIndex]) alloc] init];
[sharer loadItem:item];
if (shareDelegate != nil && [shareDelegate respondsToSelector:#selector(aboutToShareItem:withSharer:)])
{
doShare = [shareDelegate aboutToShareItem:item withSharer:sharer];
}
if(doShare)
[sharer share];
}
// More
else if ([SHKCONFIG(showActionSheetMoreButton) boolValue] && buttonIndex == numberOfSharers)
{
SHKShareMenu *shareMenu = [[SHKCONFIG(SHKShareMenuSubclass) alloc] initWithStyle:UITableViewStyleGrouped];
shareMenu.shareDelegate = shareDelegate;
shareMenu.item = item;
[[SHK currentHelper] showViewController:shareMenu];
}
[super dismissWithClickedButtonIndex:buttonIndex animated:animated];
}
Finally on my implementation file I've not modified the call to SHKActionSheet as Vilem has suggested because of some dependancies that seemed to cause conflicts for me.
So this is my caller (straight from tutorial):
NSURL *url = [NSURL URLWithString:#"http://getsharekit.com"];
SHKItem *item = [SHKItem URL:url title:#"ShareKit is Awesome!" contentType:SHKURLContentTypeWebpage];
// Get the ShareKit action sheet
SHKActionSheet *actionSheet = [SHKActionSheet actionSheetForItem:item];
// ShareKit detects top view controller (the one intended to present ShareKit UI) automatically,
// but sometimes it may not find one. To be safe, set it explicitly
[SHK setRootViewController:self];
// Display the action sheet
[actionSheet showFromToolbar:self.navigationController.toolbar];
This Calls no problems for me.
edit: by far the best way to achieve this is to use SHKShareItemDelegate. More info is in ShareKit's FAQ.

CLGeocoder reverseGeocodeLocation. First time in [placemarks count = 0]?

I am new to ObjC and I am struggling with the CLGeocoder. I want to be able to use reverseGeocodeLocation to obtain a string that contains the user location that I pass to my delegate when the user presses a Done button.
So the user triggers the display of a MapViewController, I call the reverseGeocodeLocation in the viewDidLoad but the [placemarks count = 0] this first time in, and I have no placemark to get the info that I need. The second time the user triggers the display of the MapViewController the placemarks array has been populated and everything works.
I suspect it is something to do with the reverseGeocodeLocation being an asynchronous call - but I cannot figure out how to solve this problem. I have tried searching online but nothing seems to help me understand what I am doing wrong and how i can solve this issue. Thanks in advance.
#interface MapViewController ()
#property (strong, nonatomic) CLGeocoder *geocoder;
#property (readwrite, nonatomic) NSString *theLocationName;
#end
#implementation MapViewController
#synthesize mapView, geocoder, delegate = _delegate, theLocationName = _theLocationName;
- (void)viewDidLoad
{
[super viewDidLoad];
self.mapView.delegate=self;
self.mapView.showsUserLocation = YES;
[self theUserLocation];
}
-(void)theUserLocation
{
if (!geocoder)
{
geocoder = [[CLGeocoder alloc] init];
}
MKUserLocation *theLocation;
theLocation = [self.mapView userLocation];
[geocoder reverseGeocodeLocation:theLocation.location
completionHandler:^(NSArray* placemarks, NSError* error)
{
if ([placemarks count] > 0)
{
CLPlacemark *placemark = [placemarks objectAtIndex:0];
[self setTheLocationName: placemark.locality];
}
}];
- (IBAction)done:(id)sender
{
[[self delegate] mapViewControllerDidFinish:self locationName:[self theLocationName]];
}
#end
This is not exact answer to your question but, if you can switch to other solution apart from CLGeocoder than following function can help you to get address from given latitude, longitude
#define kGeoCodingString #"http://maps.google.com/maps/geo?q=%f,%f&output=csv" //define this at top
-(NSString *)getAddressFromLatLon:(double)pdblLatitude withLongitude:(double)pdblLongitude
{
NSString *urlString = [NSString stringWithFormat:kGeoCodingString,pdblLatitude, pdblLongitude];
NSError* error;
NSString *locationString = [NSString stringWithContentsOfURL:[NSURL URLWithString:urlString] encoding:NSASCIIStringEncoding error:&error];
locationString = [locationString stringByReplacingOccurrencesOfString:#"\"" withString:#""];
return [locationString substringFromIndex:6];
}
Credit : Selected Answer to this question
So the user triggers the display of a MapViewController, I call the reverseGeocodeLocation in the viewDidLoad but the [placemarks count = 0] this first time in, and I have no placemark to get the info that I need. The second time the user triggers the display of the MapViewController the placemarks array has been populated and everything works.
It's not because the call is asynchronous - it's because the first time you call theUserLocation the actual location isn't available. Getting the user's location is not instantaneous - it takes time. However, you're asking for the user's location as soon as the map loads, which in most circumstances won't work.
What you need to do is hook into the MKMapViewDelegate methods, which provide you with callbacks when the location is updated. You can use this to check the location's accuracy, and decide whether it is accurate enough for you to reverse geolocate.

Objects string value is not set correctly

I'm writing a small project time management program for myself and have run into a problem which has confounded me.
They way it's set up is that I have an object called TCObject which I use in another object called ProjectTimerController. ProjectTimerController is a NSWindowController and it has it's own NIB file.
What I'm doing is pretty straight forward. When you click a line in a NSTableView ProjectTimerController finds a TCObject which corresponds to that line. It then loads info from that TCObject into an interface where you can view and edit some stuff.
Here's a screenshot of what it looks like:
Now when I change the text in NSTextView and then press the Add button the -saveString function is called and currentSelection (which is a TCObject and represents the currently selected line) and it's notes variable is set. I know that _notes is set as the new value as NSLog function logs the correct string being in _notes when setString is run. The same, correct, string is logged in -tableViewSelectionDidChange: just before currentSelection is set as the newly selected object.
But if I select the line where I just changed the notes it just loads the same text, "Initial String" and checking _notes tells me it's "Initial String".
Thing I don't have this problem with isFinished. When the Finished check box is toggled I set the corresponding TCObjects' isFinished Boolean value to the same value as the checkbox. This the object remembers and correctly changes depending on what line I have selected.
[EDIT]
*I've added a clearer explanation here.
I click a line in the NSTableView (lets say the top one)
This loads a corresponding TCObject from the myProjects array and that object's variable are added to the Notes NSTextView box and Finished is toggled on or off.
If I now write into The Notes box and press "Add" the text there is set into that TCObject's _notes variable.
So If I click another line some other text is loaded into the Notes box. Clicking back on the top line should give me the string I just wrote into Notes in step 3. But it doesn't. _notes always seems to contain the string I set when I initialize it in the -init method.
The "Finished" checkbox works fine. When I click that the state is saved and loaded correctly when I click a line.
I know that _notes is correctly set when I press the Add button as the NSLog method in setString logs the string I have written into Notes when I press the Add button.
[/EDIT]
Here below is a barebones version of TCObject and ProjectTimerController.
//TCObject.h
#interface TCObject : NSObject
{
NSString *_notes;
Boolean _isFinished;
}
#property (retain, nonatomic) NSString *notes;
#property (nonatomic) Boolean isFinished;
#end
//TCObject.m
#import "TCObject.h"
#implementation TCObject
#synthesize notes = _notes, isFinished = _isFinished;
-(id)init{
if (self = [super init]) {
self.notes = #"Initial string";
self.isFinished = NO;
}
return self;
}
- (void)dealloc {
[_notes release]; _notes = nil;
[super dealloc];
}
-(void)setNotes:(NSString *)notes {
[notes retain];
[_notes release];
_notes = notes;
NSLog(#"Setting _notes as: %#", _notes);
}
-(NSString *)notes {
NSLog(#"Getting _notes, which is: %#", _notes);
return _notes;
}
#end
//ProjectTimerController.m
- (id)initWithWindow:(NSWindow *)window {
self = [super initWithWindow:window];
if (self)
{
myProjects = [[NSMutableArray alloc]init];
currentSelection = nil;
TCObject *newProject = [[TCObject alloc] init];
TCObject *newProject2 = [[TCObject alloc] init];
TCObject *newProject3 = [[TCObject alloc] init];
TCObject *newProject4 = [[TCObject alloc] init];
[myProjects addObject:newProject];
[myProjects addObject:newProject2];
[myProjects addObject:newProject3];
[myProjects addObject:newProject4];
}
return self;
}
-(IBAction)isFinishedToggle:(id)sender {
if(currentSelection != nil){
currentSelection.isFinished = finishedCheckBtn.state;
}
}
-(IBAction)saveString:(id)sender {
if(currentSelection != nil){
currentSelection.notes = [[notesField textStorage] string];
}
}
//delegate function for NSTableView
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification {
NSInteger selectedIndex = [table selectedRow];
if(selectedIndex == -1){
return;
}
//here the correct notes string is printed
NSLog(#"curr: %i", currentSelection.notes);
currentSelection = [myProjects objectAtIndex:selectedIndex];
NSString *notesInfo = currentSelection.notes;
Boolean isFinishedInfo = currentSelection.isFinished;
[notesField setString:notesInfo];
[finishedCheckBtn setState:isFinishedInfo];
}
Finally found the problem. Seems that changing notes in this way:
-(IBAction)saveString:(id)sender {
if(currentSelection != nil){
currentSelection.notes = [[notesField textStorage] string];
}
}
causes some problems. Everything works fine if I do it this way:
-(IBAction)saveString:(id)sender{
if(currentSelection != nil){
NSString *temps = [NSString stringWithFormat:#"%#", [[notesField textStorage] string]];
currentSelection.notes = temps;
}
}
So I'm guessing what was going on is that _notes was pointing to the text contained in my NSTextView. So when I changed the text there _notes also changed or something like that...

Suppressing the text completion dropdown for an NSTextField

I'm trying to create the effect of an NSComboBox with completes == YES, no button, and numberOfVisibleItems == 0 (for an example, try filling in an Album or Artist in iTunes's Get Info window).
To accomplish this, I'm using an NSTextField control, which autocompletes on -controlTextDidChange: to call -[NSTextField complete:], which triggers the delegate method:
- (NSArray *)control:(NSControl *)control
textView:(NSTextView *)textView
completions:(NSArray *)words
forPartialWordRange:(NSRange)charRange
indexOfSelectedItem:(NSInteger *)index;
I've gotten this working correctly, the only problem being the side effect of a dropdown showing. I would like to suppress it, but I haven't seen a way to do this. I've scoured the documentation, Internet, and Stack Overflow, with no success.
I'd prefer a delegate method, but I'm open to subclassing, if that's the only way. I'm targeting Lion, in case it helps, so solutions don't need to be backward compatible.
To solve this, I had to think outside the box a little. Instead of using the built-in autocomplete mechanism, I built my own. This wasn't as tough as I had originally assumed it would be. My -controlTextDidChange: looks like so:
- (void)controlTextDidChange:(NSNotification *)note {
// Without using the isAutoCompleting flag, a loop would result, and the
// behavior gets unpredictable
if (!isAutoCompleting) {
isAutoCompleting = YES;
// Don't complete on a delete
if (userDeleted) {
userDeleted = NO;
} else {
NSTextField *control = [note object];
NSString *fieldName = [self fieldNameForTag:[control tag]];
NSTextView *textView = [[note userInfo] objectForKey:#"NSFieldEditor"];
NSString *typedText = [[textView.string copy] autorelease];
NSArray *completions = [self comboBoxValuesForField:fieldName
andPrefix:typedText];
if (completions.count >= 1) {
NSString *completion = [completions objectAtIndex:0];
NSRange difference = NSMakeRange(
typedText.length,
completion.length - typedText.length);
textView.string = completion;
[textView setSelectedRange:difference
affinity:NSSelectionAffinityUpstream
stillSelecting:NO];
}
}
isAutoCompleting = NO;
}
}
And then I implemented another delegate method I wasn't previously aware of (the missing piece of the puzzle, so to speak).
- (BOOL)control:(NSControl *)control
textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector {
// Detect if the user deleted text
if (commandSelector == #selector(deleteBackward:)
|| commandSelector == #selector(deleteForward:)) {
userDeleted = YES;
}
return NO;
}
Update: Simplified and corrected solution
It now doesn't track the last string the user entered, instead detecting when the user deleted. This solves the problem in a direct, rather than roundabout, manner.