Just trying to get started with Swift and hit the following issue when upgrading to Swift 1.2:
#protocol MyObjcProtocol <NSObject>
#optional
#property (copy) NSString *optionalString;
- (void) optionalMethod;
#end
...
class MySwiftClass: NSObject {}
extension MySwiftClass: MyObjcProtocol {
var optionalString: NSString {
get { return "Foo" }
set(newValue) { NSLog("Whatever") }
}
// No problem here
func optionalMethod() {
NSLog("Bar")
}
}
The Swift extension implementing the Objc protocol doesn't compile with:
Objective-C method 'optionalString' provided by getter for
'optionalString' conflicts with optional requirement getter for
'optionalString' in protocol 'MyObjcProtocol'...
Objective-C method 'setOptionalString:' provided by setter for
'optionalString' conflicts with optional requirement setter for
'optionalString' in protocol 'MyObjcProtocol'...
So clearly the compiler doesn't realise I'm trying to implement the optionals from the protocol, and thinks I'm stomping on the protocol's expected ObjC symbols. The optional method func optionalMethod() compiles just fine, however. Remove #optional from the protocol and everything compiles just fine, but it's not always possible or desirable to do that as a solution.
So, how does one implement this? Trying to implement the expected ObjC methods explicitly doesn't work either:
func optionalString() {
return "foo"
}
func setOptionalString(newValue: NSString) {
NSLog("")
}
Hope someone can help! Thanks in advance!
MyObjcProtocol protocol is translated to Swift as:
protocol MyObjcProtocol : NSObjectProtocol {
optional var optionalString: String! { get set }
optional func optionalMethod()
}
So you can just use String instead of NSString.
extension MySwiftClass: MyObjcProtocol {
var optionalString: String {
get { return "Foo" }
set(newValue) { NSLog("Whatever") }
}
func optionalMethod() {
NSLog("Bar")
}
}
I guess the problem is that the protocol requires a stored property because it's specified copy, but the swift extension cannot have storage for a property
Optional properties in Objective C is equivalent to Optional Type in Swift. So it would translate like this:
var optionalString: String! // = false
in case it can be nil:
var optionalString: String? // = nil
Related
I'm trying to observe a property (which is declared within an Objective-C) in Swift.
Objective-C protocol:
#protocol DemoViewModel <NSObject>
#property (nonatomic, strong) NSString *bla;
#end
Swift observe:
#objc public dynamic var vm: (NSObject & DemoViewModel) {
didSet {
vm.observe(#keyPath(DemoViewModel.bla)) { _,_ in
//do something
}
}
}
Interestingly I receive an error:
Member ‘observe’ cannot be used on value of protocol type ‘NSObject & DemoViewModel’; use a generic constraint instead
Any idea what's going on?
Thanks
There must be something special under the hood about the observe function, since all the functions and methods can be accessed your way. It's asking you to instead create something like this:
class Foo {
#objc public dynamic var vm: (NSObject & DemoViewModel)?
private var observer: NSKeyValueObservation?
func setVM<T>(_ vm: T) where T: NSObject &: DemoViewModel {
self.vm = vm
observer = vm?.observe(\.bla, options: [.old, .new]) { _, _ in
// do something
}
}
}
When you implement DemoViewModel, remember that you need to mark bla as dynamic, otherwise the observer won't be called.
class Bar: NSObject, DemoViewModel {
#objc dynamic var bla: String = ""
}
I'm trying to access Swift class methods from Objective-C, following this guide
https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html
This is my Swift class:
import Foundation
#objc public class MySwiftClass: NSObject {
// modifiers public, open do not help
func Hello()->String {
return "Swift says hello!";
}
}
And here is an excerpt from the autogenerated "umbrella" file MyProductModuleName-Swift.h
SWIFT_CLASS("_TtC25_MyProductModuleName12MySwiftClass")
#interface MySwiftClass : NSObject
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
#end
It seems like XCode completely ignores methods of MySwiftClass, and as a result I can't access them from Objective-C. Xcode version is 9.2.
Is it a bug or I missed anything?
You need to add #objc before each function you want to access from Objective-C:
#objc public class MySwiftClass: NSObject {
// modifiers public, open do not help
#objc func Hello() -> String {
return "Swift says hello!";
}
}
I have 2 versions of the same model in the project (and I can't get rid of the legacy one). It is Customer (legacy code) and struct CustomerModel - modern Swift implementation of the model.
I have a custom UITableViewCell which used to have setup(withCustomer: CustomerModel) method. It worked well for a new model, but now I need to use legacy one to setup same cells.
I decided to define CustomerDisplayable protocol and make both models conform it.
Here is the code:
Customer.h
#interface Customer : NSObject
#property (nonatomic, strong) NSString* name;
#property (nonatomic, strong) NSString* details;
#end
CustomerModel.swift
struct CustomerModel {
let name: String
let details: String?
init(withJSON json: [String: Any]) {
name = json["name"] as! String
details = json["details"] as? String
}
}
CustomerDisplayable.swift
protocol CustomerDisplayable {
var name: String { get }
var details: String? { get }
var reviewCount: Int { get }
var reviewRating: Double { get }
}
extension Customer: CustomerDisplayable {
var reviewCount: Int { return 100 }
var reviewRating: Double { return 4.5 }
}
extension CustomerModel: CustomerDisplayable {
var reviewCount: Int { return 100 }
var reviewRating: Double { return 4.5 }
}
I expected that as Customer.h has already properties name & details - it will conform this protocol and extension above will work. But I get a compiling error in my extension:
Type 'Customer' does not conform to protocol 'CustomerDisplayable'.
Xcode offers a quick fix - Protocol requires property 'name' with type 'String'; do you want to add a stub.
If I agree Xcode add stubs I end up with name and details computable getters but Xcode shows new compile errors:
extension Customer: CustomerDisplayable {
var details: String? {
return "test"
}
var name: String {
return "test"
}
var reviewCount: Int { return 100 }
var reviewRating: Double { return 4.5 }
}
'details' used within its own type
Getter for 'name' with Objective-C selector 'name' conflicts with the previous declaration with the same Objective-C selector
Any ideas how to solve this problem? I really want to have this protocol and abstract interface for both model representations.
The only solution I came to is to rename properties in CustomerDisplayable
NOTE: Real models are much more complex, but this code is demonstrating the problem.
Hmm, this looks, IMO, like something that probably ought to be considered a bug in the Swift compiler. I'd suggest filing a report at http://bugs.swift.org .
Here's what appears to be going on:
As you've noticed, Swift doesn't seem to notice when an Objective-C selector fulfills a retroactive protocol requirement, which is the part I'd probably file as a bug.
When you explicitly try to add name and details properties to your extension, Swift 3 notices that the extension is on an NSObject subclass and automatically exposes the properties to Objective-C. Objective-C, of course, can't have two methods with the same selector, so you get the error you've been seeing. Swift 4 doesn't automatically expose everything to Objective-C anymore, so you won't get this error there, and in Swift 3 you can work around this by adding the #nonobjc keyword. But then:
Once you do add the property in an extension, it shadows the original Objective-C property, making it hard to get at the correct value to return in the property.
Unfortunately, I can't think of a clean workaround, although I can think of an ugly, hacky one involving the Objective-C runtime. My favorite part is the way we have to use string-based NSSelectorFromString since #selector will choke from the presence of the #nonobjc shadowed property. But it works:
extension Customer: CustomerDisplayable {
#nonobjc var details: String? {
return self.perform(NSSelectorFromString("details")).takeUnretainedValue() as? String
}
#nonobjc var name: String {
return self.perform(NSSelectorFromString("name")).takeUnretainedValue() as? String ?? ""
}
}
Again, I'd recommend filing a bug report so that we don't have to do crap like this in the future.
EDIT: Never mind all this! I'm wrong. Disregard everything I said. The only reason the retroactive protocol didn't work was because you didn't have nullability specifiers in your Objective-C class, so Swift didn't know whether they could be nil or not and thus interpreted types as String!. Should have noticed that, d'oh d'oh d'oh. Anyway, I don't know if you're able to edit the original Objective-C class's definition to add nullability specifiers, but if you can, the original protocol will work fine retroactively with no hacks.
#interface Customer : NSObject
#property (nonatomic, nonnull, strong) NSString* name;
#property (nonatomic, nullable, strong) NSString* details;
- (nonnull instancetype)initWithName:(nonnull NSString *)name details: (nonnull NSString *)details;
#end
NSString in Objective-C is different than the native Swift value type String. Conversion rules I find are too obscure to memorize. In this case it appears the bridging brings in NSString * as: ImplicitlyUnwrappedOptional<String>
So if you don't mind the legacy Customer type dictating some aspects of your Swift code, change types of name and details to String! everywhere and all is well.
Otherwise, you'll have to change your protocol to have new names, say displayName and displayDetails, and reference the underlying properties. Presumably you have the freedom to do this and you achieve what is perhaps an important abstraction layer anyway. Then just dutifully list all the obvious implementations of all four properties in each extension block. Because the conversion from String! to String or String? is automatic, the code looks a bit trivial and bloated, but it also works fine.
I am trying to provide an OS X service, but I am not sure how to translate the following Objective C documentation example to Swift. Any pointers (forgive the pan) would be appreciated!
From Apple's documentation (section 'Implementing the Service Method'):
- (void)simpleEncrypt:(NSPasteboard *)pboard userData:(NSString *)userData error:(NSString **)error {
//...
}
Is this the equivalent in Swift?
func simpleEncrypt(pboard: NSPasteboard!, userData: String?, error: AutoreleasingUnsafeMutablePointer<String?>) {
//...
}
Or should it be:
func simpleEncrypt(pboard: NSPasteboard, userData: String, inout error: String?) {
//...
}
Or indeed something entirely different?
Upon more careful reading of the documentation:
import AppKit
#objc public class Servicer: NSObject {
#objc public func service(pboard: NSPasteboard?, userData: String?, error: AutoreleasingUnsafeMutablePointer<NSString?>) {
error.memory = "Not yet implemented!"
}
}
This is ported to obj-c in "Servicer-Swift.h" as:
SWIFT_CLASS("_TtC8Servicer8Servicer")
#interface Servicer : NSObject
- (void)service:(NSPasteboard *)pboard userData:(NSString *)userData error:(NSString * *)error;
- (instancetype)init OBJC_DESIGNATED_INITIALIZER;
#end
pboard can probably be an implicitly unwrapped optional (NSPasteboard!) or even a plain reference (NSPasteboard), because it is very unlikely that the os will call this method with a nil pasteboard...
if I declare the swift constant as a global constant like:
let a = "123"
but the a cannot be found in objective c.
How to solve this?
From Apple Doc:
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
Therefore its not possible to access global variables(Constants) or global functions defined in Swift.
Possible Solutions:
From the Apple Document Swift programming language, You can Declare Type Properties as
class var constant: Int = {
return 10
}()
But currently in Swift(beta-3) Type properties are not supported.
You can declare a Class function to get a constant value:
In Swift:
class func myConst() -> String {
return "Your constant"
}
Accessing from Objective-C:
NSString *constantValue = [ClassName myConst];
NSLog(#"%#", constantValue);
Swift code:
public class MyClass: NSObject {
public static let myConst = "aConst"
}
and then in Objective-C:
[MyClass myConst]
Isn't this working as well? As in this works for me.
Also this is somewhat shorter as creating a object first (alloc, init). Making a new function for every constant is... not pretty :/
Update for Swift 4
Because of the changes in Swift 4's Objective-C inference, you need to add the #objc annotation to the declared constant as well. The previous declaration then becomes:
#objcMembers
public class MyClass: NSObject {
public static let myConst = "aConst"
}
The calling Objective-C code remains the same.
Using #objcMembers makes all constants available (as if you'd write #objc before each constant), but I've had times where the compiler somehow wouldn't generate the corresponding ObjC code.
In those cases I'd suggest adding the #objc decorator before the constant as well.
I.e.: #objc public static let myConst = "aConst"
You should not have any problem by using let in Objective-C, next example was made with Xcode 7.2 :
MyClass.swift
import Foundation
import UIKit
#objc class MyClass : NSObject { // <== #objc AND NSObject ARE BOTH NECESSARY!!!
let my_color = UIColor( red:128/255,green:32/255,blue:64/255,alpha:1 ) // <== CONSTANT!!!
}
MyObjectiveC.m
#import "PROJECTNAME-Swift.h" // <== NECESSARY TO RECOGNIZE SWIFT CLASSES!!!
#interface MyObjectiveC ()
#end
#implementation MyObjectiveC
#synthesize tableview; // <== ANY UI OBJECT, JUST AS EXAMPLE!!!
- (void) viewDidLoad () {
MyClass * mc = [ [ MyClass alloc ] init ]; // <== INSTANTIATE SWIFT CLASS!!!
tableview.backgroundColor = mc.my_color; // <== USE THE CONSTANT!!!
}
#end
PROJECTNAME is the name of your Xcode project, as shown in Project Navigator.
In your swift class,
let constant: Float = -1
class YourClass: NSObject {
class func getMyConstant() -> Float {return constant}
...
}
Clean, build to let xcode prepare this method useable for obj-c.
Then at your obj-c class
if ([YourClass getMyConstant] != 0) {
...
}
First of all you need to know about the important of auto-generated Swift header file.
It is the one that will made the magic to transcribe the Swift code to be understandable from Objective-C.
This file is auto-generated by Xcode (do not look for it in your project).
The important of this file is to use the correct name, it can match with your target name, but, may not, it is the product module name. (Search for it in your project settings as "Product module")
You need to import this file on the Objective-C class that you want to use a Swift class and also the Swift class name of your Swift file.
#import <ProductModuleName-Swift.h>
#class MySwiftClassName;
My Swift class should have the prefix #objc and inherit from NSObject:
#objc class MySwiftClassName: NSObject {
let mySwiftVar = "123"
}
Then you can call your Swift variable from the Objective-C file:
MySwiftClassName *mySwiftClassO = [[MySwiftClassName alloc] init];
NSString *myVar = mySwiftClassO.mySwiftVar;
Make sure to clean and rebuild your project after each change to force regenerate this auto-generated file.
If your Swift header file was auto-generated correctly you can navigate to it by clicking over the import file name and check if all the code you need was properly transcribed.
In the following post you can find more detailed information about this. https://solidgeargroup.com/bridging-swift-objective-c
Classes func don't work. The only solution I have found out is this one:
class YourController: NSObject {
#objc static let shared = YourController()
private override init() { }
#objc class func sharedInstance() -> YourController {
return YourController.shared
}
#objc let terms = "Your-String-here"
And then on Obj-c file:
[[YourController sharedInstance].terms]