Expose Swift Array Of Enums in Objective-C - objective-c

I have this enum in swift
#objc(PaymentMethods)
public enum PaymentMethods: Int, RawRepresentable {
public typealias RawValue = String
case card
case account
case paypal
public var rawValue: RawValue {
switch self {
case .card:
return "CARD"
case .account:
return "ACCOUNT"
case .paypal:
return "PAYPAL"
}
}
public init(rawValue: RawValue){
switch rawValue {
case "CARD":
self = .card
case "ACCOUNT":
self = .account
case "PAYPAL":
self = .paypal
default:
self = .card
}
}
}
And this property in a class.
#objc public class SomeClass: ExtendingSomeOtherStuffs {
var supportedPaymentMethods:[PaymentMethods]!
}
my problem is how to bridge supportedPaymentMethods into Objective-C and use it.
I have looked at this post and this but still can't figure it out.
can someone help me out with an example at least.
Am trying to use this in Native-script and I need to expose that property from Swift to Objective

You can do it like so:
#objc public class SomeClass: NSObject {
var supportedPaymentMethods: [PaymentMethods]
#objc init(supportedPaymentMethods: [String]) {
self.supportedPaymentMethods = supportedPaymentMethods.map { .init(rawValue: $0) }
}
}
and use it like this in your Objective-C code:
[[SomeClass alloc] initWithSupportedPaymentMethods:#[#"ACCOUNT", #"CARD", #"PAYPAL"]];
If you don't want your class initializer to take any String I would suggest doing the following:
Make your init(rawValue:) failable and return nil when the argument is invalid:
public init?(rawValue: RawValue) {
switch rawValue {
case "CARD":
self = .card
case "ACCOUNT":
self = .account
case "PAYPAL":
self = .paypal
default:
return nil
}
}
Use compactMap instead of map in the init of the SomeClass like so:
#objc init(supportedPaymentMethods: [String]) {
self.supportedPaymentMethods = supportedPaymentMethods.compactMap { .init(rawValue: $0) }
}
(it will eliminate the nil values)

Related

Invalid redeclaration of rawValue in Release build

I have a mixed project and came across an interesting issue.
There's an enum, defined in obj-c
typedef NS_ENUM (NSUInteger, ABCCategory) {
ABCCategoryFirst,
ABCCategorySecond
};
Next, there's a swift file where an extension is defined
extension ABCCategory: RawRepresentable {
public typealias RawValue = String
public init(rawValue: RawValue) {
switch rawValue {
case "first":
self = .first
case "second":
self = .second
default:
self = .first
}
}
public var rawValue: RawValue {
get {
switch self {
case .first:
return "first"
case .second:
return "second"
}
}
}
}
Everything works fine in the Debug configuration, but when I switch to Release it does not build, saying: Invalid redeclaration of 'rawValue'
I've tried removing typealias, replacing RawValue with String (so the protocol could implicitly guess the value), making constructor optional as in the protocol (and implicitly unwrapped optional also) - no go.
I do understand that extending an Int enum with string is a bit weird, but why it stops building in Release and working absolutely perfect in Debug?
Is there some different mechanism of treating enums/classes/extensions for Release configuration?
The raw value syntax for enums in Swift is “just” a shorthand for conformance to the RawRepresentable protocol. It’s easy to add this manually if you want to use otherwise unsupported types as raw values.
Source
I'm not sure why it works in debug because when you create a typed enum you are already 'conforming' to RawRepresentable. So when you create an NS_ENUM it is imported in to swift like so:
public enum ABCCategory : UInt {
case first
case second
}
Meaning that it already conforms to RawRepresentable. The fix can be achieved two ways, one in Swift and in Objective-C
In Swift we just remove the RawRepresentable and change rawValue to stringValue, and RawValue to String:
extension ABCCategory {
var stringValue: String {
switch self {
case .first: return "first"
case .second: return "second"
}
}
init(_ value: String) {
switch value {
case "first":
self = .first
case "second":
self = .second
default:
self = .first
}
}
}
Or you could just change the Objective-C to use NS_TYPED_ENUM. Some info here. However this will change your enum to a struct
.h
typedef NSString *ABCCategory NS_TYPED_ENUM;
extern ABCCategory const ABCCategoryFirst;
extern ABCCategory const ABCCategorySecond;
.m
ABCCategory const ABCCategoryFirst = #"first";
ABCCategory const ABCCategorySecond = #"second";
This will be imported by swift like so:
public struct ABCCategory : Hashable, Equatable, RawRepresentable {
public init(rawValue: String)
}
public static let first: ABCCategory
public static let second: ABCCategory

How do we return an optional enum value from Swift to Obj-C?

