Disable line breaking after certain words - objective-c

I have few UILabels in my app. All of them are set to be multiline by setting numberOfLines as 0. By this, some of them have 1 line, some have 2. My problem is, that according to grammar of language of this app, certain words should never be at the end of an line.
For example, let have sentence: "John is collecting fossils and stamps".
This line will be long enought for line breaking to appear after word "and". According to grammar, this should not happen, so I want to break line before this word, so instead of this after line break:
John is collecting fossils and
stamps
I want to get:
John is collecting fossils
and stamps
Is it possible to achieve this? I am working with iOS 7 and higher, so need not to care with compatibility with older iOS versions.

Solution is to make a subclass of UILabel with 2 methods. Lets assume, that I only want to disable line breaking after word "a" (similar solution can be with multiple words):
- (void)setText:(NSString*)newText {
[super setText:[self fixSpaces:newText]];
}
- (NSString*)fixSpaces:(NSString*)originalText {
NSMutableString* tempString = [[NSMutableString alloc] initWithString:originalText];
[tempString replaceOccurrencesOfString:#"a " withString:#"a " options:NSLiteralSearch range:NSMakeRange(0, tempString.length)];
return tempString;
}
String that will be replaced is "a ", thats a standard 'a' and space. String that will replace it looks the same on the screen, but it is 'a' and non-breaking space (on MAC - Option + Space). Setting any text in a standard way of setting text to UILabel will always work as desired, but comparing string with text of label will not work (but it is easy to fix that by replacing non-breakable spaces with spaces for comparison). Setting text in storyboard or nib will naturally not work.

Related

NSTextField: integerValue behaving inconsistently

I have found that [NSTextField integerValue] behaves differently with values containing thousands separators depending on how the value was set.
(I am in Germany, so my thousands separator in these examples is a point ".").
If I call [myTextField setIntegerValue:4567], the text field will contain 4.567 (with tousands separator), and [myTextField integerValue] returns 4567.
If I type the value 4.567 info the text field manually, or use [myTextField setStringValue:#"4.567"], then [myTextField integerValue] returns just 4.
Apparently it stops parsing the number at the thousands separator, even though it inserts such a separator itself when calling setIntegerValue.
So I have actually two questions:
Is there a setting or other easy way that I can prevent it to format the number when using -setIntegerValue: ?
Can I "enable" the number parsing to understand/accept thousands separators when calling -integerValue? Or, if not, what would be the simplest way to parse a number with thousands separator from an NSString?
Add a Number Formatter (NSNumberFormatter) to the text field in the storyboard or XIB. This makes the contents of the cell and objectValue of the text field a NSNumber instead of a NSString.
Is there a setting or other easy way that I can prevent it to format the number when using -setIntegerValue: ?
Switch off Grouping Separator of the formatter.
Can I "enable" the number parsing to understand/accept thousands separators when calling -integerValue?
Switch on Grouping Separator of the formatter and set Primary Grouping to 3.
Or, if not, what would be the simplest way to parse a number with thousands separator from an NSString?
Use a NSNumberFormatter, see the class reference and Number Formatters.
integerValue is not locale-aware, it will always use . as the decimal point.
You should use NSNumberFormatter.numberFromString: instead.
Link to NSNumberFormatter class reference.

how to insert extra glyphs?

I want to an UITextView to switch between two display modes.
In mode 1 it should show abbreviations and in the full word in mode 2. For example "Abbr." vs "abbreviation".
What would be the best way to do this? Keeping in mind that some words can have the same abbreviation and that the user is free to type either the full word or the abbreviation?
So far I tried to subclass NSLayoutManager.
Assuming I get an abbreviated string and I have to draw the full word, I would implement the following method:
-(void)setGlyphs:(const CGGlyph *)glyphs
properties:(const NSGlyphProperty *)props
characterIndexes:(const NSUInteger *)charIndexes
font:(UIFont *)aFont
forGlyphRange:(NSRange)glyphRange
{
NSUInteger length = glyphRange.length;
NSString *sourceString = #"a very long string as a source of characters for substitution"; //temp.
unichar *characters = malloc(sizeof(unichar) * length+4);
CGGlyph *subGlyphs = malloc(sizeof(CGGlyph) * length+4);
[sourceString getCharacters:characters
range:NSMakeRange(0, length+4)];
CTFontGetGlyphsForCharacters((__bridge CTFontRef)(aFont),
characters,
subGlyphs,
length+4);
[super setGlyphs:subGlyphs
properties:props
characterIndexes:charIndexes
font:aFont
forGlyphRange:NSMakeRange(glyphRange.location, length+4)];
}
However this method complains about invalid glyph indices "_NSGlyphTreeInsertGlyphs invalid char index" when I try to insert 4 additional glyphs.
You're barking way up the wrong tree; trying to subclass NSLayoutManager in this situation is overkill. Your problem is merely one of swapping text stretches (replace abbrev by original or original by abbrev), so just do that - in the text, the underlying NSMutableAttributedString being displayed.
You say in a comment "some words map to the same abbreviation". No problem. Assuming you know the original word (the problem would not be solvable if you did not), store that original word as part of the NSMutableAttributedString, i.e. as an attribute in the place where the word is. Thus, when you substitute the abbreviation, the attribute remains, and thus the original word is retained, ready for you when you need to switch it back.
For example, given this string: #"I love New York" You can hide the word "New York" as an attribute in the same stretch of text occupied by "New York":
[attributedString addAttribute:#"realword" value:#"New York" range:NSMakeRange(7,8)];
Now you can set that range's text to #"NY" but the attribute remains, and you can consult it when the time comes to switch the text back to the unabbreviated form.
(I have drawn out this answer at some length because many people are unaware that you are allowed to define your own arbitrary NSAttributedString attributes. It's an incredibly useful thing to do.)

Building a CGPath around sentences in UITextView is incredibly slow at -positionFromPosition:

I'm doing some text analysis and have run into an annoying performance bump that I can't seem to find how to optimize. I start with the text from a UITextView and split the text into an array of sentences, splitting on characters in ".?!".
Then I loop over each sentence, splitting the sentence into an array of words, and pulling the first and last word from the sentence. With the NSRange of the sentence text in hand, I find the range of the first and last word in the UITextView's text.
The following part is where I get nailed with performance drains. This is how I find the bounding CGRect of the first and last word:
// the from range is increased each iteration
// so i'm not searching the entirety of the text each pass
NSRange range = [textView.text rangeOfString:firstWord options:kNilOptions range:fromRange];
UITextPosition *beginning = textView.beginningOfDocument;
UITextPosition *start = [textView positionFromPosition:beginning offset:range.location];
UITextPosition *end = [textView positionFromPosition:start offset:range.length];
UITextRange *textRange = [textView textRangeFromPosition:start toPosition:end];
firstRect = [textView firstRectForRange:textRange];
I perform this twice, once for the first word and once for the last word.
This works well on smaller text, but approaching 5+ paragraphs Instruments tells me that the UITextView -positionFromPosition: operation is eating up 492ms of clock time, locking up the UI and CPU at 100%.
The thing is I need the CGRect surrounding the first and last words so I can build a CGPath to highlight the sentence. The entire thing works and looks really great, but its the hang while the rects are found that is killing me. I'm fairly new to using UITextView's, so if there is something I can do, either optimizing my searches with ranges or somehow placing my operations on a background thread, I'd be much obliged.
You'll be better off using UITextView's attributedText property, which takes an NSAttributedString. With that, you can set the NSBackgroundColorAttributeName to a colour over a specific range.
Just note the attributed text methods only work in iOS 6+.

UITextChecker 25 Letter Words

I believe this is an Apple bug, but wanted to run it by you all and see if anyone else had run into the same/similar issues.
Simply, Apple's UITextChecker finds all words 25 letters or more as valid, spelled correctly words. Go ahead and open up Notes on your iOS device (or TextEdit on OS X) and type in a random 24 letter word. Hit enter, underlined red, right? Now add one more letter to that line so it is a 25 letter word. Hit enter again, underline red, right ... nope!
I don't know if this is related, but I have a similar unanswered question out there (UITextChecker is what dictionary?) questioning what dictionary is used for UITextChecker. In /usr/share/dict/words the longest word is 24 letters. Seems rather coincidental that 25 letters would be the first length of word that is not in the dictionary and it is always accepted as a valid word. But I don't know if that word list is the dictionary for UITextChecker.
This is important to note for anyone that might be confirming the spelling of a given word for something like a game. You really don't want players to able to use a random 25 letters to spell a word and most likely score massive points.
Here's my code to check for valid words:
- (BOOL) isValidWord:(NSString*)word {
// word is all lowercase
UITextChecker *checker = [[UITextChecker alloc] init];
NSRange searchRange = NSMakeRange(0, [word length]);
NSRange misspelledRange = [checker rangeOfMisspelledWordInString:word range:searchRange startingAt:0 wrap:NO language:#"en" ];
[checker release];
BOOL validWord = (misspelledRange.location == NSNotFound);
BOOL passOneCharTest = ([word length] > 1 || [word isEqualToString:#"a"] || [word isEqualToString:#"i"]);
BOOL passLengthTest = ([word length] > 0 && [word length] < 25); // I don't know any words more than 24 letters long
return validWord && passOneCharTest && passLengthTest;
}
So my question to the community, is this a documented 'feature' that I just haven't been able to locate?
This is likely to be caused by the algorithm used for spell-checking itself although I admit it sounds like a bit of a hole.
Even spell-checkers that use a dictionary often tend to use an algorithm to get rid of false negatives. The classic is to ignore:
(a) single-character words followed by certain punctuation (like that (a) back there); and
(b) words consisting of all uppercase like NATO or CHOGM, assuming that they're quite valid acronyms.
If the algorithm for UITextChecker also considers 25+-letter words to be okay, that's just one of the things you need to watch out for.
It may well be related to the expected use case. It may be expected to be used as not so much for a perfect checker, but more as a best-guess solution.
If you really want a perfect filter, you're probably better off doing your own, using a copy of the dictionary from somewhere. That way, you can exclude things that aren't valid in your game (acronyms in Scrabble®, for example).
You can also ensure you're not subject to the vagaries of algorithms that assume longer words are valid as appears to be the case here. Instead you could just assume any word not in your dictionary is invalid (but, of course, give the user the chance to add it if your dictionary is wrong).
Other than that, and filing a query/bug with Apple, there's probably not much else you can do.

appendAttributedString: in NSMutableAttributedString

I have an MSMutableAttributedString displayContent.
The attributes of the content vary across the string
i.e. the colours and font sizes can vary by letter.
I want to add a new character to the end of the string and for it to pick up the attributes of the last character in displayContent. I cannot know what those attributes are in advance since they are under user control.
When I append the new character (tempAttr):
NSAttributedString * tempAttr = [[NSAttributedString alloc] initWithString:appendage];
[displayContent appendAttributedString:tempAttr];
it appears to reset the attributes of the whole string to the attributes of the new character (which I haven't set since I can't know what they need to be).
How do I get tempAttr to pick up the attributes of the last character in displayContent?
Thanks.
Update.
Made progress on this in a clumsy but functional way.
Copy the attributes dictionary from the last character in the display (displayContent) and then reapply those attributes to the new character being added:
NSMutableDictionary * lastCharAttrs = [NSMutableDictionary dictionaryWithCapacity:5];
[lastCharAttrs addEntriesFromDictionary: [displayContent attributesAtIndex:0
effectiveRange:NULL]]; // get style of last letter
NSMutableAttributedString * tempAttr = [[NSMutableAttributedString alloc] initWithString:newCharacter
attributes:lastCharAttrs];
[displayContent appendAttributedString:tempAttr]; // Append to content in the display field
I would have hoped there was a more elegant way to do this like setting a property of the NSTextField.
I think I discovered a solution to this by accident, then found this page while looking for the answer to the problem I created for myself (the opposite of your issue).
If you do the following:
[[displayContent mutableString] appendString:newCharacter];
You'll end up with newCharacter appended and the previous attributes "stretched" to cover it. I cannot find this behavior documented anywhere, however, so you might be weary of counting on it.