44

I have an NSArray declared as such:

@property (nonatomic, strong) NSArray *arrayRefineSubjectCode;

I have the array elements manually filled out as below:

     arrayRefineSubjectCode = [NSArray arrayWithObjects:
                              @"  BKKC 2061",
                              @"   BKKS 2631   ",
                              @"BKKS 2381      ",
                              nil];

So how do I remove starting and ending whitespace and make each array elements to become as these:

     arrayRefineSubjectCode = [NSArray arrayWithObjects:
                              @"BKKC 2061",
                              @"BKKS 2631",
                              @"BKKS 2381",
                              nil];

I have tried using "stringByTrimmingCharactersInSet:" but it only works for NSString. Kinda confused here. Please help...

shamsulfakhar
  • 563
  • 1
  • 4
  • 7
  • possible duplicate of http://stackoverflow.com/q/758212/1114171 – T I Feb 15 '12 at 10:29
  • 1
    If you can, it'd be much better to trim the whitespace before you put them in the array to start with. – Simon Withington Feb 15 '12 at 10:34
  • But that post trims whitespace from NSString @Tom Ingram. – shamsulfakhar Feb 15 '12 at 10:40
  • @shamsulfakhar not sure I get your point, as far as i understand it you can't do it from a NX(S)ConstantString, so you either have to clean them prior to assignment or reasign them to an NSString which provides ?static? methods for cleaning, or implement something similar to what NSString provides – T I Feb 15 '12 at 11:14
  • sorry, i'm quite new to this... – shamsulfakhar Feb 15 '12 at 11:57
  • I've decided to do a workaround by trimming the whitespaces BEFORE adding it into the array. Seems to fix the problem. :) – shamsulfakhar Feb 15 '12 at 15:06

6 Answers6

83

The NSArray and the contained NSString objects are all immutable. There's no way to change the objects you have.

Instead you have to create new strings and put them in a new array:

