9

For example: var str = String(format: "%12s - %s", "key", "value")

What I want is key will hold chars of length of 12. key__________ - value

(underscore here is whitespace)

Thanks.

6 Answers 6

10

As documentation said, COpaquePointer is a wrapper around an opaque C pointer. Opaque pointers are used to represent C pointers to types that cannot be represented in Swift, such as incomplete struct types.

Key is a String - native Swift type. I believe that it is better to use this Swift String function:

let testString = "bla bli blah"
testString.stringByPaddingToLength(3, withString: " ", startingAtIndex: 0) 
//output = "bla"

Swift 3

let testString = "bla bli blah"
testString.padding(toLength: 3, withPad: " ", startingAt: 0) 
//output = "bla"
Sign up to request clarification or add additional context in comments.

1 Comment

Remember to import Foundation.
4

Basically, to format String with String(format: _:...), we can use %@:

String(format: "%@ - %@", "key", "value")

But, I believe %@ does not support "width" modifier: you cannot %12@ or such.

So, you have to convert String to COpaquePointer which can be formatted with %s:

var key = "key"
var val = "value"

var str = String(format: "%-12s - %s",
    COpaquePointer(key.cStringUsingEncoding(NSUTF8StringEncoding)!),
    COpaquePointer(val.cStringUsingEncoding(NSUTF8StringEncoding)!)
)
// -> "key          - value"

Comments

3

Swift 2 version without COpaquePointer:

import Foundation
let str = "hi".nulTerminatedUTF8
let padded = str.withUnsafeBufferPointer() {
    return String(format: "%-12s", $0.baseAddress!)
}

print(padded)

Swift 3:

import Foundation
let str = "hi".utf8CString
let padded = str.withUnsafeBufferPointer() {
    return String(format: "%-12s", $0.baseAddress!)
}

print(padded)

Comments

3

Swift 5.1 / Xcode 11.1 / iOS 13

This is really the best answer. No C string conversions (screwing up grapheme clusters), no UnsafeBufferPointer.

public extension String {
    func paddedToWidth(_ width: Int) -> String {
        let length = self.count
        guard length < width else {
            return self
        }

        let spaces = Array<Character>.init(repeating: " ", count: width - length)
        return self + spaces
    }
}

Then to use it, you can do something like this:

let fubar = "Foobar".paddedToWidth(10) + "Barfoo"
print(fubar)

// Prints "Foobar    Barfoo".

Comments

0

Build a format string at first:

let formatString = String(format: "%%%ds", key)  // gives "%12s" if key is 12
let str = String(format: formatString, value)

Comments

0

Update for Swift 4

Note: Strings formatted with %s cannot represent unicode characters like emojis and "ä", "ö", "ü", "ß" correctly.

There are two simple ways to solve the general problem with %s in Swift code:

Interface: String(format: String, arguments: CVarArg...)

1. If you have only one string in arguments: CVarArg...

let stringToFormat = "test"
let formattedString = stringToFormat.withCString{
    String(format: "%s", $0)
}

If you need to use multiple strings this gets very tedious and you have to use nested closures.

Therefore...

2. Different strings in arguments: CVarArg...

The easiest way I found is making an extension to String with a computed property c

extension String {
    // nested `struct` which is needed
    // to keep the `baseAdress` pointer valid (see (*))
    struct CString: CVarArg {
        // needed to conform to `CVarArg`
        var _cVarArgEncoding: [Int] = []

        // needed to keep the `baseAdress` pointer valid (see (*))
        var cstring: ContiguousArray<CChar> = []
        
        init(string: String) {
            // is essentially just a (special) `Array`
            cstring = string.utf8CString
            
            self._cVarArgEncoding = cstring.withUnsafeBufferPointer{ 
                // use the `_cVarArgEncoding` of the first Buffer address (*)
                $0.baseAddress!._cVarArgEncoding
            }
        }
    }
    
    // you only need to use this property (`c` stands for `CString`)
    // e.g.: String(format: "%s", "test".c)
    var c: CString {
        return CString(string: self)
    }
}

Usage

let stringToFormat1 = "test1"
let stringToFormat2 = "test2"
// note the `.c` at the end of each variable/literal
let formattedString = String(format: "%s %s %s", stringToFormat1.c, stringToFormat2.c, "test3".c)

Your specific problem

Using the second solution:

// note: it should be `-12` instead of `12` in order to pad the string to the right
var str = String(format: "%-12s - %s", "key".c, "value".c)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.