We know how to expose Swift enums to Obj-C:
#objc enum Animal: Int {
case Cat, Dog
}
But the compiler complains that the following "cannot be represented in Obj-C":
func myAnimal() -> Animal? {
if hasPet() {
return .Cat
} else {
return nil
}
}
Ideas?
Optional Ints in Swift don't map to a type in Objective-C. The issue is that your myAnimal() method is returning a type that can't be represented in Objective-C.
The way I see it, you have two options...
Option1: Change your method's return type:
func myAnimal() -> NSNumber? {
// method body goes here.
}
This doesn't really seem great since in Objective-C you'd have to do something like this:
if (myAnimal().integerValue == AnimalCat) {
// do stuff here
}
Option 2: Add a catch-all case in your enum
#objc enum Animal: Int {
case none = 0
case cat = 1
case dog = 2
init(rawValue: Int) {
switch rawValue {
case 1: self = Cat
case 2: self = Dog
default: self = None
}
}
}
// Updated method signature that makes the compiler happy.
func myAnimal() -> Animal {
if hasPet() {
return .Cat
} else {
return .None
}
}
This way, you can change your method signature to not return an optional, and the compiler will be happy.

Swift: write a function that allocates an instance of (any) class

I'm looking for the equivalent of the following Obj-C code in Swift:
- newInstanceOf:(id)classRef {
return [classRef new];
}
and then to use it:
id instance = [whatever newInstanceOf:NSArray.class]
[instance isKindOfClass:NSArray.class] == YES
I have tried using a Swift template:
func newSomething<T>(classRef:T.Type) -> T {
return classRef()
}
I get the error: error: 'T' cannot be constructed because it has no accessible initializers
You could create a protocol to act as a type constraint for objects initializable by a void-argument initializer, and thereafter extend your types of choice to this protocol.
protocol SimplyInitializable {
init()
}
extension Int : SimplyInitializable { }
extension Double : SimplyInitializable { }
extension String : SimplyInitializable { }
struct MyStruct {
var myInt : Int
init() {
myInt = 0
}
}
extension MyStruct : SimplyInitializable { }
func newSomething<T: SimplyInitializable>(classRef: T.Type) -> T {
return classRef.init()
}
/* Examples */
var a = newSomething(Int)
var b = newSomething(Double)
var c = newSomething("".dynamicType)
var d = newSomething(MyStruct)
var e = newSomething(a.dynamicType)
print(a.dynamicType) // Int
print(b.dynamicType) // Double
print(c.dynamicType) // String
print(d.dynamicType) // MyStruct
print(e.dynamicType) // Int
Actually in Swift, not all classes are guaranteed to have an init() initializer. It can work with NSObject classes though because NSObject does have that requirement.
func newInstanceOf<T:NSObject>(aClass:T.Type) -> NSObject
{ return aClass.init() }
let string = newInstanceOf(NSString) // ""
let date = newInstanceOf(NSDate) // 2016-01-15 05:27:29 +0000

How to make a Swift String enum available in Objective-C?

