28

I read from a csv file, and want to split the long string that I get using stringWithContentsOfFile, which is a multi line string, with individual lines representing rows in the csv file. How do I do this?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Sonu Jha
  • 719
  • 3
  • 13
  • 23

6 Answers6

43

Just in case anyone stumbles across this question like I did. This will work with any newline characters:

NSCharacterSet *separator = [NSCharacterSet newlineCharacterSet];
NSArray *rows = [yourString componentsSeparatedByCharactersInSet:separator];
Diana Farin
  • 580
  • 1
  • 4
  • 10
  • 6
    This can produce empty strings in the array if the line separators are `\r\n` (two characters) as happens in Windows formatted files. – Suragch Aug 15 '15 at 03:31
31

You can break the string into arrays of string and then manipulate as you want.

NSArray *brokenByLines=[yourString componentsSeparatedByString:@"\n"]
Anoop Vaidya
  • 46,283
  • 15
  • 111
  • 140
Dipendra
  • 620
  • 6
  • 8
  • 6
    This makes a LOT of assumptions about what the line separators are. – uchuugaka Mar 02 '13 at 09:38
  • 2
    If you don't know what the line separators are: `let lines = stringData.stringByReplacingOccurrencesOfString("\r\n", withString: "\"n").stringByReplacingOccurrencesOfString("\r", withString: "\n").componentsSeparatedByString("\n")` – Zaphod Apr 02 '16 at 14:25
  • One do not have to replace @"\r", if the string is separated with newLineCharacterSet; – Amin Negm-Awad Sep 12 '16 at 20:58
22

You should be aware that \n is not the only character used to split a new line. For example, if the file was saved in Windows, the newline characters would be \r\n. Read the Newline article in Wikipedia for more information about this.

Thus, if you just use componentsSeparatedByString("\n"), you may get unexpected results.

let multiLineString = "Line 1\r\nLine 2\r\nLine 3\r\n"
let lineArray = multiLineStringRN.componentsSeparatedByString("\n")
// ["Line 1\r", "Line 2\r", "Line 3\r", ""]

Note both the residual \r and the empty array element.

There are several ways to avoid these problems.

Solutions

1. componentsSeparatedByCharactersInSet

let multiLineString = "Line 1\nLine 2\r\nLine 3\n"
let newlineChars = NSCharacterSet.newlineCharacterSet()
let lineArray = multiLineString.componentsSeparatedByCharactersInSet(newlineChars).filter{!$0.isEmpty}
// "[Line 1, Line 2, Line 3]"

If filter were not used, then \r\n would produce an empty array element because it gets counted as two characters and so separates the string twice at the same location.

2. split

let multiLineString = "Line 1\nLine 2\r\nLine 3\n"
let newlineChars = NSCharacterSet.newlineCharacterSet()
let lineArray = multiLineString.utf16.split { newlineChars.characterIsMember($0) }.flatMap(String.init)
// "[Line 1, Line 2, Line 3]"

or

let multiLineString = "Line 1\nLine 2\r\nLine 3\n"
let lineArray = multiLineString.characters.split { $0 == "\n" || $0 == "\r\n" }.map(String.init)
// "[Line 1, Line 2, Line 3]"

Here \r\n gets counted as a single Swift character (an extended grapheme cluster)

3. enumerateLines

let multiLineString = "Line 1\nLine 2\r\nLine 3\n"
var lineArray = [String]()
multiLineString.enumerateLines { (line, stop) -> () in
    lineArray.append(line)
}
// "[Line 1, Line 2, Line 3]"

For more about the enumerateLine syntax, see this answer also.

Notes:

  • a multi line string would not usually mix both \r\n and \n but I am doing this here to show that these methods can handle both formats.
  • NSCharacterSet.newlineCharacterSet() are newline characters defined as (U+000A–U+000D, U+0085), which include \r and \n.
  • This answer is a summary of the answers to my previous question. Read those answers for more detail.
Community
  • 1
  • 1
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
15

Swift 3 version:

let lines = yourString.components(separatedBy: .newlines)

Nice and short.

Clifton Labrum
  • 13,053
  • 9
  • 65
  • 128
2

You need to separate your content with "\n".

    NSString *str= [NSString stringWithContentsOfFile:filePathLib encoding:NSUTF8StringEncoding error:nil];
    NSArray *rows = [str componentsSeparatedByString:@"\n"];

    for(int i =0;i<[rows count];i++)
        NSLog(@"Row %d: %@",i,[rows objectAtIndex:i]);
βhargavḯ
  • 9,786
  • 1
  • 37
  • 59
1

Here's my take on it:

    NSString* string = @"FOO\r\nBAR\r\r\n\rATZ\rELM327 v1.5";
    NSCharacterSet* newlineSet = [NSCharacterSet newlineCharacterSet];
    NSCharacterSet* whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
    NSArray<NSString*>* components = [string componentsSeparatedByCharactersInSet:newlineSet];
    NSPredicate* predicate = [NSPredicate predicateWithBlock:^BOOL(NSString* _Nullable string, NSDictionary<NSString *,id> * _Nullable bindings){
        return [string stringByTrimmingCharactersInSet:whitespaceSet].length > 0;
    }];
    NSArray<NSString*>* lines = [components filteredArrayUsingPredicate:predicate];

    [lines enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog( @"Line %u = '%@'", idx, obj );
    }];

Running this prints:

2017-10-24 15:26:05.380 Untitled 5[64977:3182818] Line 0 = 'FOO'
2017-10-24 15:26:05.380 Untitled 5[64977:3182818] Line 1 = 'BAR'
2017-10-24 15:26:05.380 Untitled 5[64977:3182818] Line 2 = 'ATZ'
2017-10-24 15:26:05.380 Untitled 5[64977:3182818] Line 3 = 'ELM327 v1.5'

It may not be the most efficient way (probably using an NSScanner would be faster), but it solves the problem here.

DrMickeyLauer
  • 4,455
  • 3
  • 31
  • 67
  • 1
    I like your answer the most because it both addresses many new-line scenarios, cleans up the output, and leaves doors open with NSScanner. I think it could even be nicely wrapped as an NSString Category NSString+lines and provide * lines; method... – Motti Shneor Nov 03 '21 at 08:59