从 Kotlin Native 继承 UIView

Subclassing UIView from Kotlin Native

UIKit 旨在通过子类和重写方法使用。

一般UIView的drawRect objective-C方法在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)
    }
}

不幸的是,Kotlin 中的 UIKit 导入将这些函数定义为无法覆盖的扩展函数。

有人通过自定义 cinterop 配置成功地从 Kotlin 继承了 UIView 吗?

所以我们成功了。

1.在build.gradle.kts

中添加一个cinterop配置任务
kotlin {
    android()
    ios {
        binaries {
            framework {
                baseName = "shared"
            }
        }
        compilations.getByName("main") {
            val uikit by cinterops.creating {
            }

        }
    }

2. 添加一个 `src/nativeinterop/cinterop/uikit.def` 文件。

package = demo.cinterop
language = Objective-C
---

#import <Foundation/Foundation.h>
#import <UIKit/UIView.h>

@protocol UIViewWithOverrides
- (void) drawRect:(CGRect)aRect;
- (void) layoutSubviews;
@end

3。创建自定义 UIView class

class从UIKit扩展了UIView,实现了之前创建的UIViewWithOverridesProtocol(后缀自动添加)

package demo

import demo.cinterop.UIViewWithOverridesProtocol
import kotlinx.cinterop.*
import platform.CoreGraphics.*
import platform.UIKit.*

@ExportObjCClass
class MyView() : UIView(frame = CGRectMake(.0, .0, .0, .0)), UIViewWithOverridesProtocol {

    override fun layoutSubviews() {
        println("layoutSubviews")
        setNeedsDisplay()
    }

    override fun drawRect(aRect: CValue<CGRect>) {
        val rectAsString = aRect.useContents {
            "" + this.origin.x + ", " + this.origin.y + ", " + (this.origin.x +this.size.width) + ", " + (this.origin.y +this.size.height)
        }
        println("drawRect:: Rect[$rectAsString]")

        val context: CPointer<CGContext>? = UIGraphicsGetCurrentContext()
        CGContextSetLineWidth(context, 2.0)
        val components = cValuesOf(0.0, 0.0, 1.0, 1.0)
        CGContextSetFillColor(context, components)
        val square = CGRectMake(100.0, 100.0, 200.0, 200.0)
        CGContextFillRect(context, square)

    }

}

fun createMyView(): UIView = MyView()

4. 从 Swift

使用它
struct ChartView: View {

    var body: some View {

        VStack {
            Text("Chart View")
            MyView()
                .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
        }
    }

}

struct ChartView_Previews: PreviewProvider {
    static var previews: some View {
        ChartView()
    }
}

struct MyView: UIViewRepresentable {

    func makeUIView(context: Context) -> UIView {
        UIChartViewKt.createMyView()
    }

    func updateUIView(_ uiView: UIView, context: Context) {
    }

}

上面的答案很棒,在我需要重写 updateConstraints() 之前它对我很有帮助——它必须调用 super.updateConstraints()。如果没有它,我就会遇到运行时错误,而且我找不到如何通过 Kotlin <-> Swift 互操作来执行该调用(现在我有理由相信这真的不可能)。

因此,我放弃了尝试在 Swift 中对自定义 UIView 进行子类化,而只专注于从 Kotlin/Native 实际实例化它(这样就很容易将数据传递给它需要):

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 ...
    }
}

并实现了一个工厂函数来实例化它:

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

然后在应用程序启动的早期,我将这个工厂函数传递给 Kotlin/Native 代码,如下所示:

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

在项目的 Kotlin 部分(实际上是在 Swift 部分之前编译的),它看起来像这样:

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
        }
    }

当我想在Kotlin代码中实例化自定义UIView时,我只需要调用:

val customView = customViewFactory(this)

然后我可以根据需要在 Kotlin 部分使用此 customView,即使 Kotlin 部分是先编译的。