IBInspectable not recognized when using NS_REFINED_FOR_SWIFT - objective-c

I have a custom Objective-C class that contains two CGFloats:
#property (nonatomic, assign) IBInspectable CGFloat minimumConstant NS_REFINED_FOR_SWIFT;
#property (nonatomic, assign) IBInspectable CGFloat maximumConstant NS_REFINED_FOR_SWIFT;
Both are marked as IBInspectable. In the initializer of the class I set both bot NaN as I need to represent a difference between 0 and nothing. To make the class nice to use from Swift I marked both properties as NS_REFINED_FOR_SWIFT and created a Swift extension with the refined implementation:
#IBInspectable public var minimumConstant: CGFloat? {
get {
let constant = __minimumConstant
return constant.isNaN ? nil : constant
}
set {
if let constant = newValue {
__minimumConstant = constant
return
}
__minimumConstant = CGFloat.nan
}
}
#IBInspectable public var maximumConstant: CGFloat? {
get {
let constant = __maximumConstant
return constant.isNaN ? nil : constant
}
set {
if let constant = newValue {
__maximumConstant = constant
return
}
__maximumConstant = CGFloat.nan
}
}
This works great when using my class from code. When using Swift NaN will be mapped to nil and I can use optional unwrapping as usual.
The issue is that as soon as I add NS_REFINED_FOR_SWIFT the Interface Builder will no longer recognize my properties as IBInspectable and does not show them.
Is this a known issue? Is there any workaround for this?

Related

public var of an Objective C enum type inside a Swift class not accessible from another Objective C class [duplicate]

I am trying to access a Swift class's Double? property from Objective-C.
class BusinessDetailViewController: UIViewController {
var lat : Double?
var lon : Double?
// Other elements...
}
In another view controller, I am trying to access lat like following:
#import "i5km-Swift.h"
#interface ViewController ()
#property (strong, nonatomic) BusinessDetailViewController *businessDetailViewController;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.businessDetailViewController = [[BusinessDetailViewController alloc] initWithNibName:#"BusinessDetailViewController" bundle:nil];
self.businessDetailViewController.lat = businessArray[1]; /* THIS GIVES ME AN ERROR */
}
and I am getting
Property 'lat' not found on object of type 'BusinessDetailViewController *'
Why can't I access this property? What am I missing?
Optional values of non-Objective-C types aren't bridged into Objective-C. That is, the first three properties of TestClass below would be available in Objective-C, but the fourth wouldn't:
class TestClass: NSObject {
var nsNumberVar: NSNumber = 0 // obj-c type, ok
var nsNumberOpt: NSNumber? // optional obj-c type, ok
var doubleVar: Double = 0 // bridged Swift-native type, ok
var doubleOpt: Double? // not bridged, inaccessible
}
In your Objective-C code, you'd access those first three properties like this:
TestClass *optTest = [[TestClass alloc] init];
optTest.nsNumberOpt = #1.0;
optTest.nsNumberVar = #2.0;
optTest.doubleVar = 3.0;
In your case, you can either convert lat and long to be non-Optional or switch them to be instances of NSNumber.
Note that you need to be careful about your Objective-C code if you take the second approach (switching lat and lon to non-optional properties of type NSNumber) -- while the Swift compiler will prevent you from assigning nil to non-optional properties, the Objective-C compiler has no qualms about allowing it, letting nil values sneak into your Swift code with no chance of catching them at runtime. Consider this method on TestClass:
extension TestClass {
func badIdea() {
// print the string value if it exists, or 'nil' otherwise
println(nsNumberOpt?.stringValue ?? "nil")
// non-optional: must have a value, right?
println(nsNumberVar.stringValue)
}
}
This works fine if invoked with values in both of the properties, but if nsNumberVar is set to nil from the Objective-C code, this will crash at runtime. Note that there is no way to check whether or not nsNumberVar is nil before using it!
TestClass *optTest = [[TestClass alloc] init];
optTest.nsNumberOpt = #1.0;
optTest.nsNumberVar = #2.0;
[optTest badIdea];
// prints 1, 2
optTest.nsNumberOpt = nil;
optTest.nsNumberVar = nil;
[optTest badIdea];
// prints nil, then crashes with an EXC_BAD_ACCESS exception
If your property is a Swift protocol type, just add #objc in front of it.
Example:
class Foo: UIViewController {
var delegate: FooDelegate?
...
}
#objc protocol FooDelegate {
func bar()
}
Optionals is a swift specific feature, not available in obj-c. Optional class instances work because a nil optional can be mapped to a nil value, but value types (int, floats, etc.) are not reference types, hence variable of those types don't store a reference, but the value itself.
I don't know if there's a solution - a possible workaround is creating non optional properties mapping the nil value to an unused data type value (such as -1 when representing an index, or 999999 for a coordinate):
class Test {
var lat : Double? {
didSet {
self._lat = self.lat != nil ? self.lat! : 999999
}
}
var lon : Double? {
didSet {
self._lon = self.lon != nil ? self.lon! : 999999
}
}
var _lat: Double = 99999999
var _lon: Double = 99999999
}
That should expose the _lat and _lon properties to obj-c.
Note that I have never tried that, so please let us know if it works.
[UInt? Int? or Double? properties] cannot be marked #objc because its type cannot be represented in Objective-C.
It is, however, possible to "wrap" them in a NSNumber like so :
class Foo {
var bar: Double?
}
// MARK: Objective-C Support
extension Foo {
/// bar is `Double?` in Swift and `(NSNumber * _Nullable)` in Objective-C
#objc(bar)
var z_objc_bar: NSNumber? {
get {
return bar as NSNumber?
}
set(value) {
bar = value?.doubleValue ?? nil
}
}
}

