49

I'm trying to print a list of Strings all padded to the same width.

In C, I would use something like printf("%40s", cstr), where cstr is a C string.

In Swift, the best I could come up is this:

line += String(format: "%40s",string.cStringUsingEncoding(<someEncoding>))

Is there a better way ?

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Droopycom
  • 1,831
  • 1
  • 17
  • 20

10 Answers10

89

In Swift 3 you can use:

let str = "Test string"
let paddedStr = str.padding(toLength: 20, withPad: " ", startingAt: 0)

Result string: "Test string "

If you need to pad to the left the text (right justify), you can write the following function as an extension to String:

extension String {
    func leftPadding(toLength: Int, withPad character: Character) -> String {
        let newLength = self.characters.count
        if newLength < toLength {
            return String(repeatElement(character, count: toLength - newLength)) + self
        } else {
            return self.substring(from: index(self.startIndex, offsetBy: newLength - toLength))
        }
    }
}

So if you write:

let str = "Test string"
let paddedStr = str.leftPadding(toLength: 20, withPad: " ")

Result string: " Test string"

In Swift 4.1 the substring method is deprecated and there are a number of new methods to obtain a substring. Either prefix, suffix or subscripting the String with a Range<String.Index>.

For the previous extension we can use the suffix method to accomplish the same result. Since the suffix method returns a String.SubSequence, it needs to be converted into a String before being returned.

extension String {
    func leftPadding(toLength: Int, withPad character: Character) -> String {
        let stringLength = self.count
        if stringLength < toLength {
            return String(repeatElement(character, count: toLength - stringLength)) + self
        } else {
            return String(self.suffix(toLength))
        }
    }
}
Gustavo Seidler
  • 2,011
  • 1
  • 14
  • 12
35

For Swift >= 3

line += string.padding(toLength: 40, withPad: " ", startingAt: 0)

For Swift < 3

NSString has the stringByPaddingToLength: method:

line += string.stringByPaddingToLength(40, withString: " ", startingAtIndex: 0)
Code Different
  • 90,614
  • 16
  • 144
  • 163
  • 3
    For add padding to left side ,we have to write our own code., extension String { func PadLeft(totalWidth: Int,byString:String) -> String { let toPad = totalWidth - self.characters.count; if toPad < 1 { return self; } return "".stringByPaddingToLength(toPad, withString: byString, startingAtIndex: 0) + self; } } – Kiran P Nair Jul 10 '16 at 18:02
  • 1
    this method will also truncate your string if it is longer than your length (40 characters in this case) rather than just add no padding. – odyth Sep 12 '19 at 23:35
  • @KiranPNair Good call-out. One would assume that startingAtIndex would mean where to start padding the string; but in fact, it's the starting index of withString. This seems like the least-useful option, but there you go Apple. – Oscar Jan 10 '20 at 00:41
  • This is using `NSString` API and, as such, the character count is add odds with Swift's way of counting characters. – Nikolai Ruhe Feb 17 '21 at 14:58
12
extension RangeReplaceableCollection where Self: StringProtocol {
    func paddingToLeft(upTo length: Int, using element: Element = " ") -> SubSequence {
        return repeatElement(element, count: Swift.max(0, length-count)) + suffix(Swift.max(count, count-length))
    }
}

"123".paddingToLeft(upTo: 5)              //  "  123"
"123".paddingToLeft(upTo: 5, using: "0")  //  "00123"
"123".paddingToLeft(upTo: 3, using: "0")  //    "123"
"$199.99".dropLast(3).paddingToLeft(upTo: 10, using: "_")  //  "______$199"

To replicate the same behaviour as padding(toLength:, withPad:, startingAt:) we can add rotateTo left functionality to RangeReplaceableCollection

extension RangeReplaceableCollection {
    func rotatingLeft(positions: Int) -> SubSequence {
        let index = self.index(startIndex, offsetBy: positions, limitedBy: endIndex) ?? endIndex
        return self[index...] + self[..<index]
    }
}

And implement it as follow:

extension RangeReplaceableCollection where Self: StringProtocol {
    func paddingToLeft<S: StringProtocol & RangeReplaceableCollection>(upTo length: Int, with string: S, startingAt index: Int = 0) -> SubSequence {
        let string = string.rotatingLeft(positions: index)
        return repeatElement(string, count: length-count/string.count)
            .joined().prefix(length-count) + suffix(Swift.max(count, count-length))
    }
}

