Non US characters in section headers for a UITableView - iphone

I have added a section list for a simple Core Data iPhone app.
I followed this so question to create it - How to use the first character as a section name but my list also contain items starting with characters outside A-Z, specially Å,Ä and Ö used here in Sweden.
The problem now is that when the table view shows the section list the three last characters are drawn wrong. See image below
alt text http://img.skitch.com/20100130-jkt6e55pgyjwptgix1q8mwt7md.jpg
It seems like my best option right now is to let those items be sorted under 'Z'
if ([letter isEqual:#"Å"] ||
[letter isEqual:#"Ä"] ||
[letter isEqual:#"Ö"])
letter = #"Z";
Someone that have figured this one out?
And while I'm at it... 'Å', 'Ä' and 'Ö' should be sorted in that order but are sorted as 'Ä', 'Å' and 'Ö' by Core Data NSSortDescriptor. I have tried to set set the selector to localizedCaseInsensitiveCompare: but that gives a out of order section name 'Ä. Objects must be sorted by section name' error. Seen that too?

So I could not let go of this one and found the following:
What you need to do in this case is called 'Unicode Normalization Form D'. It is more explained in http://unicode.org/reports/tr15/ (warning, long and dry document)
Here is a function that does the decomposition and then filters out all diacritics. You can use this to convert Äpple to Apple and then use the first letter to build an index.
- (NSString*) decomposeAndFilterString: (NSString*) string
{
NSMutableString *decomposedString = [[string decomposedStringWithCanonicalMapping] mutableCopy];
NSCharacterSet *nonBaseSet = [NSCharacterSet nonBaseCharacterSet];
NSRange range = NSMakeRange([decomposedString length], 0);
while (range.location > 0) {
range = [decomposedString rangeOfCharacterFromSet:nonBaseSet
options:NSBackwardsSearch range:NSMakeRange(0, range.location)];
if (range.length == 0) {
break;
}
[decomposedString deleteCharactersInRange:range];
}
return [decomposedString autorelease];
}
(I found this code on a mailing list, forgot the source, but I took it and fixed it up a little)

I am experiencing the same issue reported in the original question, i.e. extended characters (outside Unicode page 0) not properly displayed in the index bar.
Although I seem to feed NSFetchedResultsController with correct single unicode character strings, what I get in return accessing the 'sectionIndexTitles' property are those same characters with the high byte set to 0; for example character \u30a2 (KATAKANA LETTER A) becomes \u00a2 (CENT SIGN).
I am not sure whether this is a bug in the method sectionIndexTitleForSectionName: of NSFetchedResultsController or my own fault somewhere else, however my workaround consists in overriding it and performing what the documentation says it does internally:
- (NSString *)sectionIndexTitleForSectionName:(NSString *)sectionName
{
NSString *outName;
if ( [sectionName length] )
outName = [[sectionName substringToIndex:1] uppercaseString];
else
outName = [super sectionIndexTitleForSectionName:sectionName];
return outName;
}
This produces the expected output.

Just adding this in my controller solved the original problem for me
- (NSString *)controller:(NSFetchedResultsController *)controller sectionIndexTitleForSectionName:(NSString *)sectionName {
return sectionName;}
Å, Ä and Ö displays properly in the list

When I encounter situation like this, the first thing I ask myself is 'what does Apple do'.
As an experiment I just added 'Joe Äpple' to my iPhone address book and he shows up under the plain A. I think that makes a lot of sense.
So instead of throwing them under Z or Ä you should do the same. There must be some way to get the 'base' letter of a unicode character for the grouping.

You can use UTF-8 encoding
const char *cLetter = (const char *)[ tmp cStringUsingEncoding:NSUTF8StringEncoding];
NSString *original = [NSString stringWithCString:cLetter encoding:NSUTF8StringEncoding];

Did you follow my answer to that other question?
I am doing some tests (refetching my objects) and seeing odd intermittent behavior. It definitely sorts them correctly (with 'Å' right after 'A', but before 'Ä') sometimes. Other times it puts the characters outside of A-Z all at the end (after 'Z') even though I didn't do anything special.
Also, I noticed that 'Å' is drawn correctly if you return its lowercase form. Have you tried overriding sectionIndexTitleForSectionName: to keep the order, but change the drawn character?

Did you ever solve this issue?
I've been able to get the section title index to display correctly by implementing sectionIndexTitlesForTableView: to build my own array of section titles:
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
NSMutableArray *indexKeys = [NSMutableArray arrayWithCapacity:30];
NSArray *fetchedResults = [fetchedResultsController fetchedObjects];
NSString *currKey = #"DEFAULT";
for (NSManagedObject *managedObject in fetchedResults) {
NSString *indexKey = [managedObject valueForKey:#"indexKey"];
if (![indexKey isEqualToString:currKey]) {
[indexKeys addObject:indexKey];
currKey = indexKey;
}
}
return indexKeys;
}
Here, indexKey is the first letter of the name.
However, this creates one of two issues in sectionForSectionIndexTitle: instead:
If I simply return the index for the section this is now the unsorted index and no longer corresponds with the sort order in the fetchResultController:
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return index;
}
Alternatively, If I pass on the call to the fetchedResultsController it breaks on the non-US index titles because these are no longer the weird characters used by the fetchedResultsController:
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return [fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}
The latter code generates an error of the following kind when navigating to the "Ø" index title:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Index title at 24 is not equal to 'Ø''
A workaround for this is to translate the offending characters back to their weird selves:
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
if ([title isEqualToString:#"Æ"]) {
title = #"\u2206";
} else if ([title isEqualToString:#"Ø"]) {
title = #"\u0178";
} else if ([title isEqualToString:#"Å"]) {
title = #"\u2248";
}
return [fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}
You can find the Unicode values in the debugger with the action "Print Description to Console".
However, the good solution would be to figure out why this weird encoding happens and prevent it.

Related

IOS isEqualToString Not Working

The following example program outputs the same, but the program does not work correctly.
NSDirectoryEnumerator *directoryEnumerator = [[NSFileManager defaultManager]
enumeratorAtPath:kDocdir];
for (NSString *pathi in directoryEnumerator)
{
NSString *fileName_Manager = [pathi lastPathComponent];
NSLog(#"fileName_Manager = %#",fileName_Manager);
Artist *name_Databse = [self.fetchedResultsController
objectAtIndexPath:IndexPath];
NSLog(#"name_Databse = %#",name_Databse.name);
if ([fileName_Manager isEqualToString:name_Databse.name]) {
NSLog(#"Same Name");
}else{
NSLog(#"Different Name");
}
}
Outputs:
2013-04-25 15:37:43.256 Player[36436:907] fileName_Manager = alizée - mèxico - final j'en
2013-04-25 15:37:43.272 Player[36436:907] name_Databse = alizée - mèxico - final j'en
2013-04-25 15:37:44.107 Player[36436:907] Different Name
does not work correctly when special characters in names. Why is this happening?
Thanks ...
have the same problem here:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name == %#",[pathi lastPathComponent]];
How do I make an edit here?
The documentation for isEqualToString: suggests you might have a problem:
The comparison uses the canonical representation of strings, which for
a particular string is the length of the string plus the Unicode
characters that make up the string. When this method compares two
strings, if the individual Unicodes are the same, then the strings are
equal, regardless of the backing store. “Literal” when applied to
string comparison means that various Unicode decomposition rules are
not applied and Unicode characters are individually compared. So,
for instance, “Ö” represented as the composed character sequence “O”
and umlaut would not compare equal to “Ö” represented as one Unicode
character.
Try using (NSOrderedSame == [string1 localizedCompare:string2])
Also, if you haven't already, look into the Apple sample code 'International Mountains' which deals with numerous localization issues.
Have you tried converting both strings to UTF-8 and then do the comparison? I don't know if that works, it's just an idea.

combine UITextFields input into one UITtextField

Im new to xcode and objective c. I have asked this question three times and still can't find a good method or answer. I have several uitextfields that accept user input and adds it to the combinedtextField in order of IBAction used.ie user inputs big in one field,bad in the next one and boy in the third and the result is big bad boy in the combinedtextField.
-(IBAction)addtextField1: (id)sender
{
combinedtextField.text = [NSMutableString stringWithFormat:#"%# %#",
combinedtextField.text,textField1.text];
}
-(IBAction)addtextField2: (id)sender
{
combinedtextField.text = [NSMutableString stringWithFormat:#"%# %#",
combinedtextField.text, textField2.text];
}
-(IBAction)addtextField3:(id)sender
{
combinedtextField.text = [NSMutableString stringWithFormat:#"%# %#",
combinedtextField.text,textField3.text];
}
Now this is where it gets interesting.I need to be able to remove the selected text from the combinedtextField.
-(IBAction)removetextField1:(id)sender
{
//////////????????????////////////////
}
-(IBAction)removetextField2: (id)sender
{
//////////????????????////////////////
}
-(IBAction)removetextField3: (id)sender
{
//////////????????????////////////////
}
If I tap the removetextField2 button it would remove the corresponding text(bad) from the combinedtextField and then it would read (big boy)
Ive looked into nsarrays,nsdictionarys and other methods and have got no where.
Need some way of possibly tagging the input and removing it that way. Example code would be great and very much appreciated.
You've left out some important details about your app, so I'm making some guesses about how it should work.
Perhaps what you should do is keep a mutable array of all of the fragments that have been added to the combined string:
#implementation MyViewController {
    NSMutableArray *fragments_;
}
- (void)viewDidLoad {
[super viewDidLoad];
fragments_ = [[NSMutableArray alloc] init];
}
When one of the add buttons is tapped, you append the corresponding field's text to the array and recompute the combined string:
- (IBAction)addTextField1:(id)sender {
[fragments_ addObject:textField1.text];
[self updateCombinedTextField];
}
- (void)updateCombinedTextField {
combinedTextField.text = [fragments componentsJoinedByString:#" "];
}
When one of the remove buttons is tapped, you try to remove the corresponding field's text from the fragments array and recompute the combined string:
- (IBAction)removeTextField1:(id)sender {
[fragments_ removeObject:textField1.text];
[self updateCombinedTextField];
}
That will remove all occurrences of field 1's text from the fragments array. If you just want to remove one instance, you will need to use indexOfObject: (or one of its variants) followed by removeObjectAtIndex:.
You can't do it in a very trivial way (without anything to add, just 1stroke magic function). But, there's the easy way, which i'd possible gone for.
If you don't need to do it a lot of times, and / or the text is quite small (not like a 500 pages book), then:
Create a boolean array named mark, and mark[i] should mark the i'th text field as 'added'. Then, create a function named reloadCombinedTextField, which creates it again, depended of the mark array. (if mark[i] == true, then we add a textfield's text, otherwise not)
Then, just mark or unmark the needed text fields in every function and call the reload function in the end of every call.
Altho, there is another way, but it can be wrong in situations where your text fields have the same text. The thing is, you just search in your combined text field the text from selected text field (for example, with [NSString rangeOfString] method) and remove it.
If you are going to do it often and the text is really big, then it goes much more complicated. But i'm quite sure, that you won't do this on iOS.

Set up a NSMutableArray of objects to match a string value

I'm using the value of two sliders as the first search criteria.
Now I want a second criteria. I have a
NSMutableArray *suitsArray;
which is containing up to 10 words, or it could be empty (0 words). Example of array:
2012-08-12 03:13:14.825 App[4595:f803] The content of array is(
mushroom,
grill,
pizza
)
What I want is this:
If suitsArray is like above, containing words mushroom, grill and pizza
and the "Suits" value in a dictionary is: "This wine suits mushroom dishes, grilled food, pizza and chicken."
This dictionary is added to searchResultsArray, same with all other dictionaries where "Suits" value containing words mushroom, grill and pizza.
But if suitsArray is empty of objects, skip this critera.
But I'm not sure how to write the code for it. Can you help me on it?
-(IBAction)searchButtonPressed:(id)sender{
resultObjectsArray = [NSMutableArray array];
for(NSDictionary *object in allObjectsArray)
{
NSString *objectPrice = [object objectForKey:#"75 cl price"];
NSString *suitsString = // the words in suitsArray
NSString *objectSuits = [object objectForKey:#"Suits"];
BOOL priceConditionGood = YES;
if (minPrisSlider.value <= maxPrisSlider.value && (winePrice.floatValue < minPrisSlider.value || winePrice.floatValue > maxPrisSlider.value))
priceConditionGood = NO;
if (priceConditionGood)
[resultObjectsArray addObject:object];
}
ResultsTableViewController *nextController = [[self storyboard] instantiateViewControllerWithIdentifier:#"ResultsController"];
nextController.objectsArray = [[NSMutableArray alloc]initWithArray:resultObjectsArray];
[self.navigationController pushViewController:nextController animated:YES];
}
It would be better to see if suitsArray contains the string objectSuits, rather than using rangeOfString.
if ([suitsArray containsObject:objectSuits]);
[resultObjectsArray addObject:object];
After edit: I think I understand now. This method looks at each word in suitsArray, and if it finds any of them in objectSuits, then object is added to the resultObjectArray.
for (NSString *aWord in suitsArray) {
if ([objectSuits rangeOfString:aWord].length == aWord.length) {
[resultObjectArray addObject:objectSuits];
break;
}
}
Second Edit: After further thought, the above code has a flaw in the logic that may or may not be important -- because it uses rangeOfString it will find words inside other words. So, if objectSuits was "This wine goes well with cheesecake" and suitsArray contained the word cheese, it would find a match (I can't imagine a wine that would be good with cheese would be good with cheesecake). So, here is a better solution, I think, that breaks objectSuit up into individual words and puts them into a set. suitsArray is also converted to a set, so that we can use intersectsSet: to find if there are any common words.
NSCharacterSet *sepSet = [NSCharacterSet characterSetWithCharactersInString:#" ,.;"];
NSArray *words = [objectSuits componentsSeparatedByCharactersInSet:sepSet];
NSSet *objectSuitsWords = [NSSet setWithArray:words];
NSSet *suitsSet = [NSSet setWithArray:suitsArray];
BOOL ans = [suitsSet intersectsSet:objectSuitsWords];
So, if ans is 1, then that object should be added to the results array. Notice that sepSet starts with a space and includes a comma, period, and semicolon. There might be other things you might want to include, but I think this should work in most cases.

iPhone objective-c: detecting a 'real' word

I need a (quick and dirty) solution to basically detect if a certain NSString is a 'real' word, that is, if it's in the dictionary. So basically, a very simplistic spell checker. Does anyone know of any way to do this? Basically I either need a file containing all words in the English dictionary (which I've searched for, but to no avail), or a way to interface with the iPhones spell checking service. Of course I would like to interface with the iPhones spell check service in a similar way to NSSpellChecker on OSX so my app will work with other languages, but at this point I'll take what I can get.
Lastly, here's some pseudo-code to better illustrate my needs:
-(BOOL)isDictionaryWord:(NSString*)word; //returns TRUE when word=#"greetings". returns FALSE when word=#"slkfjsdkl";
Use UITextChecker instead. The code below might not be perfect but should give you a good idea.
-(BOOL)isDictionaryWord:(NSString*)word {
UITextChecker *checker = [[UITextChecker alloc] init];
NSLocale *currentLocale = [NSLocale currentLocale];
NSString *currentLanguage = [currentLocale objectForKey:NSLocaleLanguageCode];
NSRange searchRange = NSMakeRange(0, [word length]);
NSRange misspelledRange = [checker rangeOfMisspelledWordInString:word range:searchRange startingAt:0 wrap:NO language:currentLanguage];
return misspelledRange.location == NSNotFound;
}
You can make UITextChecker work accurately without needing to add a new dictionary.
I use a two-step process because I need the first step to be fast (but not accurate). You may only need step two which is the accurate check. Note this makes use of the UITextChecker's completionsForPartialWordRange function which is why it's more accurate than the MisspelledWord function.
//Step one: I quickly check to see if a combination of letters passes the spell check. This is not that accurate but it's very fast so I can quickly exclude lots of letter combinations (brute force approach).
UITextChecker *checker;
NSString *wordToCheck = #"whatever"; // The combination of letters you wish to check
// Set the range to the length of the word
NSRange range = NSMakeRange(0, wordToCheck.length - 1);
NSRange misspelledRange = [checker rangeOfMisspelledWordInString:wordToCheck range: range startingAt:0 wrap:NO language: #"en_US"];
BOOL isRealWord = misspelledRange.location == NSNotFound;
// Call step two, to confirm that this is a real word
if (isRealWord) {
isRealWord = [self isRealWordOK:wordToCheck];
}
return isRealWord; // if true then we found a real word, if not move to next combination of letters
// Step Two: Extra check to make sure the word is really a real word. returns true if we have a real word.
-(BOOL)isRealWordOK:(NSString *)wordToCheck {
// we dont want to use any words that the lexicon has learned.
if ([UITextChecker hasLearnedWord:wordToCheck]) {
return NO;
}
// now we are going to use the word completion function to see if this word really exists, by removing the final letter and then asking auto complete to complete the word, then look through all the results and if its not found then its not a real word. Note the auto complete is very acurate unlike the spell checker.
NSRange range = NSMakeRange(0, wordToCheck.length - 1);
NSArray *guesses = [checker completionsForPartialWordRange:range inString:wordToCheck language:#"en_US"];
// confirm that the word is found in the auto-complete list
for (NSString *guess in guesses) {
if ([guess isEqualToString:wordToCheck]) {
// we found the word in the auto complete list so it's real :-)
return YES;
}
}
// if we get to here then it's not a real word :-(
NSLog(#"Word not found in second dictionary check:%#",wordToCheck);
return NO;
}

NSString stringWithFormat swizzled to allow missing format numbered args

Based on this SO question asked a few hours ago, I have decided to implement a swizzled method that will allow me to take a formatted NSString as the format arg into stringWithFormat, and have it not break when omitting one of the numbered arg references (%1$#, %2$#)
I have it working, but this is the first copy, and seeing as this method is going to be potentially called hundreds of thousands of times per app run, I need to bounce this off of some experts to see if this method has any red flags, major performance hits, or optimizations
#define NUMARGS(...) (sizeof((int[]){__VA_ARGS__})/sizeof(int))
#implementation NSString (UAFormatOmissions)
+ (id)uaStringWithFormat:(NSString *)format, ... {
if (format != nil) {
va_list args;
va_start(args, format);
// $# is an ordered variable (%1$#, %2$#...)
if ([format rangeOfString:#"$#"].location == NSNotFound) {
//call apples method
NSString *s = [[[NSString alloc] initWithFormat:format arguments:args] autorelease];
va_end(args);
return s;
}
NSMutableArray *newArgs = [NSMutableArray arrayWithCapacity:NUMARGS(args)];
id arg = nil;
int i = 1;
while (arg = va_arg(args, id)) {
NSString *f = [NSString stringWithFormat:#"%%%d\$\#", i];
i++;
if ([format rangeOfString:f].location == NSNotFound) continue;
else [newArgs addObject:arg];
}
va_end(args);
char *newArgList = (char *)malloc(sizeof(id) * [newArgs count]);
[newArgs getObjects:(id *)newArgList];
NSString* result = [[[NSString alloc] initWithFormat:format arguments:newArgList] autorelease];
free(newArgList);
return result;
}
return nil;
}
The basic algorithm is:
search the format string for the %1$#, %2$# variables by searching for %#
if not found, call the normal stringWithFormat and return
else, loop over the args
if the format has a position variable (%i$#) for position i, add the arg to the new arg array
else, don't add the arg
take the new arg array, convert it back into a va_list, and call initWithFormat:arguments: to get the correct string.
The idea is that I would run all [NSString stringWithFormat:] calls through this method instead.
This might seem unnecessary to many, but click on to the referenced SO question (first line) to see examples of why I need to do this.
Ideas? Thoughts? Better implementations? Better Solutions?
Whoa there!
Instead of screwing with a core method that you very probably will introduce subtle bugs into, instead just turn on "Static Analyzer" in your project options, and it will run every build - if you get the arguments wrong it will issue a compiler warning for you.
I appreciate your desire to make the application more robust but I think it very likely that re-writing this method will more likely break your application than save it.
How about defining your own interim method instead of using format specifiers and stringWithFormat:? For example, you could define your own method replaceIndexPoints: to look for ($1) instead of %1$#. You would then format your string and insert translated replacements independently. This method could also take an array of strings, with NSNull or empty strings at the indexes that don't exist in the “untranslated” string.
Your method could look like this (if it were a category method for NSMutableString):
- (void) replaceIndexPointsWithStrings:(NSArray *) replacements
{
// 1. look for largest index in "self".
// 2. loop from the beginning to the largest index, replacing each
// index with corresponding string from replacements array.
}
Here's a few issues that I see with your current implementation (at a glance):
The __VA_ARGS__ thingy explained in the comments.
When you use while (arg = va_arg(args, id)), you are assuming that the arguments are nil terminated (such as for arrayWithObjects:), but with stringWithFormat: this is not a requirement.
I don't think you're required to escape the $ and # in your string format in your arg-loop.
I'm not sure this would work well if uaStringWithFormat: was passed something larger than a pointer (i.e. long long if pointers are 32-bit). This may only be an issue if your translations also require inserting unlocalised numbers of long long magnitude.