2

I searched for an answer the entire day but nothing really came close to answering my issue. I am trying to use stringWithFormat in Swift but while using printf format strings. The actual issue I have is with the %s. I can't seem to get to the original string no matter how I try this.

Any help would be much much appreciated (or workarounds). Things I already did: tried all available encodings for the cString, tried creating an ObjC function to use for this, but when I passed the arguments from Swift I ran into the same strange issue with the %s, even if when hardcoded in the ObjC function body it appears to print the actual correct String.

Please find bellow the sample code.

Many thanks!

var str = "Age %2$i, Name: %1$s"
let name = "Michael".cString(using: .utf8)!
let a = String.init(format: str, name, 1234)

Expected result is quite clear I presume, however I get something like this instead of the correct name:

"Age 1234, Name: ÿQ5"
Razvan Soneriu
  • 587
  • 3
  • 7
  • 23

2 Answers2

3

Use withCString() to invoke a function with the C string representation of a Swift string. Also note that %ld is the correct format for a Swift Int (which can be a 32-bit or 64-bit integer).

let str = "Age %2$ld, Name: %1$s"
let name = "Michael"

let a = name.withCString { String(format: str, $0, 1234) }
print(a) // Age 1234, Name: Michael

Another possible option would be to create a (temporary) copy of the C string representation (using the fact a Swift string is automatically converted to a C string when passed to a C function taking a const char * parameter, as explained in String value to UnsafePointer<UInt8> function parameter behavior):

let str = "Age %2$ld, Name: %1$s"
let name = "Michael"

let nameCString = strdup(name)!

let a = String(format: str, nameCString, 1234)
print(a)

free(nameCString)

I assume that your code does not work as expected because name (which has type [CChar] in your code) is bridged to an NSArray, and then the address of that array is passed to the string formatting method.

Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • I am not sure about why it's not working but it's driving me mad. Especially since I need to be able to add more than one name. Actually the number of arguments is variable. So i expanded on your answer and I tried this: var firstPointer: UnsafePointer? name.withCString { firstPointer = $0 } let b = String.init(format: str, firstPointer!, 1234). Which it doesn't work... it prints again random stuff. So your solution even if answers my question, it doesn't solve my problem :( – Razvan Soneriu Mar 23 '17 at 19:10
  • @RazvanSoneriu: That fails because the pointer is valid only within the closure. – Martin R Mar 23 '17 at 19:12
  • Yeah, that may very well be. So basically my issue still remains. However thanks for your kind help. Maybe I'll get to something from here – Razvan Soneriu Mar 23 '17 at 19:14
  • @RazvanSoneriu: You can nest withCString if you have two (or a fixed number of) strings. There may be other solutions/workarounds (perhaps like this http://stackoverflow.com/a/29469618/1187415?), but that depends on your actual problem. What do you mean by "variable number of arguments"? A format string usually expects a fixed number of arguments. – Martin R Mar 23 '17 at 19:20
  • Well, it's actually a variadic function. And that's what I want to have, a variadic function which knows how to handle printf arguments. So basically I should be able to call the same function and pass it whatever (how many) arguments I need for that particular case. So in my example you can add 3 more (for a different case) arguments both in str and in the arguments supplied to the function and it should return the final string – Razvan Soneriu Mar 23 '17 at 19:28
  • @RazvanSoneriu: I have added another approach, does that help? – Martin R Mar 23 '17 at 19:31
  • I will definitely check your suggested answer, it might have the solution I'm looking for. Your help is very very much appreciated, thanks a lot! – Razvan Soneriu Mar 23 '17 at 19:31
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/138867/discussion-between-razvan-soneriu-and-martin-r). – Razvan Soneriu Mar 23 '17 at 19:34
0

Use "%1$@" instead of "%1$s", and don't use the cString call.

This works for me:

var str = "Age %2$i, Name: %1$@"
let name = "Michael"
let a = String.init(format: str, name, 1234)
Lou Franco
  • 87,846
  • 14
  • 132
  • 192