Named Obj-C Property Getters and Setters in Swift

I am looking for a general solution to access:
Obj-C named property getters and named property setters from Swift
Conform to an Objective-C #protocol with readonly properties
Similar to Creating an Objective-C equivalent Getter and Setter in Swift, which is closed, yet does not offer a satisfying answer.
Objective-C to Swift Properties Example:
I have an Objective-C protocol defined with two problematic properties, one with a custom getter isEnabled and another with a private setter exists.
#protocol SomeProtocol <NSObject>
#property (nonatomic, assign, getter = isEnabled) BOOL enabled;
#property (nonatomic, readonly) BOOL exists;
#end
How can I access these Objective-C properties from Swift?
This does not seem to work:
func isEnabled() -> Bool { return self.enabled }
and neither does:
var isEnabled:Bool {
get { }
set { }
}
Straight from the documentation:
Use the #objc(<#name#>) attribute to provide Objective-C names for properties and methods when necessary. For example, you can mark a property called enabled to have a getter named isEnabled in Objective-C like this:
SWIFT
var enabled: Bool {
#objc(isEnabled) get {
/* ... */
}
}
Named Objective-C Getter Property in Swift
var _enabled:Bool = false
var enabled:Bool {
#objc(isEnabled) get {
return self._enabled
}
set(newValue){
_enabled = newValue
}
}
readonly Objective-C Property in Swift
Either
var _exists:Bool = false
private(set) var exists:Bool {
get{
return self._exists
}
set(newValue){
self._exists = newValue
}
}
or
var _exists:Bool = false
var exists:Bool {
get{
return self._exists
}
}
and access self._exists directly since there is no setter.

Swift equivalent of lazy Property getter

What is the Swift equivalent of the following expression:
#property (strong, nonatomic) UIView *topView;
- (UIView *)topView {
...
}
Is it the following:
var topView: UIView {
get {
...
}
}
If the former is true, is there a way to define an external getter?
I think what you're asking is how to implement something similar to the following:
#property (nonatomic, strong) UIView *topView
- (UIView *)topView {
if (_topView == nil) {
_topView = //...
// configure _topView...
}
return _topView;
}
This lazy property getter is easy to achieve in Swift:
lazy var topView: UIView = {
let view = //...
// configure view...
return view
}()
This results in a read-only variable that is initialised only when first accessed. The Swift code you posted is a computed read-only property which is evaluated every time it is accessed.

Cannot access property on Swift type from Objective-C

