Swift enum associated values in Objective-C - objective-c

Is there a way to use new Swift3 enums with associated value in Objective-C?
Is there a way to declare/bridge Swift3 enums with associated value in Objective-C, if I develop a library and want to give Swift3 users convenient API?

I'm afraid it's not possible, Apple has a list of Swift Type Compatibility which explicitly excludes enumerations defined in Swift without Int raw value type.
Reference

This is what I did:
In Swift class created the enum
enum Origin {
case Search(searchTerm: String, searchResultsPageNum: Int)
case Discovery(pageNum: Int)
}
Then in my Class, created enum property and functions (that are visible to Objective C) to set and get values of the enum property.
#objc class GameSession: NSObject
{
...
var gameOrigin: Origin?
...
let originStr = "origin"
let notSpecified = "Not Specified"
#objc func getOrigin() -> NSDictionary
{
guard let origin = gameOrigin else {
return [originStr: notSpecified]
}
switch origin {
case .Search(let searchTerm, let searchResultsPageNum):
return ["searchTerm": searchTerm, "searchResultsPageNum": "\(searchResultsPageNum)"]
case .Discovery(let pageNum)
return ["pageNum": pageNum]
default:
return [originStr: notSpecified]
}
}
#objc func setSearchOriginWith(searchTerm: String, searchResultsPageNum: Int, filtered:Bool)
{
self.gameOrigin = Origin.Search(searchTerm: searchTerm, searchResultsPageNum: searchResultsPageNum, filtered: filtered)
}
#objc func setDiscoveryOriginWith(pageNum: Int)
{
self.gameOrigin = Origin.Discovery(pageNum: pageNum)
}
}

Related

Kotlin enum like in the swift

I have enum in swift
enum Type {
case bool(Bool)
case int(Int)
case array([String])
}
Dont understand how i can convert this to kotlin code, i did like this:
enum class AnswerSheetType {
BOOL,
INT,
ARRAY
}
But how i can pass variable to enum type. For example then i want create method which will be return type with variable, like this(swift code):
func marks(for id: String) -> Type {
let answer = answers?[id]
if let boolAnswer = answer as? Bool {
return .bool(boolAnswer)
}
if let intAnswer = answer as? Int {
return .int(intAnswer)
}
if let arrayAnswer = answer as? [String] {
return .array(arrayAnswer)
}
}
You can use a sealed interface/class to represent this.
sealed interface Type {
data class BoolType(val value: Bool) : Type
data class IntType(val value: Int) : Type
data class ArrayType(val value: Array<String>) : Type
// if you have a case that doesn't have any associated values, just use an object
// object CaseWithoutAssociatedValues: Type
}
Usage:
// let someType: Type = .bool(true)
val someType: Type = Type.BoolType(true)
// just like how you can use a switch on a Swift enum, you can use a when like this too:
// This when is also exhaustive, if you specify all the implementers of Type
when (someType) {
is Type.BoolType -> println("Bool value: ${someType.value}")
is Type.IntType -> println("Int value: ${someType.value}")
is Type.ArrayType -> println("Array value: ${someType.value}")
}
Notice that in each of the branches, you can access someType.value, because of the smart-cast. This is unlike in Swift, where you would do pattern matching to get the associated values out.
As I was writing my answer Sweeper already answered with almost the identical solution. I wanted to add to that, that your marks function could then be written as this:
fun marks(id: String) : Type? {
when (val answer = answers?.get(id)) {
is Boolean -> return Type.BoolType(answer)
is Int -> return Type.IntType(answer)
is Array<*> -> if (answer.isArrayOf<String>()) return Type.ArrayType(answer as Array<String>)
}
return null
}
The Array case is a bit ugly but that is because checking on is Array<String> is not possible. The IDE will also still complain about having an unchecked cast but it should work. I don't know if there's a nicer way to handle this.

Using Swift Class and Enum from Objective-C

