5

What I'm wanting to do is very simple in C/C++, Java, and so many other languages. All I want to do is be able to specify the width of a string, similar to this:

printf("%-15s", var);

This would create of a field width of 15 characters. I've done a lot of googling. I've tried using COpaquepointeras well as String(format:in various ways with no luck. Any suggestions would be greatly appreciated. I could have missed something when googling.

Alex Cauthen
  • 497
  • 5
  • 12
  • 32

7 Answers7

3

You can use withCString to quickly convert the string to an array of bytes (technically an UnsafePointer<Int8>):

let str = "Hello world"
let formatted = str.withCString { String(format: "%-15s", $0) }

print("'\(formatted)'")
Code Different
  • 90,614
  • 16
  • 144
  • 163
2

You are better to do it yourself

let str0 = "alpha"
let length = 20
// right justify
var str20r = String(count: (length - str0.characters.count), repeatedValue: Character(" "))
str20r.appendContentsOf(str0)
// "               alpha"

// left justify
var str20l = str0
str20l.appendContentsOf(String(count: (length - str0.characters.count), repeatedValue: Character(" ")))
// "alpha               "

if you need something 'more general'

func formatString(str: String, fixLenght: Int, spacer: Character = Character(" "), justifyToTheRigth: Bool = false)->String {
    let c = str.characters.count
    let start = str.characters.startIndex
    let end = str.characters.endIndex
    var str = str
    if c > fixLenght {
        switch justifyToTheRigth {
        case true:
            let range = start.advancedBy(c - fixLenght)..<end
            return String(str.characters[range])
        case false:
            let range = start..<end.advancedBy(fixLenght - c)
            return String(str.characters[range])
        }
    } else {
        var extraSpace = String(count: fixLenght - c, repeatedValue: spacer)
        if justifyToTheRigth {
            extraSpace.appendContentsOf(str)
            return extraSpace
        } else {
            str.appendContentsOf(extraSpace)
            return str
        }
    }
}

let str = "ABCDEFGH"
let s0 = formatString(str, fixLenght: 3)
let s1 = formatString(str, fixLenght: 3, justifyToTheRigth: true)
let s2 = formatString(str, fixLenght: 10, spacer: Character("-"))
let s3 = formatString(str, fixLenght: 10, spacer: Character("-"), justifyToTheRigth: true)

print(s0)
print(s1)
print(s2)
print(s3)

which prints

ABC
FGH
ABCDEFGH--
--ABCDEFGH
user3441734
  • 16,722
  • 2
  • 40
  • 59
1

The problem is that Swift strings have variable size elements, so it's ambiguous what "15 characters" is. This is a source of frustration for simple strings — but makes the language more precise when dealing with emoji, regional identifiers, ligatures, etc.

You can convert the Swift string to a C-string and use normal formatters (see Santosh's answer). The "Swift" way to handle strings is to begin at the starting index of the collection of Characters and advance N times. For example:

let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

let index = alphabet.characters.startIndex.advancedBy(14) // String.CharacterView.Index
let allChars = alphabet.characters.prefixThrough(index) // String.CharacterView

print(String(allChars)) // "ABCDEFGHIJKLMNO\n"

If you want to force padding, you could use an approach like this:

extension String {
    func formatted(characterCount characterCount:Int) -> String {
        if characterCount < characters.count {
            return String(characters.prefixThrough(characters.startIndex.advancedBy(characterCount - 1)))
        } else {
            return self + String(count: characterCount - characters.count, repeatedValue: " " as Character)
        }
    }
}

let abc = "ABC"
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

print("!\(abc.formatted(characterCount: 15))!")
// "!ABC            !\n"

print("!\(alphabet.formatted(characterCount: 15))!")
// "!ABCDEFGHIJKLMNOP!\n"
Aaron Brager
  • 65,323
  • 19
  • 161
  • 287
0

Did you try this?

let string1 = "string1"
let string2 = "string2"
let formattedString = String(format: "%-15s - %s",
             COpaquePointer(string1.cStringUsingEncoding(NSUTF8StringEncoding)!),
             COpaquePointer(string2.cStringUsingEncoding(NSUTF8StringEncoding)!)
)

print(formattedString)
//string1         - string2
Santosh
  • 2,900
  • 1
  • 16
  • 16
  • https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html – user3441734 Jun 29 '16 at 15:17
  • Yes. I believe I was getting a exc_bad_instruction error. Right now I'm writing a custom function to do what I want for me until I can find a better answer. – Alex Cauthen Jun 29 '16 at 15:19
0

From one hand %@ is used to format String objects:

import Foundation

var str = "Hello"
print(String(format: "%@", str))

But it does not support the width modifier:

print(String(format: "%-15@", str))

Will still print unpadded text:

"Hello\n"

However there is a modifier %s that seems to work with CStrings:

var cstr = (str as NSString).utf8String //iOS10+ or .UTF8String otherwise

print(String(format: "%-15s", cstr!))

Output:

"Hello          \n"

One nice thing is that you can use the same format specification with NSLog:

NSLog("%-15s", cstr!)
yurgis
  • 4,017
  • 1
  • 13
  • 22
0

We've got a ton of interesting answers now. Thank you everyone. I wrote the following:

func formatLeftJustifiedWidthSpecifier(stringToChange: String, width: Int) -> String {

    var newString: String = stringToChange
    newString = newString.stringByPaddingToLength(width, withString: " ", startingAtIndex: 0)
    return newString
}
Alex Cauthen
  • 497
  • 5
  • 12
  • 32
  • A few issues: (1) You're bridging to NSString, so you're losing out on the benefits of Swift strings, (2) Why even write this instead of calling `stringByPaddingToLength` directly?, (3) `: String` is unnecessary, (4) Why assign to `newString` only to immediately overwrite the value? – Aaron Brager Jun 29 '16 at 16:01
  • I didn't realize that it bridges to an NSString. Thanks for letting me know. If I make a function I can call it from anywhere in order to avoid code duplication. I come from a background of explicitly defining my types, so it is really a stylistic thing. In Swift 3.0 and on, function parameters are defined as let constants, so it requires a local variable to make a modification. – Alex Cauthen Jun 29 '16 at 16:08
0

To augment the answer above by "Code Different" (thank you!) on Jun 29, 2016, and allow to write something like "hello".center(42); "world".alignLeft(42):

extension String {

    // note: symbol names match to nim std/strutils lib

    func align (_ boxsz: UInt) -> String {
        self.withCString { String(format: "%\(boxsz)s", $0) }
    }

    func alignLeft (_ boxsz: UInt) -> String {
        self.withCString { String(format: "%-\(boxsz)s", $0) }
    }

    func center (_ boxsz: UInt) -> String {
        let n = self.count
        guard boxsz > n else { return self }
        let padding = boxsz - UInt(n)
        let R = padding / 2
        guard R > 0 else { return " " + self }
        let L = (padding%2 == 0) ? R : (R+1)
        return " ".withCString { String(format: "%\(L)s\(self)%\(R)s", $0,$0) }
    }

}
sqr163
  • 1,074
  • 13
  • 24