How to pass vararg parameters from Kotlin common code to iOS functions?

Viewed 664

I have a Kotlin multi-platform project with two targets: Android and iOS. I'm trying to expose a JVM-like String.format function in my common code

expect fun format(format: String, vararg params: Any?)

On the Android side, I can simply use

actual fun format(format: String, vararg params: Any?) {
    return if(params.isEmpty()) {
        format
    } else {
        java.lang.String.format(Locale.getDefault(), format, *params)
    }
}

However, on the iOS side, using the equivalent approach

actual fun format(format: String, vararg params: Any?) {
    return if(params.isEmpty()) {
        format
    } else {
        NSString.stringWithFormat(format, *params)
    }
}

results in a compiler error:

When calling variadic Objective-C methods spread operator is supported only for *arrayOf(...)

I found and read this issue on the Kotlin/Native Github but this seems to only solve calls with explicit parameters.

So how can I pass vararg parameters along to Objective-C functions? Or is it really impossible? Seems like I'm missing something obvious.

2 Answers

From my answer here.

The feature is missing as we read in the JB offical answer from Svyatoslav Scherbina and we could follow this issue here: KT-42925

As an awful workaround, I was suggesting something like that (WARNING: not exhaustive, without many index count checks...)

import platform.Foundation.NSString
import platform.Foundation.stringWithFormat

actual typealias StringType = String

actual fun String.format(format: String, vararg args: Any?): StringType? {
    var returnString = ""
    val regEx = "%[\\d|.]*[sdf]|[%]".toRegex()
    val singleFormats = regEx.findAll(format).map {
        it.groupValues.first()
    }.asSequence().toList()
    val newStrings = format.split(regEx)
    for (i in 0 until args.count()) {
        val arg = args[i]
        returnString += when (arg) {
            is Double -> {
                NSString.stringWithFormat(newStrings[i] + singleFormats[i], args[i] as Double)
            }
            is Int -> {
                NSString.stringWithFormat(newStrings[i] + singleFormats[i], args[i] as Int)
            }
            else -> {
                NSString.stringWithFormat(newStrings[i] + "%@", args[i])
            }
        }
    }

    return returnString
}

But look if it could be a valid workaround for you.

enter image description here

I ran into this and solved it a slightly different way. It has the limitation of limiting the number of arguments but 10 is more than enough for my purposes.

actual fun String.format(vararg args: Any?) =
    // This ugly work around is because varargs can't be passed to Objective-C
    when (args.size) {
        0 -> NSString.stringWithFormat(this)
        1 -> NSString.stringWithFormat(this, args[0])
        2 -> NSString.stringWithFormat(this, args[0], args[1])
        3 -> NSString.stringWithFormat(this, args[0], args[1], args[2])
        4 -> NSString.stringWithFormat(this, args[0], args[1], args[2], args[3])
        5 -> NSString.stringWithFormat(this, args[0], args[1], args[2], args[3], args[4])
        6 -> NSString.stringWithFormat(this, args[0], args[1], args[2], args[3], args[4], args[5])
        7 -> NSString.stringWithFormat(this, args[0], args[1], args[2], args[3], args[4], args[5], args[6])
        8 -> NSString.stringWithFormat(this, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7])
        9 -> NSString.stringWithFormat(this, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8])
        10 -> NSString.stringWithFormat(this, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9])
        else -> throw IllegalStateException("ios String.format() can only accept up to 10 arguments")
    }
Related