36

Every example of trimming strings in Swift remove both leading and trailing whitespace, but how can only trailing whitespace be removed?

For example, if I have a string:

"    example  "

How can I end up with:

"    example"

Every solution I've found shows trimmingCharacters(in: CharacterSet.whitespaces), but I want to retain the leading whitespace.

RegEx is a possibility, or a range can be derived to determine index of characters to remove, but I can't seem to find an elegant solution for this.

Jason Sturges
  • 15,855
  • 14
  • 59
  • 80
  • 2
    Possible duplicate of [Swift remove ONLY trailing spaces from string](http://stackoverflow.com/questions/41412161/swift-remove-only-trailing-spaces-from-string) – Hamish Jan 10 '17 at 10:14

12 Answers12

49

With regular expressions:

let string = "    example  "
let trimmed = string.replacingOccurrences(of: "\\s+$", with: "", options: .regularExpression)
print(">" + trimmed + "<")
// >    example<

\s+ matches one or more whitespace characters, and $ matches the end of the string.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Yes, that's probably nicer. I don't if that goes without saying, but this needs `Foundation`, too. – Raphael Jan 10 '17 at 09:39
  • 3
    No. You shouldn't use regular expressions when there is a better tool for the job: character sets. See [the answer by Obliquely](https://stackoverflow.com/a/44117204/156210) – ChrisJF Jul 31 '18 at 17:36
  • 3
    @ChrisJF: It does the job, without recursion or creating intermediate strings. – Martin R Sep 07 '18 at 07:15
23

In Swift 4 & Swift 5

This code will also remove trailing new lines. It works based on a Character struct's method .isWhitespace

var trailingSpacesTrimmed: String {
    var newString = self

    while newString.last?.isWhitespace == true {
        newString = String(newString.dropLast())
    }

    return newString
}
Senõr Ganso
  • 1,694
  • 16
  • 23
10

This short Swift 3 extension of string uses the .anchored and .backwards option of rangeOfCharacter and then calls itself recursively if it needs to loop. Because the compiler is expecting a CharacterSet as the parameter, you can just supply the static when calling, e.g. "1234 ".trailing(.whitespaces) will return "1234". (I've not done timings, but would expect faster than regex.)

extension String {
    func trailingTrim(_ characterSet : CharacterSet) -> String {
        if let range = rangeOfCharacter(from: characterSet, options: [.anchored, .backwards]) {
            return self.substring(to: range.lowerBound).trailingTrim(characterSet)
        }
        return self
    }
}
Obliquely
  • 7,002
  • 2
  • 32
  • 51
7

In Foundation you can get ranges of indices matching a regular expression. You can also replace subranges. Combining this, we get:

import Foundation
extension String {
    func trimTrailingWhitespace() -> String {
        if let trailingWs = self.range(of: "\\s+$", options: .regularExpression) {
            return self.replacingCharacters(in: trailingWs, with: "")
        } else {
            return self
        }
    }
}

You can also have a mutating version of this:

import Foundation
extension String {
    mutating func trimTrailingWhitespace() {
        if let trailingWs = self.range(of: "\\s+$", options: .regularExpression) {
            self.replaceSubrange(trailingWs, with: "")
        }
    }
}

If we match against \s* (as Martin R. did at first) we can skip the if let guard and force-unwrap the optional since there will always be a match. I think this is nicer since it's obviously safe, and remains safe if you change the regexp. I did not think about performance.

Raphael
  • 9,779
  • 5
  • 63
  • 94
6

Handy String extension In Swift 4

extension String {

    func trimmingTrailingSpaces() -> String {
        var t = self
        while t.hasSuffix(" ") {
          t = "" + t.dropLast()
        }
        return t
    }

    mutating func trimmedTrailingSpaces() {
        self = self.trimmingTrailingSpaces()
    }

}
TimBigDev
  • 511
  • 8
  • 7
6

Demosthese's answer is a useful solution to the problem, but it's not particularly efficient. This is an upgrade to their answer, extending StringProtocol instead, and utilizing Substring to remove the need for repeated copying.

extension StringProtocol {

    @inline(__always)
    var trailingSpacesTrimmed: Self.SubSequence {
        var view = self[...]

        while view.last?.isWhitespace == true {
            view = view.dropLast()
        }

        return view
    }
}
5

Swift 4

extension String {
    var trimmingTrailingSpaces: String {
        if let range = rangeOfCharacter(from: .whitespacesAndNewlines, options: [.anchored, .backwards]) {
            return String(self[..<range.lowerBound]).trimmingTrailingSpaces
        }
        return self
    }
}
John Rogers
  • 2,192
  • 19
  • 29
4

No need to create a new string when dropping from the end each time.

extension String {

    func trimRight() -> String {
        String(reversed().drop { $0.isWhitespace }.reversed())
    }
}

This operates on the collection and only converts the result back into a string once.

3

It's a little bit hacky :D

let message = "    example  "
var trimmed = ("s" + message).trimmingCharacters(in: .whitespacesAndNewlines)
trimmed = trimmed.substring(from: trimmed.index(after: trimmed.startIndex))
Duyen-Hoa
  • 15,384
  • 5
  • 35
  • 44
2

Without regular expression there is not direct way to achieve that.Alternatively you can use the below function to achieve your required result :

func removeTrailingSpaces(with spaces : String) -> String{

        var spaceCount = 0
        for characters in spaces.characters{
            if characters == " "{
                print("Space Encountered")
                spaceCount = spaceCount + 1
            }else{
                break;
            }
        }

        var finalString = ""
        let duplicateString = spaces.replacingOccurrences(of: " ", with: "")
        while spaceCount != 0 {
          finalString = finalString + " "
            spaceCount = spaceCount - 1
        }

        return (finalString + duplicateString)


    }

You can use this function by following way :-

 let str = "   Himanshu  "
 print(removeTrailingSpaces(with : str))
Himanshu
  • 2,832
  • 4
  • 23
  • 51
1

One line solution with Swift 4 & 5

As a beginner in Swift and iOS programming I really like @demosthese's solution above with the while loop as it's very easy to understand. However the example code seems longer than necessary. The following uses essentially the same logic but implements it as a single line while loop.

// Remove trailing spaces from myString
while myString.last == " " { myString = String(myString.dropLast()) }

This can also be written using the .isWhitespace property, as in @demosthese's solution, as follows:

while myString.last?.isWhitespace == true { myString = String(myString.dropLast()) }

This has the benefit (or disadvantage, depending on your point of view) that this removes all types of whitespace, not just spaces but (according to Apple docs) also including newlines, and specifically the following characters:

  • “\t” (U+0009 CHARACTER TABULATION)
  • “ “ (U+0020 SPACE)
  • U+2029 PARAGRAPH SEPARATOR
  • U+3000 IDEOGRAPHIC SPACE

Note: Even though .isWhitespace is a Boolean it can't be used directly in the while loop as it ends up being optional ? due to the chaining of the optional .last property, which returns nil if the String (or collection) is empty. The == true logic gets around this since nil != true.

I'd love to get some feedback on this, esp. in case anyone sees any issues or drawbacks with this simple single line approach.

Arik_E
  • 19
  • 3
0

Swift 5

extension String {

   func trimTrailingWhiteSpace() -> String {
       guard self.last == " " else { return self }
    
       var tmp = self
       repeat {
           tmp = String(tmp.dropLast())
       } while tmp.last == " "
    
       return tmp
}

}

Hwangho Kim
  • 629
  • 4
  • 20