"123".paddingToLeft(upTo: 10, with: "abc", startingAt: 2)   //  "cabcabc123"
"123".padding(toLength: 10, withPad: "abc", startingAt: 2)  //  "123cabcabc"
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
7

Put all string-format-code into extension and reuse it wherever you want.

extension String {
    func padding(length: Int) -> String {
        return self.stringByPaddingToLength(length, withString: " ", startingAtIndex: 0)
    }

    func padding(length: Int, paddingString: String) -> String {
        return self.stringByPaddingToLength(length, withString: paddingString, startingAtIndex: 0)
    }
}

var str = "str"
print(str.padding(10)) // "str       "
print(str.padding(10, paddingString: "+")) // "str+++++++"
pacification
  • 5,838
  • 4
  • 29
  • 51
6

The following two functions return a string padded to the given width, either left or right justified. It is pure Swift 4, no NSString, and no C string either. You may choose if a string longer than the padding width will be truncated or not.

extension String {
    func rightJustified(width: Int, truncate: Bool = false) -> String {
        guard width > count else {
            return truncate ? String(suffix(width)) : self
        }
        return String(repeating: " ", count: width - count) + self
    }

    func leftJustified(width: Int, truncate: Bool = false) -> String {
        guard width > count else {
            return truncate ? String(prefix(width)) : self
        }
        return self + String(repeating: " ", count: width - count)
    }
}
Tom E
  • 1,530
  • 9
  • 17
5

For left padding, you can use double-reverse trick:

String(String(s.reversed()).padding(toLength: 5, withPad: "0", startingAt: 0).reversed())

Of course, you can wrap it as an extension:

extension String {
    func leftPadding(toLength: Int, withPad: String) -> String {
        String(String(reversed()).padding(toLength: toLength, withPad: withPad, startingAt: 0).reversed())
    }
}
user2878850
  • 2,446
  • 1
  • 18
  • 22
3

Here's my solution, specific to String, but I'm sure someone smarter than I could make it more generic.

extension String {
    func frontPadding(toLength length: Int, withPad pad: String, startingAt index: Int) -> String {
        return String(String(self.reversed()).padding(toLength: length, withPad: pad, startingAt: index).reversed())
    }
}
Zonker.in.Geneva
  • 1,389
  • 11
  • 19
2

Swift 5:

I just spent an embarrassing amount of time playing with this same problem.

let short = "ab"
String(repeating: "0", count: 8 - short.count).appending(short)
// 000000ab
0
import Foundation   // for NSString.padding()

/**
 *  Custom Extension's API
 *  ------------------------------
 *  • str.padEnd(_:_:)
 *  • str.padStart(_:_:)
 *  ------------------------------
 *  • int.padStart(_:_:forceSign:)
 */

extension String {

    // str.padEnd(8, "-")
    func padEnd(_ length: Int, _ pad: String) -> String {
        return padding(toLength: length, withPad: pad, startingAt: 0)
    }

    // str.padStart(8, "*")
    func padStart(_ length: Int, _ pad: String) -> String {
        let str = String(self.reversed())
        return String(str.padEnd(length, pad).reversed())
    }
}

extension Int {

    // int.padStart(8)
    func padStart(

        _  length: Int,             // total length
        _     pad: String = "0",    // pad character
        forceSign: Bool   = false   // force + sign if positive

    ) -> String {

        let isNegative = self < 0
        let n = abs(self)
        let str = String(n).padStart(length, pad)

        return 
            isNegative ? "- " + str :
            forceSign  ? "+ " + str :
            str
    }
}

// test run
let s1 = "abc"

[
    s1.padEnd(15, "*"),                 // abc************
    s1.padStart(15, "*"),               // ************abc
    3.padStart(8, forceSign: true),     // + 00000003
    (-125).padStart(8)                  // - 00000125

].forEach{ print($0) }
lochiwei
  • 1,240
  • 9
  • 16
0

left padding is here

extension String {
  func leftPadding(toLength: Int, withPad character: Character) -> String {
    if count < toLength {
      return String(repeating: character, count: toLength - count) + self
    } else {
      return self
    }
  }
}

result

"234".leftPadding(toLength: 1, withPad: "0") // 234
"234".leftPadding(toLength: 2, withPad: "0") // 234
"234".leftPadding(toLength: 3, withPad: "0") // 234
"234".leftPadding(toLength: 4, withPad: "0") // 0234
"234".leftPadding(toLength: 5, withPad: "0") // 00234
Hun
  • 3,652
  • 35
  • 72