I'm trying to make a property wrapper for accessing the string value of a control owned by a view controller. So far I have this:
@propertyWrapper struct ControlStringValue<Container> where Container: AnyObject
{
private let container: Container
private let keyPath: ReferenceWritableKeyPath<Container, NSControl?>
var wrappedValue: String
{
get { return container[keyPath: keyPath]?.stringValue ?? "" }
set { container[keyPath: keyPath]?.stringValue = newValue }
}
}
class MyViewController: NSViewController
{
@IBOutlet var someControl: NSControl!
@ControlStringValue(container: self, keyPath: \MyViewController.someControl) var text: String
}
But I get this error:
error: Sandbox.playground:20:53: error: cannot convert value of type 'ReferenceWritableKeyPath<MyViewController, NSControl?>' to expected argument type 'ReferenceWritableKeyPath<_, NSControl?>'
@ControlStringValue(container: self, keyPath: \MyViewController.someControl) var text: String
So it seems like it's not fully inferring the generic type. What else can I do to nudge the compiler?
An alternative would be to store the control in the wrapper later, once the view controller is fully initialized, but that doesn't seem as clean.
There are two issues:
- The synthesised
init
is private
- You're passing
self
in a property initializer
You're getting a bogus error because of the first issue. If you remove container
, you'll get the actual error:
'ControlStringValue' initializer is inaccessible due to 'private' protection level
Once you've fixed that, I think there's two ways to fix (2):
-
You can use the _enclosingInstance
static subscript, but keep in mind it's a prototype and can change behaviour or stop working as it's not official yet.
-
You can assign self
after the view loads.
Here's how you can do (2):
@propertyWrapper
struct ControlStringValue<Container: AnyObject> {
private var container: Container?
private let keyPath: ReferenceWritableKeyPath<Container, NSControl?>
init(keyPath: ReferenceWritableKeyPath<Container, NSControl?>) {
self.keyPath = keyPath
}
var wrappedValue: String {
get { return container?[keyPath: keyPath]?.stringValue ?? "" }
set { container?[keyPath: keyPath]?.stringValue = newValue }
}
var projectedValue: Container? {
get { container }
set { container = newValue }
}
}
final class MyViewController: NSViewController {
@IBOutlet var someControl: NSControl!
@ControlStringValue(keyPath: \MyViewController.someControl) var text: String
override func viewDidLoad() {
super.viewDidLoad()
$text = self
}
}
Thanks! I think I'll go for the second option until enclosingInstance
is official.
1 Like