How can I call + (BOOL)resolveInstanceMethod:(SEL)aSEL while changing dynamic variable variable name?
E.g.
I've got an ObjC class:
#interface SomeClass: NSObject
///
#end
#implementation SomeClass: NSObject
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
//// code
}
#end
Swift
#objcMembers
public final class SomeSwiftClass: SomeClass {
public dynamic var value: NSNumber?
public dynamic func mmFunc() {
print("mmFunc")
}
}
code:
let anInstance = SomeSwiftClass()
anInstance.value = NSNumber(10) /// how can I call resolveInstanceMethod? Doesn't work
anInstance.mmFunc() //doesn't work too.
If I implement the same code in ObjC it works.
Any ideas?
All the following works w/o changes in your classes (tested with Xcode 11.2 / iOS 13.2).
func test() {
let anInstance = SomeSwiftClass()
let hasMethod = SomeSwiftClass.resolveInstanceMethod(#selector(SomeSwiftClass.mmFunc))
print("Result: \(hasMethod)")
anInstance.value = NSNumber(10)
anInstance.mmFunc()
}
if not at your side, see if objc-swift bridging is correctly configured and all needed includes are added.
Update: compiler was confused by ambiguous name of value, changing it to other more specific name works as below
#interface SomeClass: NSObject
#property (nonatomic) BOOL isValid;
#end
#objc
public class SomeSwiftClass: SomeClass {
#objc public var specificValue: NSNumber = NSNumber(5)
#objc public func mmFunc() {
print("mmFunc")
}
}
#objc
final class Runner: NSObject {
#objc public static func test() {
let anInstance = SomeSwiftClass()
let hasProperty = SomeSwiftClass.resolveInstanceMethod(#selector(getter: SomeSwiftClass.isValid))
print("Has property: \(hasProperty)")
let swiftProperty = SomeSwiftClass.resolveInstanceMethod(#selector(getter: SomeSwiftClass.specificValue))
print("Swift property: \(swiftProperty)")
let hasMethod = SomeSwiftClass.resolveInstanceMethod(#selector(SomeSwiftClass.mmFunc))
print("Has method: \(hasMethod)")
anInstance.specificValue = NSNumber(10)
anInstance.mmFunc()
}
}
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 attempting to slowly migrate an Objective C app over to Swift and have started to create new classes -
public class MapsAPI : NSObject {
let delegate: MapsAPIResponseDelegate
public init(managerWithDelegate delegate: MapsAPIResponseDelegate) {
self.delegate = delegate
}
}
Now in my Objective C .m file I've declared #import MyTarget-Swift.h and in my .h I've added #class MapsAPI which all seems fine however I'm not sure what the Objective C initialisation code should look like. I've tried -
MapsAPI *api = [[MapsAPI alloc] initWithManagerWithDelegate: self];
But that errors with -
No visible #interface for 'MapsAPI' declares the selector
'initWithManagerWithDelegate:'
I've tried looking at the definition of my MyTarget-Swift.h but all that shows is -
SWIFT_CLASS("_TtC4What7MapsAPI")
#interface MapsAPI : NSObject
- (nonnull instancetype)init SWIFT_UNAVAILABLE;
#end
Is there something I'm doing wrong here?
You may choose to add #objcMembers to your class declaration:
public class #objcMembers MapsAPI : NSObject {
let delegate: MapsAPIResponseDelegate
public init(managerWithDelegate delegate: MapsAPIResponseDelegate) {
self.delegate = delegate
}
}
Alternatively (or additionally... who am I to judge) you can mark your initializer as being exposed to Objective-C
public class MapsAPI : NSObject {
let delegate: MapsAPIResponseDelegate
#objc public init(managerWithDelegate delegate: MapsAPIResponseDelegate) {
self.delegate = delegate
}
}
And if you want to, you can also explicitly define the Objective-C selector used:
public class MapsAPI : NSObject {
let delegate: MapsAPIResponseDelegate
#objc(initManagerWithDelegate:)
public init(managerWithDelegate delegate: MapsAPIResponseDelegate) {
self.delegate = delegate
}
}
We are trying to reference Swift methods inside an Objective-C implementation.
Swift class:
import Foundation
#objc class MySwiftClass: NSObject {
override init() {
super.init()
}
func sayHello() -> Void {
print("hello");
}
func addX(x:Int, andY y:Int) -> Int {
return x+y
}
}
Objective-C implementation (Objective-c.m):
#import "ProductModuleName-Swift.h"
MySwiftClass* getData = [[MySwiftClass alloc]init];
[getData sayHello] //works
[getData addX:5 addY:5] //No visible #interface for 'MySwiftClass' declares selector 'addX:addY'
The last line of code gives the following error:
No visible #interface for 'MySwiftClass' declares selector 'addX:addY'
If you command-click on "ProductModuleName-Swift.h" in the Xcode
source file editor then you can see how the Swift methods are mapped to Objective-C.
In your case that would be
#interface MySwiftClass : NSObject
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
- (void)sayHello;
- (NSInteger)addXWithX:(NSInteger)x andY:(NSInteger)y;
#end
which is called as
MySwiftClass* getData = [[MySwiftClass alloc]init];
[getData sayHello];
NSInteger result = [getData addXWithX:5 andY:5];
A better Swift 3 method name might be
func add(x: Int, y:Int) -> Int
because x is already the argument (external) name of the first
parameter. You can also add an #objc() attribute to the Swift definition
to control the Objective-C name. For example, with
#objc(addX:andY:)
func add(x: Int, y: Int) -> Int {
return x+y
}
it would be called from Objective-C as
NSInteger result = [getData addX:5 andY:5];
As #ekscrypto pointed out, in Swift 4 and later you need to annotate individual functions with #objc. Prior to that, a single, class-level #objc was enough.
Of course in Objective-C class you must add import of NAME_PROJECT-swift.h.
If your project name is Sample then you must add:
#import Sample-swift.h
And then:
Swift 4 or Less
#objc class MySwiftClass: NSObject {
func sayHello(){
//function
}
func addX(){
//function
}
}
Swift 4 or Greater
#objc class MySwiftClass: NSObject {
#objc func sayHello(){
//function
}
#objc func addX(){
//function
}
}
In my case I had forgotten to add:
#import "MyProject-Swift.h"
Into the obj c file.
Is it possible to define a method for a property on a Swift class
For example, I have a class named Test in Objective-C, and this class has one property called testProperty of NSString type
Test.h
#interface Test : NSObject
#property (nonatomic, readonly) NSString *testProperty;
#end
I define a getter method in the implementation for this property:
Test.m
#import "Test.h"
#implementation Test
-(NSString*)testProperty
{
return #"testing property like method";
}
#end
How can I do the same in Swift? If make the class in Swift,
import UIKit
class Test: NSObject {
var testProperty : String?
func testProperty() -> String {
return "testing property like method"
}
}
then I receive an error:
"Invalid redeclaration of testProperty()"
These are called Computed properties
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html
Example
var testProperty: String {
return "testing property like method"
}
Subclass
class ClassA: NSObject {
var testProperty: String {
return "This is Class A"
}
}
class ClassB: ClassA {
override var testProperty: String {
return "This is Class B"
}
}
let test = ClassB()
print(test.testPropery) // prints "This is Class B"
I'm having some trouble accessing a Swift Singleton from Objective-C.
#objc class SingletonTest: NSObject {
// swiftSharedInstance is not accessible from ObjC
class var swiftSharedInstance: SingletonTest {
struct Singleton {
static let instance = SingletonTest()
}
return Singleton.instance
}
}
swiftSharedInstance can not be reached.
Nicky Goethlis's answer is correct but I just want to add another way of Singleton creation termed as One line Singleton" in Swift which I came across recently and it does not use Struct:
Singleton.swift
#objc class Singleton: NSObject {
static let _singletonInstance = Singleton()
private override init() {
//This prevents others from using the default '()' initializer for this class.
}
// the sharedInstance class method can be reached from ObjC. (From OP's answer.)
class func sharedInstance() -> Singleton {
return Singleton._singletonInstance
}
// Some testing
func testTheSingleton() -> String {
return "Hello World"
}
}
SomeObjCFile.m
Singleton *singleton = [Singleton sharedInstance];
NSString *testing = [singleton testTheSingleton];
NSLog(#"Testing---> %#",testing);
Swift 5 and above
final class Singleton: NSObject {
#objc static let shared = Singleton()
#objc var string: String = "Hello World"
private override init() {}
}
use in Objective-C
#import <ProjectName-Swift.h> // change ProjectName to actual project name
NSLog("Singleton String = %#", [Singleton shared].string);
For now I have the following solution. Maybe I am overlooking something that would enable me to access "swiftSharedInstance" directly?
#objc class SingletonTest: NSObject {
// swiftSharedInstance is not accessible from ObjC
class var swiftSharedInstance: SingletonTest {
struct Singleton {
static let instance = SingletonTest()
}
return Singleton.instance
}
// the sharedInstance class method can be reached from ObjC
class func sharedInstance() -> SingletonTest {
return SingletonTest.swiftSharedInstance
}
// Some testing
func testTheSingleton() -> String {
return "Hello World"
}
}
Then in ObjC I can get the sharedInstance class method (after importing the xcode generated swift header bindings)
SingletonTest *aTest = [SingletonTest sharedInstance];
NSLog(#"Singleton says: %#", [aTest testTheSingleton]);
To make members of the SingletonTest class accessible (swiftSharedInstance is a member of this class), use #objcMembers modifier on the class, or add #objc modifier directly on the swiftSharedInstance:
#objc #objcMembers class SingletonTest: NSObject {
// swiftSharedInstance is not accessible from ObjC
class var swiftSharedInstance: SingletonTest {
struct Singleton {
static let instance = SingletonTest()
}
return Singleton.instance
}
}
Or:
#objc class SingletonTest: NSObject {
// swiftSharedInstance is not accessible from ObjC
#objc class var swiftSharedInstance: SingletonTest {
struct Singleton {
static let instance = SingletonTest()
}
return Singleton.instance
}
}
After creating the Bridging header, be sure to have the Objective-C Generated Interface Header Name set in your Build Settings from your app target. If the value is empty, add the following value:
$(SWIFT_MODULE_NAME)-Swift.h
You need add #objc property wrapper to your singleton:
#objc final class Singleton: NSObject {
#objc static let sharedInstance = Singleton()
#objc func foo() { }
}
Then, in the Objective-C class, import the following:
// Replace the "App" with your Target name.
#import "App-Swift.h"
Finally, after compiling the project, you will be able to use your singleton from Swift inside your Objective-C class:
[[Singleton sharedInstance]foo];
You pretty much have it. To use Swift classes in Obj-C you both need to #import "SingletonTest-Swift.h the generated header or forward declaration with #class MySwiftClass.
Additionally the class needs to inherit from an Obj-C class like you have don here with NSObject or be marked with #objc to expose it. You don't need to do both though, #objc is there to be a more granular option when choosing things to expose.
Apple has some good documentation on all of this and there are two different WWDC sessions you can watch on the topic of Obj-C interoperability as well.
Don't forget to set sharedInstance as public
public final class TestSwiftMain: NSObject {
#objc public static let sharedInstance = TestSwiftMain()
private override init() {}
#objc public func test() {
print("testing swift framework")
}
}
Using it in Objc
[[testSwiftMain sharedInstance] test];
Update 12 Oct 2022
ObcMember So you won't have to write objC behind every function
Swift Class
#objcMembers class SwiftHelpingExtentions: NSObject {
static let instanceShared = SwiftHelpingExtentions()
func testingMethod() {
print("testing")
}
}
}
On objective C View Controller
import : #import "App-Swift.h"
Call The method:
[SwiftHelpingExtentions.instanceShared testingMethod];