【问题标题】:Subclassing UIView from Kotlin Native从 Kotlin Native 继承 UIView
【发布时间】: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?

【问题讨论】:

    标签: ios swift objective-c kotlin kotlin-native


    【解决方案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 部分是先编译的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-07-30
      • 2017-10-28
      • 1970-01-01
      • 1970-01-01
      • 2015-03-06
      • 1970-01-01
      • 2019-01-12
      相关资源
      最近更新 更多