もちろん、使ってますよね。
Swiftの文字列補完とは、以下のように、文字列リテラルに変数や式を埋め込んで文字列を生成できる便利な記法です。(公式ドキュメントから例を引用)
let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message is "3 times 2.5 is 7.5"
でも、Swiftの文字列補完は、Pythonのフォーマット済み文字列リテラルのように、書式を自由に指定できないです1。
Swiftで書式化しようとすると、String(format:_:)
を使うのが一般的かと思います。
先に引用した文字列補完をString(format:_:)
で書くと、以下のようになります。
let multiplier = 3
let message = String(format: "%d times 2.5 is %.1f", multiplier, Double(multiplier) * 2.5)
// message is "3 times 2.5 is 7.5"
でもやっぱり、Swiftの文字列補完で書式を指定したいと思い、使用頻度が高いと思われるいくつかのケースに限って、String.StringInterpolation
を拡張して、書式指定を作ってみました。
書式指定を どう使うの?
以下の使用例をお見せすれば、簡単にイメージをつかんでいただけると思います。
print("\(10)") //書式無し
//10
print("\(10, width: 20)") //幅20桁
// 10
print("\(11, width: 20, leadingZeros: true)") //幅20桁、前ゼロあり
//00000000000000000011
print("\(11, width: -20)") //マイナス幅で前ゼロありを指定可
//00000000000000000011
//16進数文字
print("\(toHex: 127)") //最小桁
//0x7f
print("\(toHex: 127, width: 8)") //前ゼロあり8桁
//0x0000007f
print("\(toHex: 127, width: 10, prefix: "", leadingZeros: false)")
// 7f
print("\(12.123)") //書式無し
//12.123
print("\(12.123, width: 20)") //幅20桁
// 12.12300
print("\(13.123, width: 20, precision: 3)") //幅20桁、少数以下3桁
// 13.123
print("\(14.123, width: 20, leadingZeros: true)") //幅20桁、前ゼロあり
//00000000000014.12300
print("\(14.123, width: -20)") //マイナス幅で前ゼロありを指定可
//00000000000014.12300
//整数型
print("\(withComma: 10_001)") //3桁カンマ、幅指定無し
//10,001
print("\(withComma: 10_002, width: 20)") //3桁カンマ、幅20桁
// 10,002
print("\(withComma: 10_002, width: 20, leadingZeros: true)") //3桁カンマ、幅20桁、前ゼロあり
//00,000,000,000,000,010,002
//前ゼロを指定すると、幅指定に`,`の文字数は含まないようだ
//浮動小数点型
print("\(withComma: 10_003.2345)") //浮動小数点3桁カンマ、幅指定無し
//10,003.23450
print("\(withComma: 10_004.2345, precision: 3)") //少数以下3桁
//10,004.234
print("\(withComma: 10_005.2345, width: 20, precision: 3)") //幅20桁、少数以下3桁
// 10,005.234
print("\(withComma: 10_006.2345, width: 20, leadingZeros: true)") //幅20桁、少数以下3桁、前ゼロあり
// 10,006.23450
//浮動小数点型の3桁カンマ付きは、前ゼロありを指定しても付きませんね
print("\(true)") //false: 0, true: 1
//1
print("\(false, label: (" No", "Yes"))") //任意のラベル (false: " No", true: "Yes")
// No
print("\(Date.now, dateFormat: "yyyy/MM/dd(E)HH:mm:ss.SSS")")
//2023/12/17(日)00:28:51.015
print("\(Date.now, dateFormat: "MM/dd/yyyy(E)HH:mm:ss.SSS", locale: "en_US", timeZone: "America/New_York")")
//12/16/2023(Sat)10:28:51.015
//上のどれにも当てはまらない場合は、直接formatを指定できる
//整数型、浮動小数点型、String型のみ指定可
print("\(10.123456, format: "%-20.10f+")")
//10.1234560000 +
print("\(11.123456, format: "%20.10f+")")
// 11.1234560000+
上のformat
だけあれば、用は足りると云えば足りますね。
実装コード
以下に、StringInterpolation拡張の実装コードを示す。引数とformat指定子を見ていただければ、内容は一目瞭然と思います。
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: any FixedWidthInteger, width: Int, leadingZeros: Bool = false) {
let leadingZeros = leadingZeros || width < 0, width = abs(width)
appendLiteral(String(format: "%\(leadingZeros ? "0" : "")\(width)d", value as! CVarArg))
}
mutating func appendInterpolation(_ value: any BinaryFloatingPoint, width: Int = 0, precision: Int = 5, leadingZeros: Bool = false) {
let leadingZeros = leadingZeros || width < 0, width = abs(width)
appendLiteral(String(format: "%\(leadingZeros ? "0" : "")" + (width > 0 ? "\(width).\(precision)f" : ".\(precision)f"), value as! CVarArg))
}
mutating func appendInterpolation(toHex value: any FixedWidthInteger, width: Int = 0, prefix: String = "0x", leadingZeros: Bool = true) {
let leadingZeros = leadingZeros || width < 0, width = abs(width)
appendLiteral(String(format: prefix + "%\(leadingZeros ? "0" : "")\(width)x", value as! CVarArg))
}
mutating func appendInterpolation(withComma value: any FixedWidthInteger, width: Int = 0, leadingZeros: Bool = false) {
let leadingZeros = leadingZeros || width < 0, width = abs(width)
appendLiteral(String.localizedStringWithFormat("%\(leadingZeros ? "0" : "")" + (width > 0 ? "\(width)d" : "d"), value as! CVarArg))
}
mutating func appendInterpolation(withComma value: any BinaryFloatingPoint, width: Int = 0, precision: Int = 5, leadingZeros: Bool = false) {
let leadingZeros = leadingZeros || width < 0, width = abs(width)
appendLiteral(String.localizedStringWithFormat("%\(leadingZeros ? "0" : "")" + (width > 0 ? "\(width).\(precision)f" : ".\(precision)f"), value as! CVarArg))
}
mutating func appendInterpolation(_ value: Bool, label: (false: String, true: String) = ("0", "1")) {
appendLiteral(value ? label.true : label.false)
}
mutating func appendInterpolation(_ date: Date, dateFormat: String, locale: String = "ja_JP", timeZone: String = "Asia/Tokyo") {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: locale)
formatter.timeZone = TimeZone(identifier: timeZone)
formatter.dateFormat = dateFormat
appendLiteral(formatter.string(from: date))
}
mutating func appendInterpolation(_ value: Any, format: String) {
guard (value is any FixedWidthInteger) || (value is any BinaryFloatingPoint) || (value is any StringProtocol) else { return }
appendLiteral(String(format: format, value as! CVarArg))
}
}
以上
普段使いそうなパターンは網羅したつもりです。
おまけ
文字列補完とは直接関係しませんが、DateFormatter
で指定できるtimeZone
とlocale
を確認する方法は、以下のとおりです。何百もあるんですね。
import Foundation
print(TimeZone.knownTimeZoneIdentifiers)
[
"Africa/Abidjan",
"Africa/Accra",
"Africa/Addis_Ababa",
"Africa/Algiers",
: : :
"Pacific/Tongatapu",
"Pacific/Truk",
"Pacific/Wake",
"Pacific/Wallis"
]
import Foundation
print(Locale.availableIdentifiers.sorted())
[
"af",
"af_NA",
"af_ZA",
"agq",
"agq_CM",
: : :
"zh_Hant_CN",
"zh_Hant_HK",
"zh_Hant_MO",
"zh_Hant_TW",
"zu",
"zu_ZA"
]
-
SwiftUIのTextなどを除く ↩