I have this enum with String values, which will be used to tell an API method that logs to a server what kind of serverity a message has. I'm using Swift 1.2, so enums can be mapped to Objective-C
#objc enum LogSeverity : String {
case Debug = "DEBUG"
case Info = "INFO"
case Warn = "WARN"
case Error = "ERROR"
}
I get the error
#objc enum raw type String is not an integer type
I haven't managed to find anywhere which says that only integers can be translated to Objective-C from Swift. Is this the case? If so, does anyone have any best-practice suggestion on how to make something like this available in Objective-C?
One of the solutions is to use the RawRepresentable protocol.
It's not ideal to have to write the init and rawValue methods but that allows you to use this enum as usual in both Swift and Objective-C.
#objc public enum LogSeverity: Int, RawRepresentable {
case debug
case info
case warn
case error
public typealias RawValue = String
public var rawValue: RawValue {
switch self {
case .debug:
return "DEBUG"
case .info:
return "INFO"
case .warn:
return "WARN"
case .error:
return "ERROR"
}
}
public init?(rawValue: RawValue) {
switch rawValue {
case "DEBUG":
self = .debug
case "INFO":
self = .info
case "WARN":
self = .warn
case "ERROR":
self = .error
default:
return nil
}
}
}
From the Xcode 6.3 release notes (emphasis added):
Swift Language Enhancements
...
Swift enums can now be exported to Objective-C using the #objc
attribute. #objc enums must declare an integer raw type, and cannot be
generic or use associated values. Because Objective-C enums are not
namespaced, enum cases are imported into Objective-C as the
concatenation of the enum name and case name.
Here's a solution that works.
#objc public enum ConnectivityStatus: Int {
case Wifi
case Mobile
case Ethernet
case Off
func name() -> String {
switch self {
case .Wifi: return "wifi"
case .Mobile: return "mobile"
case .Ethernet: return "ethernet"
case .Off: return "off"
}
}
}
Here is work around if you really want to achieve the goal. However, you can access the enum values in objects that Objective C accepts, not as actual enum values.
enum LogSeverity : String {
case Debug = "DEBUG"
case Info = "INFO"
case Warn = "WARN"
case Error = "ERROR"
private func string() -> String {
return self.rawValue
}
}
#objc
class LogSeverityBridge: NSObject {
class func Debug() -> NSString {
return LogSeverity.Debug.string()
}
class func Info() -> NSString {
return LogSeverity.Info.string()
}
class func Warn() -> NSString {
return LogSeverity.Warn.string()
}
class func Error() -> NSString {
return LogSeverity.Error.string()
}
}
To call :
NSString *debugRawValue = [LogSeverityBridge Debug]
If you don't mind to define the values in (Objective) C, you can use the NS_TYPED_ENUM macro to import constants in Swift.
For example:
.h file
typedef NSString *const ProgrammingLanguage NS_TYPED_ENUM;
FOUNDATION_EXPORT ProgrammingLanguage ProgrammingLanguageSwift;
FOUNDATION_EXPORT ProgrammingLanguage ProgrammingLanguageObjectiveC;
.m file
ProgrammingLanguage ProgrammingLanguageSwift = #"Swift";
ProgrammingLanguage ProgrammingLanguageObjectiveC = #"ObjectiveC";
In Swift, this is imported as a struct as such:
struct ProgrammingLanguage: RawRepresentable, Equatable, Hashable {
typealias RawValue = String
init(rawValue: RawValue)
var rawValue: RawValue { get }
static var swift: ProgrammingLanguage { get }
static var objectiveC: ProgrammingLanguage { get }
}
Although the type is not bridged as an enum, it feels very similar to one when using it in Swift code.
You can read more about this technique in Grouping Related Objective-C Constants
Code for Xcode 8, using the fact that Int works but other methods aren't exposed to Objective-C. This is pretty horrible as it stands...
class EnumSupport : NSObject {
class func textFor(logSeverity severity: LogSeverity) -> String {
return severity.text()
}
}
#objc public enum LogSeverity: Int {
case Debug
case Info
case Warn
case Error
func text() -> String {
switch self {
case .Debug: return "debug"
case .Info: return "info"
case .Warn: return "warn"
case .Error: return "error"
}
}
}
This is my use case:
I avoid hard-coded Strings whenever I can, so that I get compile warnings when I change something
I have a fixed list of String values coming from a back end, which can also be nil
Here's my solution that involves no hard-coded Strings at all, supports missing values, and can be used elegantly in both Swift and Obj-C:
#objc enum InventoryItemType: Int {
private enum StringInventoryItemType: String {
case vial
case syringe
case crystalloid
case bloodProduct
case supplies
}
case vial
case syringe
case crystalloid
case bloodProduct
case supplies
case unknown
static func fromString(_ string: String?) -> InventoryItemType {
guard let string = string else {
return .unknown
}
guard let stringType = StringInventoryItemType(rawValue: string) else {
return .unknown
}
switch stringType {
case .vial:
return .vial
case .syringe:
return .syringe
case .crystalloid:
return .crystalloid
case .bloodProduct:
return .bloodProduct
case .supplies:
return .supplies
}
}
var stringValue: String? {
switch self {
case .vial:
return StringInventoryItemType.vial.rawValue
case .syringe:
return StringInventoryItemType.syringe.rawValue
case .crystalloid:
return StringInventoryItemType.crystalloid.rawValue
case .bloodProduct:
return StringInventoryItemType.bloodProduct.rawValue
case .supplies:
return StringInventoryItemType.supplies.rawValue
case .unknown:
return nil
}
}
}
Here's what I came up with. In my case, this enum was in the context providing info for a specific class, ServiceProvider.
class ServiceProvider {
#objc enum FieldName : Int {
case CITY
case LATITUDE
case LONGITUDE
case NAME
case GRADE
case POSTAL_CODE
case STATE
case REVIEW_COUNT
case COORDINATES
var string: String {
return ServiceProvider.FieldNameToString(self)
}
}
class func FieldNameToString(fieldName:FieldName) -> String {
switch fieldName {
case .CITY: return "city"
case .LATITUDE: return "latitude"
case .LONGITUDE: return "longitude"
case .NAME: return "name"
case .GRADE: return "overallGrade"
case .POSTAL_CODE: return "postalCode"
case .STATE: return "state"
case .REVIEW_COUNT: return "reviewCount"
case .COORDINATES: return "coordinates"
}
}
}
From Swift, you can use .string on an enum (similar to .rawValue).
From Objective-C, you can use [ServiceProvider FieldNameToString:enumValue];
You can create an private Inner enum. The implementation is a bit repeatable, but clear and easy. 1 line rawValue, 2 lines init, which always look the same. The Inner has a method returning the "outer" equivalent, and vice-versa.
Has the added benefit that you can directly map the enum case to a String, unlike other answers here.
Please feel welcome to build on this answer if you know how to solve the repeatability problem with templates, I don't have time to mingle with it right now.
#objc enum MyEnum: NSInteger, RawRepresentable, Equatable {
case
option1,
option2,
option3
// MARK: RawRepresentable
var rawValue: String {
return toInner().rawValue
}
init?(rawValue: String) {
guard let value = Inner(rawValue: rawValue)?.toOuter() else { return nil }
self = value
}
// MARK: Obj-C support
private func toInner() -> Inner {
switch self {
case .option1: return .option1
case .option3: return .option3
case .option2: return .option2
}
}
private enum Inner: String {
case
option1 = "option_1",
option2 = "option_2",
option3 = "option_3"
func toOuter() -> MyEnum {
switch self {
case .option1: return .option1
case .option3: return .option3
case .option2: return .option2
}
}
}
}
I think #Remi 's answer crashes in some situations as I had this:
My error's screesshot. so I post my edition for #Remi 's answer:
#objc public enum LogSeverity: Int, RawRepresentable {
case debug
case info
case warn
case error
public typealias RawValue = String
public var rawValue: RawValue {
switch self {
case .debug:
return "DEBUG"
case .info:
return "INFO"
case .warn:
return "WARN"
case .error:
return "ERROR"
}
}
public init?(rawValue: RawValue) {
switch rawValue {
case "DEBUG":
self = .debug
case "INFO":
self = .info
case "WARN":
self = .warn
case "ERROR":
self = .error
default:
return nil
}
}
}

