Swift:格式化字符串宽度

5

我希望做的事在C/C++、Java和其他许多语言中都很简单。我想做的就是能够指定一个字符串的宽度,就像这样:

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

这将创建一个宽度为15个字符的字段。我已经做了很多谷歌搜索。我尝试使用COpaquepointer以及各种方式使用String(format:,但都没有成功。任何建议都将不胜感激。我在谷歌搜索时可能会错过一些东西。

7个回答

3
你可以使用 withCString 快速将字符串转换为字节数组(技术上是一个 UnsafePointer<Int8>):
let str = "Hello world"
let formatted = str.withCString { String(format: "%-15s", $0) }

print("'\(formatted)'")

你会得到 '("%-15s", 0x00007fcf287884b0)' 🙂 - user3441734
根据http://www.runswiftlang.com的说法并非如此。我现在离Xcode很远,所以无法测试。 - Code Different
如果它不起作用,我会等待另一个确认。 如果确实没有作用,我会将其删除。 - Code Different
如果您导入Foundation,它应该按照您的预期工作。 - user3441734

2
你最好自己做。
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               "

如果您需要“更通用”的东西

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)

打印

ABC
FGH
ABCDEFGH--
--ABCDEFGH

1
问题在于Swift字符串具有可变大小的元素,因此“15个字符”是含糊不清的。这对于简单字符串来说是一种挫败感,但在处理表情符号、区域标识符、连字等时,使语言更加精确。

您可以将Swift字符串转换为C字符串并使用常规格式化程序(请参见Santosh的答案)。处理字符串的“Swift”方式是从Character集合的起始索引开始,并向前移动N次。例如:

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"

如果您想强制填充,可以采用类似以下的方法:
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"

如果字符串长度小于15个字符,是否需要填充? - Alex Cauthen
@AlexCauthen 不行,因为你会超过字符串中的字符数而导致崩溃。这只是一个关于在Swift中如何处理字符串长度的微不足道的例子。 - Aaron Brager
1
@AlexCauthen 我更新了我的答案,加入了一个字符串扩展,可能可以实现你想要的功能。 - Aaron Brager

0
你试过这个吗?
let string1 = "string1"
let string2 = "string2"
let formattedString = String(format: "%-15s - %s",
             COpaquePointer(string1.cStringUsingEncoding(NSUTF8StringEncoding)!),
             COpaquePointer(string2.cStringUsingEncoding(NSUTF8StringEncoding)!)
)

print(formattedString)
//string1         - string2

https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html - user3441734
是的,我相信我得到了一个 "exc_bad_instruction" 错误。现在我正在编写一个自定义函数来为我执行所需的操作,直到找到更好的解决方案。 - Alex Cauthen

0

一方面,%@ 用于格式化字符串对象:

import Foundation

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

但它不支持宽度修饰符:

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

仍将打印未填充的文本:

"Hello\n"

然而,似乎有一个修饰符 %s 可以与 CStrings 一起使用:

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

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

输出:

"Hello          \n"

一个好处是你可以在NSLog中使用相同的格式规范:

NSLog("%-15s", cstr!)

'NSString' 没有 'utf8String' 成员。 - user3441734
是的,请尝试使用UTF8String,似乎在iOS10中它被替换为小写前缀。 - yurgis

0

我们现在已经有了很多有趣的答案。谢谢大家。我写了以下内容:

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

    var newString: String = stringToChange
    newString = newString.stringByPaddingToLength(width, withString: " ", startingAtIndex: 0)
    return newString
}

几个问题:(1)您正在桥接到NSString,因此您正在失去Swift字符串的好处,(2)为什么不直接调用stringByPaddingToLength而编写此代码? (3): String是不必要的,(4)为什么只分配给newString,然后立即覆盖该值? - Aaron Brager
我没有意识到它与NSString相关联。谢谢你告诉我。如果我创建一个函数,我可以在任何地方调用它,以避免代码重复。我习惯明确定义我的类型,所以这真的是个风格问题。在Swift 3.0及更高版本中,函数参数被定义为let常量,因此需要一个本地变量来进行修改。 - Alex Cauthen

0
为了补充上面“Code Different”(谢谢!)在2016年6月29日的回答,并允许编写类似于"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) }
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接