Objective-C NS_OPTIONS in Swift - objective-c

I'm given an NS_OPTIONS defined in Objective-C:
typedef NS_OPTIONS(NSInteger, MyType) {
MyTypeOption1 = 1 << 0,
MyTypeOption2 = 1 << 1,
MyTypeOption3 = 1 << 2,
// etc
}
I'm importing this type into Swift, but I can't form bit fields.
let default : MyType = MyTypeOption1 | MyTypeOption2
The error:
Protocol 'BinaryInteger' requires that 'MyType' conform to 'BinaryInteger'
The IDE indicates that it is the standing-colon bitwise-or operator which is the problem.
Altering the NS_OPTIONS declarations or declaring a new type with Swift's OptionSet aren't ... options. How can I get Swift to play ball?

Your syntax is simply wrong. The Swift syntax for a bit field (which is actually called an OptionSet in Swift) is the array syntax, not the bitwise OR syntax.
Objective-C enums created using NS_OPTIONS are automatically imported into Swift as OptionSets.
So you just need to do let default: MyType = [.option1, .option2] instead of trying to replicate the Obj-C bitfield syntax.
For more info on the topic, see How to create NS_OPTIONS-style bitmask enumerations in Swift?

Related

Objective-C float #define not accessible in Swift4

I am migrating code from Objective-C to Swift 4.0. Here I have some float #define constants related to my deviceHeight in Specific Objective-C header class. While accessing this #define giving error "Use of unresolved identifier". When I use Objective-C string #define identifier it's easily accessible within Swift class.
Not accessible in Swift4
#define PHONE_IPHONE10 PHONE_UISCREEN_HEIGHT==812.0f
Accessible in Swift4
#define ERROR #"Some error occured. Please try later."
Help me with your comments or solution.
The reason this imports to Swift...
#define ERROR #"Some error occured. Please try later."
...is that it’s semantically equivalent to a constant declaration. That is, it permanently associates that string-literal value with the name ERROR. The Swift compiler recognizes that you’re using the C preprocessor to define a constant, and translates it to a Swift constant.
(Even though you could—and probably should—define C global constants without the preprocessor, Swift recognizes that there’s a long tradition of using #define instead, and imports it anyway.)
The reason this doesn’t import to Swift...
#define PHONE_IPHONE10 PHONE_UISCREEN_HEIGHT==812.0f
...is that this is a preprocessor macro. It doesn’t statically map a name to a value. Instead, it tells C that wherever it sees your name PHONE_IPHONE10, it should substitute the expression PHONE_UISCREEN_HEIGHT==812.0f. Presumably PHONE_UISCREEN_HEIGHT is itself a macro, so the whole thing expands to a chain of method calls and an equality comparison.
Swift itself doesn’t do preprocessor macros, or anything like such, so it doesn’t import them from C.
A close equivalent would be to redefine this logic using a computed property or function (and the idiomatic way to do that in Swift would be as a static member on a type, not a global symbol). Something like this:
extension UIDevice {
class var isMaybeiPhoneX: Bool {
return false // or some logic based on UIScreen.main.size
}
}
But be warned, the whole idea of conditionally changing your app’s UI or behavior based on a specific screen height check is fraught with peril. Tried Auto Layout?
To achieve similar functionality I created Constants.swift file with this structure:
struct Constants {
struct phoneHeights {
static let PHONE_UISCREEN_HEIGHT = 812.0
//some others consts
}
struct iPhoneX {
static let statusBarHeight: CGFloat = 44
//some others consts
}
}
Or simply:
struct Constants {
static let PHONE_UISCREEN_HEIGHT = 812.0
static let statusBarHeight: CGFloat = 44
}
And for type safety in Swift, you can read here.

enum defined in Objc > Declared in Swift > to be used in Objc

I have a situation. I would appreciated if anyone has a solution for this
I have an objC enum say Abc
I declare this in a swift class, say, MySwiftClass.swift as var abc : Abc!
I have created an instance of MySwiftClass (mySwiftClass) in another ObjC class (myObjC.m file)
In myObjC.m, I’m trying to access enum Abc as mySwiftClass.abc.
This is throwing an error - “Property ‘abc’ not found on object of type MySwiftClass *”.
Basically the enum is not added as property in the “ProjectName-Swift.h” file.
What I believe is happening is that when I’m declaring the ObjC enum in Swift class, it is getting converted to a swift enum and hence I’m not able to access it in ObjC file.
Note: Marking the Swift class as #objc did not work.
Numeric Swift optionals cannot be represented in Objective-C, and thus will not be exposed to Objective-C. Declare abc to not be optional and it should be available from Objective-C.
Consider this Objective-C enumeration:
typedef NS_ENUM(NSInteger, Foo) {
FooBar,
FooBaz,
FooQux
};
Then consider this Swift 3 class:
class SomeObject: NSObject {
var foo1: Foo = .bar // this is exposed to Objective-C
var foo2: Foo! = .bar // this is not
}
The non-optional, foo1, will be exposed to Objective-C, whereas the optional, foo2, will not.

How do I implement Sequence (to allow Swift's for-in syntax) from Objective-C?

I'm writing an API in Objective-C and would like it to play nicely in Swift. I'm having trouble getting "for..in" syntax working though. I think I need to implement the Sequence protocol, but I can't find any examples doing this from Objective-C. Just referencing Sequence gives me error: no type or protocol named 'Sequence'. Is there a special #import to get access to it or something?
I tried implementing the NSFastEnumeration protocol, thinking maybe it'd magically convert to Sequence in Swift, but that didn't work.
///// Obj-C Code
#interface Foo : NSObject<NSFastEnumeration>
...
#end
///// Swift Code
var foo: Foo = Foo()
// ERROR: Type 'Foo' does not conform to protocol 'Sequence'
for y in foo {
print("Got a y.")
}
EDIT: It looks like inheriting from NSEnumerator gets me closer, but doesn't quite work either:
///// Obj-C Code
#interface Foo : NSEnumerator<NSString *>
...
#end
///// Swift Code
// ERROR: 'NSFastEnumerationIterator.Element' (aka 'Any') is not convertible to 'String'
for y: String in foo {
print("Got \(y)")
}
EDIT 2: I still don't have a good solution and have logged a bug: https://bugs.swift.org/browse/SR-2801
The Swift extension of Foundation includes some support for making classes that adopt NSFastEnumeration also support the Swift Sequence protocol... but not automatically.
One way to do it is to extend your ObjC type in Swift and pass through to the NSFastEnumerationIterator type:
extension Foo: Sequence {
public func makeIterator() -> NSFastEnumerationIterator {
return NSFastEnumerationIterator(self)
}
}
NSFastEnumerationIterator (and all forms of ObjC enumeration) are type erasing, though, so they don't provide any insight on the element type you're iterating through. That means that you can do this (after adding the above extension):
var foo: Foo = Foo()
for y in foo {
print("Got a y.")
}
... but the static type of y is always Any. If you want typed access to the members of foo, you'll need a cast or a filtered loop:
for y in foo where y is String {
print("Got \(y)")
}
Sadly, if your class adopts ObjC generics, there doesn't seem to be a way to make this work — you'll get an error "Extension of a generic Objective-C class cannot access the class's generic parameters at runtime", even if you adopt the runtime type introspection method(s) in SE-0057. For non-generic ObjC classes you're good, though.

How to use ObjC block typedefs in Swift2 (esp. when containing BOOL parameters)

After moving on to Xcode7 (beta5) and Swift 2, I'm getting errors in my Swift code regarding Bool (or BOOL / ObjCBool) values that are passed from (or into) closures with an Objective-C typedef.
typedef void (^completion_success_block_t) (BOOL success);
When I use this type in my Swift class, I'm getting compiler errors.
func doSomething(completionBlock : completion_success_block_t) {
doSomethingElse { success in
if success == true { } // (1) error 1
let foo : Bool = true
completionBlock(foo) // (2) error 2
completionBlock(true) // (3) works just fine!
}
}
// error 1: "Binary operator '==' cannot be applied to operands of type 'ObjCBool' and 'Bool'"
// error 2: "Cannot invoke 'completionBlock' with an argument list of type '(Bool)'"
func doSomethingElse(completionBlock : completion_success_block_t) {
completionBlock(true)
}
Why does line (3) compile just fine, but not line (2)?
It seems like the compiler does convert between ObjCBools and Bools in some cases but not always.
It feels like using my ObjC typedef is like telling the compiler: 'I really want this to be an ObjCBool, not a Bool, so please don't do any conversions for me'. But that's not what I want. My code was working perfectly fine in Xcode 6.
Now I only see two options:
1) convert all values manually before passing / using them: let swiftSuccess = Bool(success) and let objCFoo = ObjCBool(foo)
2) stop using the ObjC typedefs for blocks containing BOOL parameters
Is there a better way? Perhaps changing the block signature in the typedef to work with both Swift and ObjC? But how?
Cool! Apple fixed this in Xcode Version 7.0 (7A218). Now passing a Swift Bool to a closure that was an ObjC typedef with a BOOL parameter works just fine. Comparing the BOOL parameter with true or false in Swift works as well.
Only downside for me: now all my workaround lines of code (e.g. completionBlock(ObjCBool(true))) cause compiler errors (sometimes even of the slightly obscure Segmentation fault: 11 type). Have to revert it all back. Sigh... (thank god there's git)

Is it possible to use Swift's Enum in Obj-C?

I'm trying to convert some of my Obj-C class to Swift. And some other Obj-C classes still using enum in that converted class. I searched In the Pre-Release Docs and couldn't find it or maybe I missed it. Is there a way to use Swift enum in Obj-C Class? Or a link to the doc of this issue?
This is how I declared my enum in my old Obj-C code and new Swift code.
my old Obj-C Code:
typedef NS_ENUM(NSInteger, SomeEnum)
{
SomeEnumA,
SomeEnumB,
SomeEnumC
};
#interface SomeClass : NSObject
...
#end
my new Swift Code:
enum SomeEnum: NSInteger
{
case A
case B
case C
};
class SomeClass: NSObject
{
...
}
Update: From the answers. It can't be done in Swift older version than 1.2. But according to this official Swift Blog. In Swift 1.2 that released along with XCode 6.3, You can use Swift Enum in Objective-C by adding #objc in front of enum
As of Swift version 1.2 (Xcode 6.3) you can. Simply prefix the enum declaration with #objc
#objc enum Bear: Int {
case Black, Grizzly, Polar
}
Shamelessly taken from the Swift Blog
Note: This would not work for String enums or enums with associated values. Your enum will need to be Int-bound
In Objective-C this would look like
Bear type = BearBlack;
switch (type) {
case BearBlack:
case BearGrizzly:
case BearPolar:
[self runLikeHell];
}
To expand on the selected answer...
It is possible to share Swift style enums between Swift and Objective-C using NS_ENUM().
They just need to be defined in an Objective-C context using NS_ENUM() and they are made available using Swift dot notation.
From the Using Swift with Cocoa and Objective-C
Swift imports as a Swift enumeration any C-style enumeration marked with the NS_ENUM macro. This means that the prefixes to enumeration value names are truncated when they are imported into Swift, whether they’re defined in system frameworks or in custom code.
Objective-C
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
Swift
let cellStyle: UITableViewCellStyle = .Default
From the Using Swift with Cocoa and Objective-C guide:
A Swift class or protocol must be marked with the #objc attribute to
be accessible and usable in Objective-C. [...]
You’ll have access to anything within a class or protocol that’s
marked with the #objc attribute as long as it’s compatible with
Objective-C. This excludes Swift-only features such as those listed
here:
Generics Tuples / Enumerations defined in Swift / Structures defined in
Swift / Top-level functions defined in Swift / Global variables defined in
Swift / Typealiases defined in Swift / Swift-style variadics / Nested types /
Curried functions
So, no, you can't use a Swift enum in an Objective-C class.
Swift 4.1, Xcode 9.4.1:
1) Swift enum must be prefixed with #objc and be Int type:
// in .swift file:
#objc enum CalendarPermission: Int {
case authorized
case denied
case restricted
case undetermined
}
2) Objective-C name is enum name + case name, eg CalendarPermissionAuthorized:
// in .m file:
// point to something that returns the enum type (`CalendarPermission` here)
CalendarPermission calPermission = ...;
// use the enum values with their adjusted names
switch (calPermission) {
case CalendarPermissionAuthorized:
{
// code here
break;
}
case CalendarPermissionDenied:
case CalendarPermissionRestricted:
{
// code here
break;
}
case CalendarPermissionUndetermined:
{
// code here
break;
}
}
And, of course, remember to import your Swift bridging header as the last item in the Objective-C file's import list:
#import "MyAppViewController.h"
#import "MyApp-Swift.h"
If you prefer to keep ObjC codes as-they-are, you could add a helper header file in your project:
Swift2Objc_Helper.h
in the header file add this enum type:
typedef NS_ENUM(NSInteger, SomeEnum4ObjC)
{
SomeEnumA,
SomeEnumB
};
There may be another place in your .m file to make a change: to include the hidden header file:
#import "[YourProjectName]-Swift.h"
replace [YourProjectName] with your project name. This header file expose all Swift defined #objc classes, enums to ObjC.
You may get a warning message about implicit conversion from enumeration type... It is OK.
By the way, you could use this header helper file to keep some ObjC codes such as #define constants.
If you (like me) really want to make use of String enums, you could make a specialized interface for objective-c. For example:
enum Icon: String {
case HelpIcon
case StarIcon
...
}
// Make use of string enum when available:
public func addIcon(icon: Icon) {
...
}
// Fall back on strings when string enum not available (objective-c):
public func addIcon(iconName:String) {
addIcon(Icon(rawValue: iconName))
}
Of course, this will not give you the convenience of auto-complete (unless you define additional constants in the objective-c environment).
After researching this, I kept finding only partial answers, so I created an entire example of a Swift App bridged to Objective C that has Swift enums used by Objective C code and Objective C enums used by Swift code. It is a simple Xcode project that you can run and experiment with. It was written using Xcode 10.3 with Swift 5.0
Example Project
In case you are trying to observe an enum which looks like this:
enum EnumName: String {
case one = "One"
case two = "Two"
}
this workaround helped me.
Observable Class:
create #objc dynamic var observable: String?
create your enum instance like this:
private var _enumName: EnumName? {
didSet {
observable = _enumName!.rawValue
}
}
Observer Class:
create private var _enumName: EnumName?
create private let _instance = ObservableClass()
create
private var _enumObserver: NSKeyValueObservation = _instance.observe(\.observable, options: .new, changeHandler: { [weak self] (_, value) in
guard let newValue = value.newValue else { return }
self?._enumName = EnumName(rawValue: period)!
})
Than's it. Now each time you change the _enumName in the observable class, an appropriate instance on the observer class will be immediately updated as well.
This is of course an oversimplified implementation, but it should give you an idea of how to observe KVO-incompatible properties.
this might help a little more
Problem statement :- I have enum in swift class, which I am accessing form other swift classes, and Now I need to access it form my one of the objective C class.
Before accessing it from objective-c class :-
enum NTCType {
case RETRYNOW
case RETRYAFTER
}
var viewType: NTCType?
Changes for accessing it from objective c class
#objc enum NTCType :Int {
case RETRYNOW
case RETRYAFTER
}
and add a function to pass it on the value
#objc func setNtc(view:NTCType) {
self.viewType = view; // assign value to the variable
}