Search code examples
ioskotlin-multiplatformcoil

Does Coil 3 have native iOS support?


Does anyone have an example of how to use Coil3 for image loading in a native iOS in a KMM project? I have managed to set it up for android, using a common ImageLoader, but i can't figure out how to set it up for iOS. FYI this is not a Compose Multiplatform project, just shared Kotlin logic and native UI per platform

Googled examples, everything seems to point to compose multiplatform, but I need native implementation, if it exists.


Solution

  • Coil3 documentation says:

    On non-Android platforms, Coil uses Skiko to render images. Skiko is a set of Kotlin bindings that wrap the Skia graphics engine developed by Google.

    coil3.ImageLoader returns Deferred<ImageResult> which in it's order has coil3.Image field inside. In nonAndroid platforms coil3.Image interface contains method toBitmap() to get org.jetbrains.skia.Bitmap.

    So, get images in SwiftUI (get UIImage object) we have to solve two tasks:

    1. Resolve Deferred<ImageResult> to ImageResult. I would recommend to use this plugin:
    skie = { id = "co.touchlab.skie", version.ref = "skie"}
    

    Read more here: https://skie.touchlab.co/features/suspend

    1. Convert org.jetbrains.skia.Bitmap to UIImage

    Here is example:

    import coil3.Image
    import kotlinx.cinterop.ExperimentalForeignApi
    import kotlinx.cinterop.refTo
    import org.jetbrains.skia.Bitmap
    import org.jetbrains.skia.Image.Companion.makeFromBitmap
    import platform.CoreFoundation.CFDataCreate
    import platform.CoreGraphics.CGColorRenderingIntent
    import platform.CoreGraphics.CGColorSpaceCreateDeviceRGB
    import platform.CoreGraphics.CGDataProviderCreateWithCFData
    import platform.CoreGraphics.CGImageAlphaInfo
    import platform.CoreGraphics.CGImageCreate
    import platform.CoreGraphics.kCGBitmapByteOrder32Little
    import platform.UIKit.UIImage
    
    @OptIn(ExperimentalForeignApi::class)
    fun convertImage(image: Image): UIImage? {
            val bitmap: Bitmap = image.toBitmap()
            val skikoImage = makeFromBitmap(bitmap)
            val skikoImagePixelMap = skikoImage.peekPixels()
            if (skikoImagePixelMap != null) {
                val cfDataRef = CFDataCreate(
                    allocator = null,
                    bytes = skikoImagePixelMap.buffer.bytes.asUByteArray().refTo(0),
                    length = skikoImagePixelMap.buffer.size.toLong()
                )
                 val cgImageRef = CGImageCreate(
                    width = skikoImage.width.toULong(),
                    height = skikoImage.height.toULong(),
                    bitsPerComponent = 8u,
                    bitsPerPixel = 32u,
                    bytesPerRow = (skikoImage.width * 4).toULong(),
                    space = CGColorSpaceCreateDeviceRGB(),
                    bitmapInfo = kCGBitmapByteOrder32Little or CGImageAlphaInfo.kCGImageAlphaPremultipliedFirst.value,
                    provider = CGDataProviderCreateWithCFData(cfDataRef),
                    decode = null,
                    shouldInterpolate = true,
                    intent = CGColorRenderingIntent.kCGRenderingIntentDefault
                )
                return UIImage(cGImage = cgImageRef)
            }
            return null
    }
    

    It worth to mention few things:

    1. Despite I successfully run it in iossimulator and checked with [jpg, jpeg, png, webp], please do not consider like ready to use code.
    2. kotlinx.cinterop @OptIn(ExperimentalForeignApi::class) annotation says that API still in experemental mode.
    3. Depends on how you will to work with suspended functions from Swift likely you will be limited by minimal iOS version 15+ (if you will use Task)

    I hope it will help you.