NSMutableArray *trimmedStrings = [NSMutableArray array];
for (NSString *string in arrayRefineSubjectCode) {
    NSString *trimmedString = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
    [trimmedStrings addObject:trimmedString];
}
arrayRefineSubjectCode = trimmedStrings;
Nikolai Ruhe
  • 81,520
  • 17
  • 180
  • 200
  • What if I declare my array as an NSMutableArray, can I change the array elements then? – shamsulfakhar Feb 15 '12 at 10:36
  • 2
    You could *exchange* them for other objects. You cannot modify (immutable) `NSString` objects. – Nikolai Ruhe Feb 15 '12 at 10:37
  • 2
    you'll be unable to modify the strings inside it, since they are immutable. You'll only be able to change the content of the array...not the content of the strings inside it –  Feb 15 '12 at 10:38
  • 2
    One gotcha: if you're expecting to also trim the newline character you need to use `[NSCharacterSet whitespaceAndNewlineCharacterSet]` – bcattle Mar 10 '16 at 08:01
  • Mutate no, replace yes. for (NSInteger i=0; i – Elise van Looij Jul 08 '18 at 14:58
  • @ElisevanLooij That does not work. `[NSArray objectAtIndex:]` does not produce an l-value. – Nikolai Ruhe Jul 08 '18 at 20:13
  • @NikolaiRuhe I did have an error in my code (should have used setObject:) but I don't understand your l-value comment. I looked it up, and an l-value appears to be a value that can be used elsewhere. The following test works fine though, or am I missing something? - (void)test_objectAtIndex { NSArray *arrayRefineSubjectCode = [NSArray arrayWithObjects: @" BKKC 2061", @" BKKS 2631 ", @"BKKS 2381 ", nil]; NSString *obj1 = [arrayRefineSubjectCode objectAtIndex:1]; XCTAssertTrue([obj1 isEqualToString:@" BKKS 2631 "]);} – Elise van Looij Aug 08 '18 at 13:30
  • @ElisevanLooij An l-value is an expression that can be assigned to, basically the left side of an `a = b` expression. An example would be a variable or a C-array subscript. The return value of a method call is not an l-value. – Nikolai Ruhe Aug 08 '18 at 19:26
16

Read http://nshipster.com/nscharacterset/

NSString -stringByTrimmingCharactersInSet: is a method you should know by heart. It's most often passed NSCharacterSet +whitespaceCharacterSet or +whitespaceAndNewlineCharacterSet in order to remove the leading and trailing whitespace of string input.

So, in Swift 3

let _ = " A B  C  ".trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) // A B  C
onmyway133
  • 45,645
  • 31
  • 257
  • 263
  • 12
    For lazy persons like me: theString = [theString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; should do the trick, while editing the same string. – Mesbah Aug 18 '15 at 07:58
2

SSToolkit has a couple of nice categories for this: edit: link is broken and it doesn't seem to be in SSToolkit anymore.

This is the old code:

- (NSString *)stringByTrimmingLeadingAndTrailingCharactersInSet:(NSCharacterSet *)characterSet {
return [[self stringByTrimmingLeadingCharactersInSet:characterSet]
        stringByTrimmingTrailingCharactersInSet:characterSet];
}


- (NSString *)stringByTrimmingLeadingAndTrailingWhitespaceAndNewlineCharacters {
    return [[self stringByTrimmingLeadingWhitespaceAndNewlineCharacters]
            stringByTrimmingTrailingWhitespaceAndNewlineCharacters];
}


- (NSString *)stringByTrimmingLeadingCharactersInSet:(NSCharacterSet *)characterSet {
    NSRange rangeOfFirstWantedCharacter = [self rangeOfCharacterFromSet:[characterSet invertedSet]];
    if (rangeOfFirstWantedCharacter.location == NSNotFound) {
        return @"";
    }
    return [self substringFromIndex:rangeOfFirstWantedCharacter.location];
}


- (NSString *)stringByTrimmingLeadingWhitespaceAndNewlineCharacters {
    return [self stringByTrimmingLeadingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}


- (NSString *)stringByTrimmingTrailingCharactersInSet:(NSCharacterSet *)characterSet {
    NSRange rangeOfLastWantedCharacter = [self rangeOfCharacterFromSet:[characterSet invertedSet]
                                                               options:NSBackwardsSearch];
    if (rangeOfLastWantedCharacter.location == NSNotFound) {
        return @"";
    }
    return [self substringToIndex:rangeOfLastWantedCharacter.location + 1]; // Non-inclusive
}


- (NSString *)stringByTrimmingTrailingWhitespaceAndNewlineCharacters {
    return [self stringByTrimmingTrailingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}

However instead of stripping several times with different character sets, it might be better to create a union of all character sets you want removed. NSMutableCharacterSet is your friend there.

orkoden
  • 18,946
  • 4
  • 59
  • 50
1

Removes leading white spaces and trailing white spaces from "string"

- (NSString*)stringByRemovingLeadingAndTrailingWhiteSpaces:(NSString*)string {

    NSArray * components = [string componentsSeparatedByString:@" "];

    if([components count] == 1) {
        return string;
    }

    NSUInteger originalLength = [string length];
    unichar buffer[originalLength+1];
    [string getCharacters:buffer range:NSMakeRange(0, originalLength)];

    NSMutableString * newStringNoLeadingSpace = [NSMutableString string];
    BOOL goToStripTrailing = NO;
    for(int i = 0; i < originalLength; i++) {
        NSLog(@"%C", buffer[i]);
        NSString * newCharString = [NSString stringWithFormat:@"%c", buffer[i]];
        if(goToStripTrailing == NO && [newCharString isEqualToString:@" "]) continue;
        goToStripTrailing = YES;
        [newStringNoLeadingSpace appendString:newCharString];
    }

    NSUInteger newLength = [newStringNoLeadingSpace length];
    NSMutableString * newString = [NSMutableString string];
    unichar bufferSecondPass[newLength+1];
    [newStringNoLeadingSpace getCharacters:bufferSecondPass range:NSMakeRange(0, newLength)];

    int locationOfLastCharacter = (int)newLength;
    for(int i = (int)newLength - 1; i >= 0; i--) {
        NSLog(@"%C", bufferSecondPass[i]);
        NSString * newCharString = [NSString stringWithFormat:@"%c", bufferSecondPass[i]];
        locationOfLastCharacter = i+1;
        if(![newCharString isEqualToString:@" "]) break;
    }

    NSRange range = NSMakeRange(0, locationOfLastCharacter);

    newString = [[NSString stringWithString:[newStringNoLeadingSpace substringWithRange:range]] copy];

    return newString;
}
zumzum
  • 17,984
  • 26
  • 111
  • 172
1

Nikolai is right about the mutability. So the probably easiest way to solve things is to define

@property (nonatomic, strong) NSMutableArray *arrayRefineSubjectCode;

and then insert strings one by one, e.g.

for ( int counter = 0 ; counter < 3 ; counter++ ) {
    NSMutableString *s = [NSMutableString stringWithFormat:@"   blah  "];
    [arrayRefineSubjectCode addObject:s];
    }

... to get three elements with " blah " in it. Note that you cannot addObject to an immutable NSArray, only to a mutable NSMutableArray.

Of course, you may have the strings with superfluous spaces sitting around somewhere already. You'll have to make a mutable (!) copy of those strings, and add those to the arrayRefineSubjectCode array with addObject. You could remove the spaces before or after adding them to the array.

Hope that helps a bit.

Just thought to add a last remark. You might wonder why you'd use immutable objects anyway. There are a few reasons, but if you can get away with immutable, they result in faster code, copying is easy (just copy the pointer to the address that holds the data, because that data won't change anyway), and it is more likely to be thread-safe. Of course, be careful then with an NSArray that points at mutable objects like NSMutableString!

Carelinkz
  • 936
  • 8
  • 27
  • 1
    By the way, the problem is not that the array is immutable (after all, it just contains pointers to the NSString objects), the problem is that the NSString objects are immutable. So if you have an NSArray populated with pointers to NSMutableString objects, you'd have no trouble. – Carelinkz Feb 15 '12 at 10:50
  • You are welcome. To create a mutable array just use NSMutableArray, it will does most of what NSArray does (all?) and then some. I am still a bit curious why you would build an array like the one in your example, but I presume that is just a quick bit of code to get the point across? – Carelinkz Feb 15 '12 at 20:34
0

Mutate no, copy and replace yes:

- (void)test_stringByTrimming
{
    NSArray *arrayRefineSubjectCode = [NSArray arrayWithObjects:
                    @"  BKKC 2061",
                    @"   BKKS 2631   ",
                    @"BKKS 2381      ",
                    nil];
    NSMutableArray *trimmedStrings = [NSMutableArray arrayWithArray:arrayRefineSubjectCode];

    for (NSInteger i=0; i<trimmedStrings.count;i++) {
        [trimmedStrings setObject:[[arrayRefineSubjectCode objectAtIndex:i] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] atIndexedSubscript:i];
    }
    XCTAssertTrue([[trimmedStrings objectAtIndex:0] isEqualToString:@"BKKC 2061"]);
    XCTAssertTrue([[trimmedStrings objectAtIndex:1] isEqualToString:@"BKKS 2631"]);
    XCTAssertTrue([[trimmedStrings objectAtIndex:2] isEqualToString:@"BKKS 2381"]);

    XCTAssertTrue([[arrayRefineSubjectCode objectAtIndex:0] isEqualToString:@"  BKKC 2061"]);
    XCTAssertTrue([[arrayRefineSubjectCode objectAtIndex:1] isEqualToString:@"   BKKS 2631   "]);
    XCTAssertTrue([[arrayRefineSubjectCode objectAtIndex:2] isEqualToString:@"BKKS 2381      "]);
}
Elise van Looij
  • 4,162
  • 3
  • 29
  • 52