'Subclassing UIView from Kotlin Native

UIKit is designed to be used through subclasses and overridden methods.

Typically, the drawRect objective-C method of UIView is implemented like this in SWIFT:

import UIKit
import Foundation

class SmileView: UIView {
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        let smile = ":)" as NSString
        smile.draw(in: rect, withAttributes: nil)
    }
}

Unfortunately, the UIKit import in Kotlin defines these functions as extensions function that cannot be overridden.

Did anybody succeed in subclassing an UIView from Kotlin through a custom cinterop configuration?



Solution 1:[1]

The above answer is awesome, and it served me pretty well until I needed to override updateConstraints() - which has to call super.updateConstraints(). Without that, I was getting runtime errors, and I found no way how to do that call via the Kotlin <-> Swift interop (and now I'm reasonably sure it's really not possible).

So instead, I gave up on trying to subclass the custom UIView in Swift, and only focused on actually instantiating it from Kotlin/Native (so that it is easy to pass it the data it needs):

class CustomView : UIView {

    /* Data we need to use from the Kotlin Code */
    lazy var kotlinClass: KotlinClass? = nil

    ... init etc. ...

    override func updateConstraints() {
        ... my stuff ...
        super.updateConstraints()
    }

    override func draw(_ rect: CGRect) {
        ... call the kotlinClass' methods as you need ...
    }
}

And implemented a factory function to instantiate it:

func customViewFactory(kotlinClass: KotlinClass) -> UIView {
    return CustomView(kotlinClass: kotlinClass)
}

Then early during the app startup, I pass this factory function to the Kotlin/Native code like this:

KotlinClass.Companion.shared.setCustomViewFactory(factory: customViewFactory(kotlinClass:))

In the Kotlin part of the project (that is actually compiled before the Swift part), it looks like this:

class KotlinClass {

    companion object {
        /* To be used where I want to instantiate the custom UIView from the Kotlin code. */
        lateinit var customViewFactory: (kotlinClass: KotlinClass) -> UIView

        /* To be used early during the startup of the app from the Swift code. */
        fun setCustomViewFactory(factory: (kotlinClass: KotlinClass) -> UIView) {
            customViewFactory = factory
        }
    }

When I want to instantiate the custom UIView in the Kotlin code, I just call:

val customView = customViewFactory(this)

And then I can work with this customView as I need in the Kotlin part, even though the Kotlin part is compiled first.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jan Holesovsky