从 Kotlin Native 继承 UIView

Posted

技术标签:

【中文标题】从 Kotlin Native 继承 UIView【英文标题】:Subclassing UIView from Kotlin Native 【发布时间】:2021-11-14 20:34:13 【问题描述】:

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

通常,UIView 的drawRectobjective-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】:

所以我们设法让它发挥作用。

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 类

该类从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) 
    



【讨论】:

请注意,您可以使用cValue CGRectZero 代替CGRectMake(.0, .0, .0, .0) 感谢@PhilipDukhov。 用@ObjCAction 注释来注释layoutSubviews 会不会更容易?【参考方案2】:

上面的答案很棒,在我需要重写 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 部分是先编译的。

【讨论】:

以上是关于从 Kotlin Native 继承 UIView的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin-native 执行命令并获取输出

使用协程的 Kotlin/Native 多线程

Kotlin/Native 无法导入 io.ktor.network.selector.ActorSelectorManager

如何使用 Kotlin 同时定位 JVM/Native 和 Android

Kotlin Native 如何将 ByteArray 转换为 String?

从 Kotlin Psi API 检索继承类全名