6

I am trying to create a String with an array of Strings, I would expect this to work:

let format = "%@ and %@!"
let str1 = "bla"
let str2 = "blob"

private func formatItNicely(format: String, substrings: [String]) -> String {
    return String(format: format, arguments: substrings)
}

let result = formatItNicely(format, substrings: [str1, str2])

but I am getting fatal error: can't unsafeBitCast between types of different sizes.

I have seen this and this and this questions (and many more), but I still don't know how to accomplish what I am trying to do.

4
  • "I would expect this to work" I don't see why you'd expect that. An array is not a variadic. The problem is that Swift has no "splat" operator. You cannot turn an array into a variadic. Commented Jul 21, 2016 at 17:36
  • @matt can I define my function somehow different to be able to do that? If I define the parameter substrings as String... I get the same result Commented Jul 21, 2016 at 17:36
  • 1
    You could just manually substitute each string for each %@ in order, without using String(format:). Otherwise you would have to get down and dirty with the C variadic stuff. Commented Jul 21, 2016 at 17:38
  • 1
    Good discussion here: drivenbycode.com/the-missing-apply-function-in-swift Commented Jul 21, 2016 at 17:42

4 Answers 4

7

String(format:, arguments: ) expects a [CVarArgType] as the second parameter:

let format = "%@ and %@!"
let str1 = "bla"
let str2 = "blob"

private func formatItNicely(format: String, substrings: [CVarArgType]) -> String {
    return String(format: format, arguments: substrings)
}

let result = formatItNicely(format, substrings: [str1, str2])
print(result) // bla and blob!

But note that this can crash (or give unexpected output) if the format specifiers do not match the actual arguments, the formatting functions do not (cannot) check that.

Sign up to request clarification or add additional context in comments.

1 Comment

I ended up doing substrings.map { $0 as CVarArgType } when I call String(format:,arguments:). This way the substrings do not have to be CVarArgType
3

(Nowadays) you do not have to do anything of the suggested above.

In Swift5 you can just pass an array of Strings as arguments to String.init(format:arguments:).

Therefore, the following is working fine for me without any typecasting/mapping, etc.

let formatString = "First one: %1$@, second one: %2$@"
label.text = String(format: formatString, arguments: ["hello", "blabla"])

Comments

2

Or you can use the variadic rendition:

private func formatItNicely(format: String, _ arguments: CVarArgType...) -> String {
    return String(format: format, arguments: arguments)
}

Calling it like so:

let format = "%@ and %@!"
let str1 = "bla"
let str2 = "blob"

let result = formatItNicely(format, str1, str2)

Comments

1

Since you are not doing anything very interesting, you don't actually need the built-in format string logic. You can just do the substitution yourself:

func join(_ arr : [String], withFormatString s: String) -> String {
    var s = s
    for str in arr {
        if let r = s.range(of: "%@") {
            s.replaceSubrange(r, with: str)
        }
    }
    return s
}
let result = join([str1,str2], withFormatString:format)

2 Comments

Nitpicking: This can give wrong results if a string in arr contains %@ :)
@MartinR And in fact I was going to point out that since he isn't depending on the built-in string formatting he can provide his own extremely unlikely substitution marker. I like to use things like five upside-down exclamation marks in a row.

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.