Custom equality in swift objects preserving compatibility with legacy Objective-C code

In Objective-C you would do something along the lines of
- (BOOL)isEqual:(id)other {
if (other == self)
return YES;
if (!other || ![other isKindOfClass:[self class]])
return NO;
return [self.customProperty isEqual:other.customProperty];
}
My first naive attempt in swift goes as follows
func isEqual(other: AnyObject) -> Boolean {
if self === other {
return true
}
if let otherTyped = other as? MyType {
return self.myProperty == otherTyper.myProperty
}
return false
}
But I'm far from being happy with it. I don't even know whether the signature is right or whether we're supposed to use anything different than isEqual.
Any thoughts?
EDIT:
I'd also like to keep Objective-C compatibility (my class is used in both legacy Obj-C code and new Swift code). So I think only overriding == isn't enough. Am I wrong?
Yes, you need to override isEqual (and hash) to make your objects fully Objective-C compatible. Here's a Playground-ready example for the syntax:
import Foundation
class MyClass: NSObject {
var value = 5
override func isEqual(object: AnyObject?) -> Bool {
if let object = object as? MyClass {
return value == object.value
} else {
return false
}
}
override var hash: Int {
return value.hashValue
}
}
var x = MyClass()
var y = MyClass()
var set = NSMutableSet()
x.value = 10
y.value = 10
set.addObject(x)
x.isEqual(y) // true
set.containsObject(y) // true
(syntax current as of Xcode 6.3)
You could also implement a custom equatable, for instance:
func == (lhs: CustomClass, rhs: CustomClass) -> Bool {
return lhs.variable == rhs.variable
}
This will allow you to simply check equality like this:
let c1: CustomClass = CustomClass(5)
let c2: CustomClass = CustomClass(5)
if c1 == c2 {
// do whatever
}
Be sure your custom equatable is outside the class scope!
swift3 sig:
open override func isEqual(_ object: Any?) -> Bool {
guard let site = object as? PZSite else {
return false
}
....
}
In Swift you can override infix operators (and even make your own). See here.
So rather than using isEqual you could do:
myType == anotherType
One more example
public class PRSize: NSObject {
public var width: Int
public var height: Int
public init(width: Int, height: Int) {
self.width = width
self.height = height
}
static func == (lhs: PRSize, rhs: PRSize) -> Bool {
return lhs.width == rhs.width && lhs.height == rhs.height
}
override public func isEqual(_ object: Any?) -> Bool {
if let other = object as? PRSize {
if self === other {
return true
} else {
return self.width == other.width && self.height == other.height
}
}
return false
}
override public var hash : Int {
return "\(width)x\(height)".hashValue
}
}
To archive Objective-C compatibility you have to override isEqual method as described on page 16 of this document: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/BuildingCocoaApps.pdf