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.
Related
Making Swift var backwards compatible with Objective-C
I have an Objective-C class I've converted to Swift. All my tests pass, but I'd like to further optimize it by adding a deprecation warning to update to notify users to update downstream dependencies to the Swift version of the var (Decimal) if they can. Whether they can depends upon whether the class they're using is an Objective-C class (which can only "see" NSDecimalNumber) or a Swift class. Is there a way to do this? This is what I've got so far.
#available(swift, introduced: 5.0)
public var mySwiftDecimal: Decimal?
#available(*, deprecated, renamed: "mySwiftDecimal")
public var myObjCDecimal: NSDecimalNumber? {
get {
return mySwiftDecimal as NSDecimalNumber?
} set {
mySwiftDecimal = newValue as Decimal?
}
}
You can annotate the member as deprecated in Swift:
#available(swift, deprecated: 5.0, renamed: "mySwiftDecimal")
#objc public var myObjCDecimal: NSDecimalNumber? {
get {
return mySwiftDecimal as NSDecimalNumber?
} set {
mySwiftDecimal = newValue as Decimal?
}
}
Then using it from Swift gives a warning:
let foo = Foo()
print(foo.myObjCDecimal)
// 'myObjCDecimal' is deprecated: renamed to 'mySwiftDecimal'
but using it from Objective-C does not:
Foo *foo = [[Foo alloc] init];
NSDecimalNumber *dec = foo.myObjCDecimal;
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
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)
}
}
MMCondition is a protocol defined in Swift, but interoperates with Objective-C (annotated with #objc).
#objc public protocol MMCondition {
static var name: String { get }
static var isMutuallyExclusive: Bool { get }
}
I have the following code:
// addCondition cannot be generic as I want it to be accessible from Objective-C as well.
public func addCondition(condition: MMCondition) {
// How do I initialize OperationConditionImplementer here?
let operationCondition = OperationConditionImplementer(condition: condition) // doesn't compile
// Error: Cannot invoke initializer for type 'OperationConditionImplementer<T>' with an argument list of type '(condition: MMCondition)'
// Can I use condition.dynamicType to init OperationConditionImplementer somehow?
}
struct OperationConditionImplementer<T: MMCondition> {
let condition: T
static var name: String {
return "Silent<\(T.name)>"
}
static var isMutuallyExclusive: Bool {
return T.isMutuallyExclusive
}
init(condition: T) {
self.condition = condition
}
}
From Objective-C, you can't use generics as stated in the documentation.
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
...
So you need to remove completely the generics code. One possible solution might be:
#objc protocol MMCondition {
static var name: String { get }
static var isMutuallyExclusive: Bool { get }
}
struct OperationConditionImplementer {
let condition: MMCondition
var name: String {
return "Silent<\(condition.dynamicType.name)>"
}
var isMutuallyExclusive: Bool {
return condition.dynamicType.isMutuallyExclusive
}
init(condition: MMCondition) {
self.condition = condition
// Here decide comparing types
if condition.dynamicType === ExampleCondition.self {
print(condition.dynamicType.name)
}
}
}
So for instance, if you try it out in a playground:
class ExampleCondition: NSObject, MMCondition {
static var name: String = "ExampleCondition"
static var isMutuallyExclusive: Bool = false
}
let example = OperationConditionImplementer(condition: ExampleCondition())
You'll see "ExampleCondition" printed.
If you eventually switch to pure Swift, you need to specify T when initializing a OperationConditionImplementer.
You can achieve that defining the addCondition method as:
func addCondition<T: MMCondition>(condition: T) {
let a = OperationConditionImplementer<T>(condition: condition)
}
Since Swift 2.0 instances of generic classes can implement Objective-C protocols. What won't be possible I believe is having a struct implement the protocol. In fact I expect that your protocol may need to inherit from NSObjectProtocol to be usable in Objective-C which would then prevent you from implementing the protocol with structs or enums.
You also rightly mention that you can't access generic functions from Objective-C.
For a concrete example of using a generic to fulfil an Objective-C protocol have a look at this blog post.
I have a delegate method that passes an enum as an argument:
func gestureRecognizer(gestureRecognizer: JTTableViewGestureRecognizer!, commitEditingState state: JTTableViewCellEditingState, forRowAtIndexPath indexPath: NSIndexPath!) -> Void {
//....
}
The enum is JTTableViewCellEditingState. It's implementation is in the same header file as the delegate method. It's as follows:
typedef enum {
JTTableViewCellEditingStateMiddle,
JTTableViewCellEditingStateLeft,
JTTableViewCellEditingStateRight,
} JTTableViewCellEditingState;
Yet trying to reference a state, for example Left, gives an error:
if state == JTTableViewCellEditingState.Left {
'JTTableViewCellEditingState.Type' does not have a member named 'Left'
Trying to do it the old, Objective-C way, like some kind of peasant, gives me a different, more expected, error:
if state == JTTableViewCellEditingStateLeft {
Cannot invoke '==' with an argument list of type '(JTTableViewCellEditingState, JTTableViewCellEditingState)'
I'm wondering how I should overcome this issue? I believe referencing Objective-C enums has worked just fine in the past.
This type of enum decleration causes problems in swift. I had similar problem. My solution is to create a helper objective-c method that does comparison and use that method in swift whenever == is needed.
Other solution may be refactor that code if you can and convert it proper enum decleration in objective-c.
typedef NS_ENUM(NSInteger, MyEnum) {
MyEnumValue1,
MyEnumValue2
};
Can you use NS_ENUM instead? And then try JTTableViewCellEditingState.JTTableViewCellEditingStateLeft or even .JTTableViewCellEditingStateLeft to access your enum. If you can't change it to NS_ENUM, please have a look at Using non NS_ENUM objective-C enum in swift
In my environment - Xcode Version 6.1.1 (6A2006):
typedef enum {
JTTableViewCellEditingStateMiddle,
JTTableViewCellEditingStateLeft,
JTTableViewCellEditingStateRight,
} JTTableViewCellEditingState;
is exported to Swift as:
struct JTTableViewCellEditingState {
init(_ value: UInt32)
var value: UInt32
}
var JTTableViewCellEditingStateMiddle: JTTableViewCellEditingState { get }
var JTTableViewCellEditingStateLeft: JTTableViewCellEditingState { get }
var JTTableViewCellEditingStateRight: JTTableViewCellEditingState { get }
So, this should works:
func gestureRecognizer(gestureRecognizer: JTTableViewGestureRecognizer!, commitEditingState state: JTTableViewCellEditingState, forRowAtIndexPath indexPath: NSIndexPath!) -> Void {
if state.value == JTTableViewCellEditingStateLeft.value {
// ...
}
}