I am trying to access a Swift class's Double? property from Objective-C.
class BusinessDetailViewController: UIViewController {
var lat : Double?
var lon : Double?
// Other elements...
}
In another view controller, I am trying to access lat like following:
#import "i5km-Swift.h"
#interface ViewController ()
#property (strong, nonatomic) BusinessDetailViewController *businessDetailViewController;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.businessDetailViewController = [[BusinessDetailViewController alloc] initWithNibName:#"BusinessDetailViewController" bundle:nil];
self.businessDetailViewController.lat = businessArray[1]; /* THIS GIVES ME AN ERROR */
}
and I am getting
Property 'lat' not found on object of type 'BusinessDetailViewController *'
Why can't I access this property? What am I missing?
Optional values of non-Objective-C types aren't bridged into Objective-C. That is, the first three properties of TestClass below would be available in Objective-C, but the fourth wouldn't:
class TestClass: NSObject {
var nsNumberVar: NSNumber = 0 // obj-c type, ok
var nsNumberOpt: NSNumber? // optional obj-c type, ok
var doubleVar: Double = 0 // bridged Swift-native type, ok
var doubleOpt: Double? // not bridged, inaccessible
}
In your Objective-C code, you'd access those first three properties like this:
TestClass *optTest = [[TestClass alloc] init];
optTest.nsNumberOpt = #1.0;
optTest.nsNumberVar = #2.0;
optTest.doubleVar = 3.0;
In your case, you can either convert lat and long to be non-Optional or switch them to be instances of NSNumber.
Note that you need to be careful about your Objective-C code if you take the second approach (switching lat and lon to non-optional properties of type NSNumber) -- while the Swift compiler will prevent you from assigning nil to non-optional properties, the Objective-C compiler has no qualms about allowing it, letting nil values sneak into your Swift code with no chance of catching them at runtime. Consider this method on TestClass:
extension TestClass {
func badIdea() {
// print the string value if it exists, or 'nil' otherwise
println(nsNumberOpt?.stringValue ?? "nil")
// non-optional: must have a value, right?
println(nsNumberVar.stringValue)
}
}
This works fine if invoked with values in both of the properties, but if nsNumberVar is set to nil from the Objective-C code, this will crash at runtime. Note that there is no way to check whether or not nsNumberVar is nil before using it!
TestClass *optTest = [[TestClass alloc] init];
optTest.nsNumberOpt = #1.0;
optTest.nsNumberVar = #2.0;
[optTest badIdea];
// prints 1, 2
optTest.nsNumberOpt = nil;
optTest.nsNumberVar = nil;
[optTest badIdea];
// prints nil, then crashes with an EXC_BAD_ACCESS exception
If your property is a Swift protocol type, just add #objc in front of it.
Example:
class Foo: UIViewController {
var delegate: FooDelegate?
...
}
#objc protocol FooDelegate {
func bar()
}
Optionals is a swift specific feature, not available in obj-c. Optional class instances work because a nil optional can be mapped to a nil value, but value types (int, floats, etc.) are not reference types, hence variable of those types don't store a reference, but the value itself.
I don't know if there's a solution - a possible workaround is creating non optional properties mapping the nil value to an unused data type value (such as -1 when representing an index, or 999999 for a coordinate):
class Test {
var lat : Double? {
didSet {
self._lat = self.lat != nil ? self.lat! : 999999
}
}
var lon : Double? {
didSet {
self._lon = self.lon != nil ? self.lon! : 999999
}
}
var _lat: Double = 99999999
var _lon: Double = 99999999
}
That should expose the _lat and _lon properties to obj-c.
Note that I have never tried that, so please let us know if it works.
[UInt? Int? or Double? properties] cannot be marked #objc because its type cannot be represented in Objective-C.
It is, however, possible to "wrap" them in a NSNumber like so :
class Foo {
var bar: Double?
}
// MARK: Objective-C Support
extension Foo {
/// bar is `Double?` in Swift and `(NSNumber * _Nullable)` in Objective-C
#objc(bar)
var z_objc_bar: NSNumber? {
get {
return bar as NSNumber?
}
set(value) {
bar = value?.doubleValue ?? nil
}
}
}

Objective-C supertype polymorphism

I'm fairly new to Objective-C and wondering if it's possible to type objects as their supertype without receiving compiler warnings when assigning them, or if there is a recognised way of achieving the same thing?
I realise that this is what type id is for but I have a base class with synthesized properties and if I try to use id I get a build error "request for member 'x' in something not a structure or union", presumably because dynamic typing is fine for sending messages to an object but not for accessing synthesized properties.
For example in Java I might have:
public abstract class A {
public function doSomething() {
//some func
}
}
public class B extends A {
public function doSomething() {
//override some func
}
}
public class C extends A {
public function doSomething() {
//override some func
}
}
//and in my main class:
A objB = new B();
A objC = new C();
//the purpose of all of this is so I can then do:
A objHolder;
objHolder = objB;
objHolder.doSomething();
objHolder = objC;
objHolder.doSomething();
I currently have the above working in Objective-C but with a compiler warning: "assignment from distinct Objective-C type"
OK, here is the Objective-C interfaces, I can add the implementations if you want. It's a composite pattern:
//AbstractLeafNode
#import <Foundation/Foundation.h>
#interface AbstractLeafNode : NSObject {
NSString* title;
AbstractLeafNode* parent;
}
#property (nonatomic, retain) NSString* title;
#property (nonatomic, retain) AbstractLeafNode* parent;
#end
//Page
#import "AbstractLeafNode.h"
#interface Page : AbstractLeafNode {
//there will be stuff here later!
}
#end
//Menu
#import "AbstractLeafNode.h"
#interface Menu : AbstractLeafNode {
NSMutableArray* aChildren;
}
- (void)addChild:(AbstractLeafNode *)node;
- (void)removeChild:(AbstractLeafNode *)node;
- (AbstractLeafNode *)getChildAtIndex:(NSUInteger)index;
- (AbstractLeafNode *)getLastChild;
- (NSMutableArray *)getTitles;
#end
// I'd then like to do something like (It works with a warning):
AbstractLeafNode* node;
Menu* menu = [[Menu alloc] init];
Page* page = [[Page alloc] init];
node = menu;
[node someMethod];
node = page;
[node someMethod];
// Because of the synthesized properties I can't do this:
id node;
// I can do this, but I suspect that if I wanted synthesized properties on the page or menu it would fail:
node = (AbstractLeafNode*)menu;
node = (AbstractLeadNode*)page;
Sorry, as I was editing the question I realised that I was trying to do it the wrong way round and assign an AbstractLeafNode to a Menu, so the compiler warning completely makes sense. No errors when assigning a Menu to an AbstractLeafNode.
I've been staring at this for too long!