Reputation: 511656
I have a string that I got from a text file.
Text file:
Line 1
Line 2
Line 3
...
I want to convert it to an array, one array element per line.
[ "Line 1", "Line 2", "Line 3", ... ]
Depending on how the file was saved, the string could take one of the following forms:
string = "Line 1\nLine 2\nLine 3\n..."
where \n
is the new line (line feed) character
string = "Line 1\r\nLine 2\r\nLine 3\r\n..."
where \r
is the carriage return character.
As I understand it, \n
is commonly used in Apple/Linux today, while \r\n
is used in Windows.
How do I split a string at any line break to get a String array without any empty elements?
There are several solutions that work below. At this point I don't have any compelling reason to choose one as more correct than the others. Some factors that may influence choice could be (1) how "Swift" it is and (2) how fast it is for very long strings. You can provide feedback by upvoting one or more of them and/or leaving a comment.
Upvotes: 63
Views: 88836
Reputation: 573
Option 1:
let getName = "Davender+Verma"
let cleanFile = getName.replacingOccurrences(of: "+", with: "+\n")
self.upcomingViewPetName.text = cleanFile
// Output
Davender+
verma
Option 2:
let getName = "Davender+Verma"
let cleanFile = getName.replacingOccurrences(of: "+", with: "\n")
self.upcomingViewPetName.text = cleanFile
//Output:
Davender
verma
Upvotes: 0
Reputation: 236340
Swift 5.2 or later
You can split your String
using the new Character
property isNewline
:
let sentence = "Line 1\nLine 2\nLine 3\n"
let lines = sentence.split(whereSeparator: \.isNewline)
print(lines) // "["Line 1", "Line 2", "Line 3"]\n"
You can also extend StringProtocol and create a lines instance property to break up the string lines into subsequences:
extension StringProtocol {
var lines: [SubSequence] { split(whereSeparator: \.isNewline) }
}
let sentence = "Line 1\nLine 2\r\nLine 3\n"
for line in sentence.lines {
print(line)
}
let lines = sentence.lines // ["Line 1", "Line 2", "Line 3"]
Original Answer
You can use String method enumerateLines:
Enumerates all the lines in a string.
Swift 3 or later
let sentence = "Line 1\nLine 2\nLine 3\n"
var lines: [String] = []
sentence.enumerateLines { line, _ in
lines.append(line)
}
print(lines) // "["Line 1", "Line 2", "Line 3"]\n"
extension String {
var lines: [String] {
var result: [String] = []
enumerateLines { line, _ in result.append(line) }
return result
}
}
let sentence2 = "Line 4\nLine 5\nLine 6\n"
let sentence2Lines = sentence2.lines
print(sentence2Lines) // "["Line 4", "Line 5", "Line 6"]\n"
let sentence3 = "Line 7\r\nLine 8\r\nLine 9\r\n"
let sentence3Lines = sentence3.lines
print(sentence3Lines) // "["Line 7", "Line 8", "Line 9"]\n"
Upvotes: 139
Reputation: 38667
For the record, Swift's Foundation CharacterSet
can be used within split:
extension String {
var lines: [String] {
return split { String($0).rangeOfCharacter(from: .newlines) != nil }.map(String.init)
}
}
extension String {
var lines: [String] {
return split { CharacterSet.newlines.contains($0.unicodeScalars.first!) }.map(String.init)
}
}
Upvotes: 3
Reputation: 43
Swift 4:
I would recommend to first save your CSV into string if you haven't already done it, then "clean" the string by removing unnecessary carriage returns
let dataString = String(data: yourData!, encoding: .utf8)!
var cleanFile = dataString.replacingOccurrences(of: "\r", with: "\n")
cleanFile = cleanFile.replacingOccurrences(of: "\n\n", with: "\n")
Above will give you a string with a most desirable format, then you can separate the string using \n as your separator:
let csvStrings = cleanFile.components(separatedBy: ["\n"])
Now you have an array of 3 items like:
["Line1","Line2","Line3"]
I am using a CSV file and after doing this, I am splitting items into components, so if your items were something like:
["Line1,Line2,Line3","LineA,LineB,LineC"]
let component0 = csvStrings[0].components(separatedBy: [","]) // ["Line1","Line2","Line3"]
let component1 = csvStrings[1].components(separatedBy: [","]) // ["LineA","LineB","LineC"]
Upvotes: 1
Reputation: 269
in Xcode 8.2, Swift 3.0.1:
Use NSString method components(separatedBy:)
let text = "line1\nline2"
let array = text.components(separatedBy: CharacterSet.newlines)
Or use String method enumerateLines, like Leo Dabus
's answer
Upvotes: 26
Reputation: 511656
This answer is a summary of the other solutions already given. It comes from my fuller answer, but it would be useful to have the actual method choices available here.
New lines are usually made with the \n
character, but can also be made with \r\n
(from files saved in Windows).
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.
\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
.Upvotes: 9
Reputation: 37043
In Swift 2, the top-level split
function is now a method on CollectionType
(which each of String
s "character views" conforms to). There are two versions of the method, you want the one that takes a closure as a predicate to indicate whether a given element should be treated as a separator.
You can get the character collection from the string as a collection of UTF16 characters using string.utf16
, making them compatible with the NSCharacterSet
APIs. This way, we can easily check inside the closure whether a given character in the string is a member of the newline character set.
It's worth noting that split(_:)
will return a SubSequence
of characters (basically a Slice
), so it needs transforming back into an array of Strings which is generally more useful. I've done this below using flatMap(String.init)
- the UTF16View
initialiser on String
is failable, so using flatMap
will ignore any nil
values that might be returned, ensuring you get an array of non-optional strings back.
So for a nice Swift-like way of doing this:
let str = "Line 1\nLine 2\r\nLine 3\n"
let newlineChars = NSCharacterSet.newlineCharacterSet()
let lines = str.utf16.split { newlineChars.characterIsMember($0) }.flatMap(String.init)
// lines = ["Line 1", "Line 2", "Line 3"]
What makes this nice is that the split
method has a parameter allowEmptySubsequences
, which ensures you don't receive any empty character sequences in the result. This is false
by default, so you don't actually need to specify it at all.
If you want to avoid NSCharacterSet
altogether, you can just as easily split the collection of unicode compliant Character
s.
let lines = str.characters.split { $0 == "\n" || $0 == "\r\n" }.map(String.init)
Swift is able to treat "\r\n"
as a single extended grapheme cluster, using it as a single Character
for the comparison instead of creating a String
. Also note that the initialiser for creating a string from a Character
is non failable, so we can just use map
.
Upvotes: 11
Reputation: 2400
How do I split a string at any line break to get a String array without any empty elements?
You were almost there - it's just the trailing closure which is different here:
let array = stringFromFile.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()).filter{!$0.isEmpty}
Which is the same as:
let newLineChars = NSCharacterSet.newlineCharacterSet() // newline characters defined as (U+000A–U+000D, U+0085)
let array = stringFromFile.componentsSeparatedByCharactersInSet(newLineChars).filter{!$0.isEmpty}
ETA: removed unnecessary extra brackets at trailing closure
Upvotes: 1
Reputation: 17534
let test1 = "Line1\n\rLine2\nLine3\rLine4"
let t1 = test1.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet())
let t2 = t1.filter{ $0 != "" }
let t3 = t1.filter{ !$0.isEmpty }
Upvotes: 7