Closed soywiz closed 3 years ago
Default initializer here is not designated even in Swift.
Your Swift code snippet is not equivalent to Kotlin one: it doesn't delegate to super.init
because it doesn't declare any initializer: by default Swift inherits all initializers from super class.
If you add init() { super.init() }
to your Swift class, then you will see that Swift doesn't allow to call super.init()
too.
Please delegate to a proper designated initializer, e.g. initWithFrame:
.
In the case this helps someone else:
I find it a bit strange. Not sure how swift generates the default init
from NSResponder
. Maybe because the failable initializers?
Now I somehow understand how it is like that. But I guess other people will have similar issues. Will try to port code from Swift to K/N that do not provide other initializers, and will stumble upon this problem.
Would be nice if inherited K/N classes from Obj-C/Swift autogenerate proper constructors when no default constructor is provided. Though not sure how complex would be do that. Or at least a quickfix to generate all the constructors automatically, and detect the error at editing time. Specially I'm not sure about how the default init
from NSResponder
is generated for MyOpenglView
, so couldn't reproduce for other classes without manually instantiating the required arguments. Maybe they are providing default values like zero-filled memory for value types? No clue here.
I find it a bit strange. Not sure how swift generates the default
init
fromNSResponder
.
It is inherited from NSOpenGLView
, which inherits it from NSView
, which inherits it from NSResponder
.
In the case of UIViewController
from iOS UIKit, passing a default NSCoder()
instance results in a crash, and adding a no-args constructor override results in this same compilation error (that IntelliJ doesn't catch).
Is there a workaround to be able to subclass UIViewController
, something very basic for making an iOS app?
Here's an example of a subclass that compiles and can be instantiated if referenced from a storyboard:
@ExportObjCClass
class OldViewController : UIViewController {
@OverrideInit
constructor(coder: NSCoder) : super(coder)
@ObjCOutlet
lateinit var label: UILabel
@ObjCOutlet
lateinit var textField: UITextField
@ObjCOutlet
lateinit var button: UIButton
@ObjCAction
fun buttonPressed() {
label.text = "Konan says: 'Hello, ${textField.text}!'"
}
}
However, if you want to be able to instantiate it from code, breaking free from storyboards, you're out of luck as adding the following results in the compilation error of that issue:
@OverrideInit
constructor() : super()
https://github.com/JetBrains/kotlin-native/issues/2010#issuecomment-418986603
Please delegate to a proper designated initializer, e.g.
initWithFrame:.
@SvyatoslavScherbina How? I don't see how I'd translate that to Kotlin.
I found a way to make my UIViewController
subclass usable from Kotlin! There's a third constructor I didn't notice that takes 2 nullable parameters, and passing them null works just fine, and can be set in default parameters from Kotlin! Here's an example snippet:
ExportObjCClass
class MyViewController @OverrideInit constructor(
nibName: String? = null,
bundle: NSBundle? = null
) : UIViewController(nibName, bundle) {
init {
title = "MyViewController"
}
private val button: UIButton = UIButton.buttonWithType(UIButtonTypeSystem).apply {
setTitle("Finally!", UIControlStateNormal)
view.addSubview(this)
setFrame(CGRectMake(x = 10.0, y = 25.0, width = 125.0, height = 75.0))
}
suspend fun run() {
var counter = 0
while (true) {
button.awaitOneClick() // Custom suspending extension function
button.setTitle("Clicked ${++counter} times!", UIControlStateNormal)
}
}
}
Please note that you don't need @ExportObjCClass
and @OverrideInit
if you instantiate MyViewController
manually.
Btw,
init {
title = "MyViewController"
}
How would you do this in Swift without overriding initializers?
That's right, here's a smaller version that works:
private class MyNavViewController : UINavigationController(null, bundle = null) {
// named argument is needed here to disambiguate overload resolution from the navigation UIController
...
}
As for the init
block, I think Kotlin wins on that one, Swift doesn't seem to be able to do it without overriding the initializers, although I am not so familiar with it.
Trying to make the same thing using appkit NSViewController to build a desktop app without storyboard but this code lead to a crash
class ViewController : NSViewController(null, bundle = null) { }
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[NSNib _initWithNibNamed:bundle:options:] could not load the nibName: kobjc2 in bundle (null).'
Can't get rid of Unable to call non-designated initializer as super constructor
either, and i don't know what to do with disableDesignatedInitializerChecks
to try as it's a simple program without any .def file
Any advices would be apreciated :(
i got this error, when i try to inherit IOS WKWebView. Does K/N currently not support this case?
We already discussed @roths question in the Kotlin Slack, so posting it just for the record. One can inherit from WKWebView
like this:
import kotlinx.cinterop.*
import platform.WebKit.*
import platform.Foundation.NSZeroRect
class myWebView : WKWebView(frame = NSZeroRect.readValue(), configuration = WKWebViewConfiguration())
@SBNTT: Trying to make the same thing using appkit NSViewController to build a desktop app without storyboard but this code lead to a crash [...]
It's a bit hidden, but here's what it says in the [“Discussion”] section of [NSViewController
]'s [init(nibName:bundle:)
] method reference:
If you pass in
nil
fornibNameOrNil
, [nibName
] returnsnil
and [loadView()
] throws an exception; in this case you must set [view
] before [view
] is invoked, or override [loadView()
].
So, one way to work around the issue is:
import platform.AppKit.NSView
import platform.AppKit.NSViewController
import platform.AppKit.NSWindow
@Suppress("FunctionName")
fun NSViewController(view: NSView) =
object : NSViewController(nibName = null, bundle = null) {
init {
this.view = view
}
}
fun example(view: NSView): NSWindow =
NSWindow.windowWithContentViewController(NSViewController(view))
[NSViewController
]:
https://developer.apple.com/documentation/appkit/nsviewcontroller
[init(nibName:bundle:)
]:
https://developer.apple.com/documentation/appkit/nsviewcontroller/1434481-init
[“Discussion”]: https://developer.apple.com/documentation/appkit/nsviewcontroller/1434481-init#discussion
[nibName
]:
https://developer.apple.com/documentation/appkit/nsviewcontroller/1434472-nibname
[loadView()
]:
https://developer.apple.com/documentation/appkit/nsviewcontroller/1434405-loadview
[view
]:
https://developer.apple.com/documentation/appkit/nsviewcontroller/1434401-view
Although this error report is not a bug in Kotlin, it still can be improved. Follow this issue: https://youtrack.jetbrains.com/issue/KT-47992
This is valid swift code. But doesn't work on Kotlin/Native:
Related issue: https://github.com/JetBrains/kotlin-native/issues/1995 This class is from the
platform.AppKit
so cannot do the trick ofdisableDesignatedInitializerChecks