Missing some Methods when change from NSString to NSAttributedString - ios

Hey everybody :) i have one method, which cuts a long text after a given number of words. It looks like this:
NSString *trimString(NSString *string, int length, Boolean soft) {
if(string == Nil || string.length == 0){
return string;
}
NSMutableString *sb = [[NSMutableString alloc]init];
int actualLength = length - 3;
if(string.length > actualLength){
// -3 because we add 3 dots at the end. Returned string length has to be length including the dots.
if(!soft) {
[sb appendString:[string substringToIndex:actualLength - 3]];
[sb appendString:#"..."];
return sb;
} else {
NSRange r = NSMakeRange(0, actualLength);
//NSRange range = [string rangeOfString:#" " options:NSBackwardsSearch range:r];
NSRange range = [string rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:#" \n\r\t"] options:NSBackwardsSearch range:r];
[sb appendString:[string substringToIndex:range.location]];
[sb appendString:#"..."];
return sb;
}
}
return string;
}
My Problem is that i have to change everything to NSAttributedString, but there are no things like "substringToIndex:" so that i can change it....
Is it even possible to change it?
Thanks!

You can do anything to an NSAttributedString that NSString supports by referencing the string property on the attributed string. This is the underlying NSString that you may do whatever you like with.

If you are just looking for string clipping (substringToIndex:) you can use:
- (NSAttributedString *)attributedSubstringFromRange:(NSRange)aRange
Create the proper range and that will give you the functionality you are "missing".
Source.

Assuming that you're working with an NSMutableAttributedString, you need to call your function on NSAttributedString's string property, and you can use the returned string as a parameter to replaceCharactersInRange:withString:
If I were you, I would change the prototype of the function to return an NSRange value, this way you could use it in a more general way with strings and AttributedStrings (with functions such as stringWithRange)

Related

Add attributes to all emoji in an NSAttributedString?

The font I'm using in my iOS app has an unfortunate quality: its characters are unusually small, and as a result, if a user types in a string which includes emoji (or possibly other characters not included in the font? Haven't tested that), when iOS draws those glyphs in the AppleColorEmoji font they come out huge relative to the other glyphs.
This is of course complicated by the fact that emoji are "two-part" glyphs so I can't just do a simple for-each-character-in-the-string loop.
What I need is a method along the lines of
-(NSAttributedString *)attributedStringByAddingAttributes:(NSArray *)attrs toString:(NSString*)myString forCharactersNotInFont:(UIFont *)font
... Or failing that, at least
-(NSAttributedString *)attributedStringByAddingAttributes:(NSArray *)attrs toStringForEmoji:(NSString*)myString
...or something.
Not sure of the best way to do this.
Code I ended up with, using code adapted from here:
- (BOOL)isEmoji:(NSString *)str {
const unichar high = [str characterAtIndex: 0];
// Surrogate pair (U+1D000-1F77F)
if (0xd800 <= high && high <= 0xdbff) {
const unichar low = [str characterAtIndex: 1];
const int codepoint = ((high - 0xd800) * 0x400) + (low - 0xdc00) + 0x10000;
return (0x1d000 <= codepoint && codepoint <= 0x1f77f);
// Not surrogate pair (U+2100-27BF)
} else {
return (0x2100 <= high && high <= 0x27bf);
}
}
// The following takes a string s and returns an attributed string where all the "special characters" contain the provided attributes
- (NSAttributedString *)attributedStringForString:(NSString *)s withAttributesForEmoji:(NSDictionary *)attrs {
NSMutableAttributedString *as = [[NSMutableAttributedString alloc] initWithString:#""];
NSRange fullRange = NSMakeRange(0, [s length]);
[s enumerateSubstringsInRange:fullRange
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange,
NSRange enclosingRange, BOOL *stop)
{
if ([self isEmoji:substring]) {
[as appendAttributedString:[[NSAttributedString alloc] initWithString:substring
attributes:attrs]];
} else {
[as appendAttributedString:[[NSAttributedString alloc] initWithString:substring]];
}
}];
return as;
}
So far, seems to work quite nicely!

Find substring range of NSString with unicode characters

If I have a string like this.
NSString *string = #"๐Ÿ˜€1๐Ÿ˜€3๐Ÿ˜€5๐Ÿ˜€7๐Ÿ˜€"
To get a substring like #"3๐Ÿ˜€5" you have to account for the fact the smiley face character take two bytes.
NSString *substring = [string substringWithRange:NSMakeRange(5, 4)];
Is there a way to get the same substring by using the actual character index so NSMakeRange(3, 3) in this case?
Thanks to #Joe's link I was able to create a solution that works.
This still seems like a lot of work for just trying to create a substring at unicode character ranges for an NSString. Please post if you have a simpler solution.
#implementation NSString (UTF)
- (NSString *)substringWithRangeOfComposedCharacterSequences:(NSRange)range
{
NSUInteger codeUnit = 0;
NSRange result;
NSUInteger start = range.location;
NSUInteger i = 0;
while(i <= start)
{
result = [self rangeOfComposedCharacterSequenceAtIndex:codeUnit];
codeUnit += result.length;
i++;
}
NSRange substringRange;
substringRange.location = result.location;
NSUInteger end = range.location + range.length;
while(i <= end)
{
result = [self rangeOfComposedCharacterSequenceAtIndex:codeUnit];
codeUnit += result.length;
i++;
}
substringRange.length = result.location - substringRange.location;
return [self substringWithRange:substringRange];
}
#end
Example:
NSString *string = #"๐Ÿ˜€1๐Ÿ˜€3๐Ÿ˜€5๐Ÿ˜€7๐Ÿ˜€";
NSString *result = [string substringWithRangeOfComposedCharacterSequences:NSMakeRange(3, 3)];
NSLog(#"%#", result); // 3๐Ÿ˜€5
Make a swift extension of NSString and use new swift String struct. Has a beautifull String.Index that uses glyphs for counting characters and range selecting. Very usefull is cases like yours with emojis envolved

Correcting the text cursor in a UITextField when replacing its text

I am implementing shortcut substitution for text typed into a UITextField.
For instance, if the text field already contains "a" and he types another "a" after it, I'd replace it with "รค". In another case, if he types "a", then "b", I'd replace it with "XYZ". And if the text contains two consecutive spaces, I like to replace them with a single space.
So, depending on what the user types, I might replace it with either a longer, a shorter, or a same-length text.
The simple way to do that is to implement the [UITextFieldDelegate textField: shouldChangeCharactersInRange: ... delegate function, assign the replacement text to textField.text, and return NO.
But this also requires adjusting the cursor position accordingly, and that's what I'm struggling with, a little.
I am handling this cursor positioning "by hand" currently. It's a bit ugly, and so I wonder if there is a more elegant solution. After all, all the code for handling the cursor position after replacing text (e.g. when selecting, then pasting) is already implemented in the UITextField code anyway. I just wonder if more of it is exposed for needs such as mine and I haven't found it yet.
I really think that you don't need textField:shouldChangeCharactersInRange:replacementString:. There is an easy way to solve your requirements and the solution don't have problems with the cursor.
You should add this line of code in your viewDidLoad (self.textField is your UITextField):
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(shortcut:) name:UITextFieldTextDidChangeNotification object:self.textField];
then, you should add the selector, e.g.:
- (void) shortcut: (NSNotification*) notification
{
UITextField *notificationTextField = [notification object];
if (notificationTextField == self.textField)
{
[self checkDoubleA:notificationTextField];
[self checkDoubleAB:notificationTextField];
[self checkDoubleSpace:notificationTextField];
}
}
Then you only need to add the 3 methods to check your shortcuts:
-(void) checkDoubleA: (UITextField*) textField
{
NSMutableString *string = [textField.text mutableCopy];
NSRange range = [string rangeOfString:#"aa"];
if (range.location == NSNotFound)
{
NSLog(#"string was not found");
}
else
{
[string replaceCharactersInRange:range withString:#"รค"];
}
textField.text = string;
}
-(void) checkDoubleAB: (UITextField*) textField
{
NSMutableString *string = [textField.text mutableCopy];
NSRange range = [string rangeOfString:#"ab"];
if (range.location == NSNotFound)
{
NSLog(#"string was not found");
}
else
{
[string replaceCharactersInRange:range withString:#"XYZ"];
}
textField.text = string;
}
- (void) checkDoubleSpace: (UITextField*) textField
{
NSMutableString *string = [textField.text mutableCopy];
NSRange range = [string rangeOfString:#" "];
if (range.location == NSNotFound)
{
NSLog(#"String was not found");
}
else
{
[string replaceCharactersInRange:range withString:#" "];
}
textField.text = string;
}
You can download a demo of this code here.

How to pad strings to a fixed width with NSMutableString?

I'm trying to write a string to a text file. That text file will then be read by another program. That second program is expecting the different "fields" in the text file to be a fixed width. Therefore, when I write the text file with my app, I will need to add spaces between my actual data to get everything to line up correctly. How do I get these spaces added?
So far, I've tried writing a function that takes a source string and a target length as input. If the target is longer than the source, it just appends " ". Code for this routine is below:
- (NSString *) makeStringFrom:(NSString *)source withLength:(NSInteger)length
{
// Method to add spaces to the end of a string to get it to a certain length
if ([source length] > length)
{
// String is too long - throw warning and send it back
NSLog(#"Warning - string is already longer than length supplied. Returning source string");
return source;
}
else if ([source length] == length)
{
// String is already correct length, so just send it back
return source;
}
else
{
// String is too short, need to add spaces
NSMutableString *newString = [[NSMutableString alloc] initWithString:source];
NSLog(#"newString initial length = %d",[newString length]);
for (int current = [source length]; current < length; current ++)
{
[newString stringByAppendingString:#" "];
NSLog(#"hit");
}
NSLog(#"target length = %d. newString length = %d",length,[newString length]);
return newString;
}
}
This apparently doesn't work. The length of the string I'm getting back in the return isn't changing any from the length of the supplied string, even when the NSLog(#"hit"); runs multiple times.
There's a stringByPaddingToLength:withString:startingAtIndex: method on NSString that does just this.
You did a silly mistake here
[newString stringByAppendingString:#" "];
This returns a new string, and it doesnot effect the caller object. You need to store it
newString=[newString stringByAppendingString:#" "];
or simply
[newString appendString:#" "];
You want to change:
[newString stringByAppendingString:#" "];
into:
newString = [newString stringByAppendingString:#" "];

Split NSString and Limit the response

I have a string Hello-World-Test, I want to split this string by the first dash only.
String 1:Hello
String 2:World-Test
What is the best way to do this? What I am doing right now is use componentsSeparatedByString, get the first object in the array and set it as String 1 then perform substring using the length of String 1 as the start index.
Thanks!
I added a category on NSString to split on the first occurrence of a given string. It may not be ideal to return the results in an array, but otherwise it seems fine. It just uses the NSString method rangeOfString:, which takes an NSString(B) and returns an NSRange showing where that string(B) is located.
#interface NSString (Split)
- (NSArray *)stringsBySplittingOnString:(NSString *)splitString;
#end
#implementation NSString (Split)
- (NSArray *)stringsBySplittingOnString:(NSString *)splitString
{
NSRange range = [self rangeOfString:splitString];
if (range.location == NSNotFound) {
return nil;
} else {
NSLog(#"%li",range.location);
NSLog(#"%li",range.length);
NSString *string1 = [self substringToIndex:range.location];
NSString *string2 = [self substringFromIndex:range.location+range.length];
NSLog(#"String1 = %#",string1);
NSLog(#"String2 = %#",string2);
return #[string1, string2];
}
}
#end
Use rangeOfString to find if split string exits and then use substringWithRange to create new string on bases of NSRange.
For Example :
NSString *strMain = #"Hello-World-Test";
NSRange match = [strMain rangeOfString:#"-"];
if(match.location != NSNotFound)
{
NSString *str1 = [strMain substringWithRange: NSMakeRange (0, match.location)];
NSLog(#"%#",str1);
NSString *str2 = [strMain substringWithRange: NSMakeRange (match.location+match.length,(strMain.length-match.location)-match.length)];
NSLog(#"%#",str2);
}

Resources