I've included a set of swift classes and their swift dependencies into my Objective-C project. I've already done this for other swift libraries, so things like the Obj-C Generated Interface Header are already present.
This is the class I wish to use:
#objc public class StatusBarNotificationBanner: BaseNotificationBanner
{
override init(style: BannerStyle) {
super.init(style: style)
bannerHeight = 20.0
titleLabel = MarqueeLabel()
titleLabel?.animationDelay = 2
titleLabel?.type = .leftRight
titleLabel!.font = UIFont.systemFont(ofSize: 12.5, weight: UIFontWeightBold)
titleLabel!.textAlignment = .center
titleLabel!.textColor = .white
addSubview(titleLabel!)
titleLabel!.snp.makeConstraints { (make) in
make.top.equalToSuperview()
make.left.equalToSuperview().offset(5)
make.right.equalToSuperview().offset(-5)
make.bottom.equalToSuperview()
}
updateMarqueeLabelsDurations()
}
public convenience init(title: String, style: BannerStyle = .info) {
self.init(style: style)
titleLabel!.text = title
}
public convenience init(attributedTitle: NSAttributedString, style: BannerStyle = .info) {
self.init(style: style)
titleLabel!.attributedText = attributedTitle
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
This is how one would use the class in swift:
let banner = StatusBarNotificationBanner(title: title, style: .success)
banner.show()
How in Obj-C would I instantiate StatusBarNotificationBanner and call its show() method?
Also, how do I pass the enum parameter style?
This is the enum:
public enum BannerStyle {
case danger
case info
case none
case success
case warning
}
I guess that the enum needs to take the form:
#objc public enum BannerStyle: Int {
case danger
case info
case none
case success
case warning
}
But I still don't know how to pass it as a param in Obj-C and I'm not understanding why the Int has to be specified? Isn't the enum implicitly Int?
Isn't the enum implicitly Int?
Not really. Objective-C cannot see a Swift enum at all. In Swift, an enum is an object type. Objective-C has no knowledge whatever of any such object type; its only objects are classes. (In Objective-C, enums are just numbers with names.) Therefore, neither a Swift enum type, nor a method that takes or produces a Swift enum, nor a Swift enum property, is exposed to Objective-C
However, in the special case where you say #objc enum BannerStyle: Int, it is translated into an Objective-C enum for you. So, in Objective-C, names such as BannerStyleDanger and BannerStyleInfo will spring to life. But they will just be integers.

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.

Shouldn't every .swift file be a class?

I have some experience in ObjC and I just started learning Swift. In Objc everything is a class through #interface in .h and #implementation of .m, or in other Swift classes that I have seen everything is usually in some form of
class MyCustomClassInhertingfrom: SomeFoundationClass { //methods & properties}
Yet here in some class named pancakeHouse.Swift there is no mention of the keyword class WHY? Isn't this a Model Class? Doesn't this break the MVC design pattern? Is this happening because of new powerful features of enums& structs vs class in Swift?_____I am confused obviously!
import UIKit
import CoreLocation
enum PriceGuide : Int {
case Unknown = 0
case Low = 1
case Medium = 2
case High = 3
}
extension PriceGuide : CustomStringConvertible {
var description : String {
switch self {
case .Unknown:
return "?"
case .Low:
return "$"
case .Medium:
return "$$"
case .High:
return "$$$"
}
}
}
enum PancakeRating {
case Unknown
case Rating(Int)
}
extension PancakeRating {
init?(value: Int) {
if value > 0 && value <= 5 {
self = .Rating(value)
} else {
self = .Unknown
}
}
}
extension PancakeRating {
var ratingImage : UIImage? {
guard let baseName = ratingImageName else {
return nil
}
return UIImage(named: baseName)
}
var smallRatingImage : UIImage? {
guard let baseName = ratingImageName else {
return nil
}
return UIImage(named: "\(baseName)_small")
}
private var ratingImageName : String? {
switch self {
case .Unknown:
return nil
case .Rating(let value):
return "pancake_rate_\(value)"
}
}
}
struct PancakeHouse {
let name: String
let photo: UIImage?
let thumbnail: UIImage?
let priceGuide: PriceGuide
let location: CLLocationCoordinate2D?
let details: String
let rating: PancakeRating
}
extension PancakeHouse {
init?(dict: [String : AnyObject]) {
guard let name = dict["name"] as? String,
let priceGuideRaw = dict["priceGuide"] as? Int,
let priceGuide = PriceGuide(rawValue: priceGuideRaw),
let details = dict["details"] as? String,
let ratingRaw = dict["rating"] as? Int,
let rating = PancakeRating(value: ratingRaw) else {
return nil
}
self.name = name
self.priceGuide = priceGuide
self.details = details
self.rating = rating
if let imageName = dict["imageName"] as? String where !imageName.isEmpty {
photo = UIImage(named: imageName)
} else {
photo = nil
}
if let thumbnailName = dict["thumbnailName"] as? String where !thumbnailName.isEmpty {
thumbnail = UIImage(named: thumbnailName)
} else {
thumbnail = nil
}
if let latitude = dict["latitude"] as? Double,
let longitude = dict["longitude"] as? Double {
location = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
} else {
location = nil
}
}
}
extension PancakeHouse {
static func loadDefaultPancakeHouses() -> [PancakeHouse]? {
return self.loadPancakeHousesFromPlistNamed("pancake_houses")
}
static func loadPancakeHousesFromPlistNamed(plistName: String) -> [PancakeHouse]? {
guard let path = NSBundle.mainBundle().pathForResource(plistName, ofType: "plist"),
let array = NSArray(contentsOfFile: path) as? [[String : AnyObject]] else {
return nil
}
return array.map { PancakeHouse(dict: $0) }
.filter { $0 != nil }
.map { $0! }
}
}
extension PancakeHouse : CustomStringConvertible {
var description : String {
return "\(name) :: \(details)"
}
}
extension PancakeHouse: Equatable {
}
func ==(lhs: PancakeHouse, rhs: PancakeHouse) -> Bool {
return lhs.name == rhs.name
}
Note: I would appreciate an answer that also includes comparison of the .swift vs .h + .m ie don't just consider this as a specific question, consider it as a general question and explain or link the prerequisites's details needed to understand this question)
pancakeHouse.swift defines PancakeHouse and all the various things that go with it. That is perfectly good Swift style. There is no class here because PancakeHouse happens to be a struct, which is also perfectly good Swift style (and mildly preferred). Structs are much like classes in Swift, in that they can have data and methods (and extensions).
ObjC does not require that each class be defined in its own .h/.m pair, but it is fairly typical ObjC style to do so. That said, even in ObjC there are exceptions. It is common to have mutable subclasses defined in the same file as their base class (NSArray and NSMutableArray are both defined in NSArray.h). Swift style has evolved towards lumping related things together more closely. One style makes it easier to find something if you know its name. The other makes it easier to find related concepts together. Both have their advantages, and once people are used to one they tend to believe that that one is obviously correct. But they're just different ways of organizing.
Note that this file also makes use of Swift extensions to break up related methods. That is also common and good Swift. Old ObjC did that with categories, but it's much less common to organize ObjC code that way today (categories are used for other things now). Again, neither is deeply correct. It's just the styles that have evolved.
Swift files just need valid swift code. That's all. In this case, your swift file is just defining two enums. Generally it's better practice to put each enum (and its extension) in it's own .swift file, but that's really just personal opinion when you get right down to it.
A .m is the implementation of the .h interface. It's a pain in the !##$ to keep those two things always up to date. Swift makes this much easier by just merging them into one thing/file, and your entire project automatically sees it based on the access you've set. If you don't set any access, like the example above, then you have 'internal' which means the whole project.
There are two philosophies the compiler writers could take on this question:
Each public class needs a file with the matching name - this is Java's way of doing it. Although it is minimally restrictive, the logic behind it is to let the compiler find class references without looking through all the files. It also helps programmers organize their code.
Do it your own way - this is the road taken by Swift, along with C, C#, Objective-C. Essentially, compiler writers tell you that their compiler will find your classes no matter where you put them, letting you organize your code in a way that you find the most intuitive for you and your team.

How to access private members of an Objective-C class from a Swift extension?

I'm trying to extend an Objective-C class in Swift and make it conform to the Equatable protocol. This requires to access some private members of the extended class, which the compiler doesn't let me do. What is the correct way to do it without making the private members public?
My Swift code:
import Foundation
extension ShortDate : Equatable { }
public func == (lhs: ShortDate, rhs: ShortDate) -> Bool {
if (lhs.components.year == rhs.components.year)
&& (lhs.components.month == rhs.components.month)
&& (lhs.components.day == rhs.components.day) {
return true;
}
return false;
}
Objective-C:
#interface ShortDate : NSObject<NSCopying, NSCoding> {
NSDate *inner;
NSDateComponents *components; // The date split into components.
}
...
#end
The error I'm getting:
ShortDate.swift:26:9: 'ShortDate' does not have a member named 'components'
I came across this question while trying to find a way to access a private variable of a class from one of the SDKs we use. Since we don't have or control the source code we can't change the variables to properties. I did find that the following solution works for this case:
extension ObjcClass {
func getPrivateVariable() -> String? {
return value(forKey: "privateVariable") as? String
}
open override func value(forUndefinedKey key: String) -> Any? {
if key == "privateVariable" {
return nil
}
return super.value(forUndefinedKey: key)
}
}
Overriding value(forUndefinedKey:) is optional. value(forKey:) will crash if the private variable doesn't exist on the class unless you override value(forUndefinedKey:) and provide a default value.
I believe that there is no way to access Objective-C instance variables from Swift. Only Objective-C properties get mapped to Swift properties.