Cannot conform to swift delegate protocol from objective-c - ios

I'm using a swift pod in an objective C project.
I successfully access swift classes from objective-c, anyway I'm facing an issue to adopt swift protocol.
The compiler does not complain, generated bridging header seems correct.
Problem occurs at runtime when calling [self.sceneLocationView setLocationNodeTouchDelegate: self]; that results in [ARCL.SceneLocationView setLocationNodeTouchDelegate:]: unrecognized selector sent to instance.
I cannot find any solution to this issue, I spent time to search here, but still no luck to make this working.
Any help would be greatly appreciated.
Thanks
My swift and objective-c files are shown below.
SceneLocationView.swift:
#available(iOS 11.0, *)
open class SceneLocationView: ARSCNView {
...
#objc public weak var locationNodeTouchDelegate: LNTouchDelegate?
...
}
SceneLocationViewDelegate.swift :
#objc public protocol LNTouchDelegate: class {
func locationNodeTouched(node: AnnotationNode)
}
ViewController.h :
#import "ARCL-Swift.h"
#interface ViewController : UIViewController <LNTouchDelegate>{
}
#property (nonatomic, strong) SceneLocationView *sceneLocationView;
ViewController.m :
#import "ARCL-Swift.h"
#import "ViewController.h"
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.sceneLocationView = [[SceneLocationView alloc] init];
[self.sceneLocationView run];
[self.view addSubview: self.sceneLocationView];
...
[self.sceneLocationView setLocationNodeTouchDelegate: self]; <-- ERROR HERE
}
-(void) locationNodeTouchedWithNode:(AnnotationNode *)node {
}
ARCL-Swift.h :
SWIFT_PROTOCOL("_TtP4ARCL15LNTouchDelegate_")
#protocol LNTouchDelegate
- (void)locationNodeTouchedWithNode:(AnnotationNode * _Nonnull)node;
#end
SWIFT_CLASS("_TtC4ARCL17SceneLocationView") SWIFT_AVAILABILITY(ios,introduced=11.0)
#interface SceneLocationView : ARSCNView
#property (nonatomic, weak) id <LNTouchDelegate> _Nullable locationNodeTouchDelegate;
- (nonnull instancetype)init;
- (nonnull instancetype)initWithFrame:(CGRect)frame options:(NSDictionary<NSString *, id> * _Nullable)options OBJC_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)aDecoder OBJC_DESIGNATED_INITIALIZER;
- (void)layoutSubviews;
- (nonnull instancetype)initWithFrame:(CGRect)frame SWIFT_UNAVAILABLE;
#end

Related

Can't see Swift variable/method in Objective C

I am unable to see the method/variable to set myVar to true from Objective C even tough I've added the #objc and public modifiers and there is no Bool in the setMyVarTrue() method's signature.
This is probably caused by the difference between Swift's Bool and Objective C's BOOL.
Other class methods/variables are visible, just this particular one isn’t.
How is is possible to set Swift's Bool from Objective C?
Swift code:
public class MyViewController : UIViewController {
public var myVar:Bool = false
#objc public func setMyVarTrue() {
self.myVar = true
}
}
Objective C code:
MyViewController* myViewController = [MyViewController new];
myViewController.myVar = true // Variable not found
myViewController.setMyVarTrue() // Method not found
[self presentViewController:myViewController animated:NO completion:nil];
You should do
add #objc to declaration of your class
add a file named "yourName-swift.h", which should containt #class yourClass;
import "yourName-swift.h" in Objective C file, where you want to call
your swift class
The solution was to clean and rebuild the project. Xcode generates the swift bridging header automatically and in my case it was stopping the build process with a (variable/method not found) error prior to regenerating the bridging header.
SWIFT_CLASS("_TtC11MyProject25MyViewController")
#interface MyViewController : UIViewController
#property (nonatomic, strong) MBProgressHUD * _Nonnull progressIndicator;
- (nonnull instancetype)initWithNibName:(NSString * _Nullable)nibNameOrNil bundle:(NSBundle * _Nullable)nibBundleOrNil OBJC_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)aDecoder OBJC_DESIGNATED_INITIALIZER;
- (void)viewDidLoad;
- (void)viewWillAppear:(BOOL)animated;
- (void)submitProgressHandler:(NSNotification * _Nonnull)notification;
- (void)submitSuccessHandler;
- (void)submitFailureHandler:(NSNotification * _Nonnull)notification;
- (void)subscribe;
- (void)unsubscribe;
#end
Now it works fine:
SWIFT_CLASS("_TtC11MyProject25MyViewController")
#interface MyViewController : UIViewController
#property (nonatomic, strong) MBProgressHUD * _Nonnull progressIndicator;
#property (nonatomic) BOOL closing;
- (nonnull instancetype)initWithNibName:(NSString * _Nullable)nibNameOrNil bundle:(NSBundle * _Nullable)nibBundleOrNil OBJC_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)aDecoder OBJC_DESIGNATED_INITIALIZER;
- (void)viewDidLoad;
- (void)viewWillAppear:(BOOL)animated;
- (void)setClosingTrue;
- (void)submitProgressHandler:(NSNotification * _Nonnull)notification;
- (void)submitSuccessHandler;
- (void)submitFailureHandler:(NSNotification * _Nonnull)notification;
- (void)subscribe;
- (void)unsubscribe;
#end

Property Inheritance in Objective C

I want to inherit my base class properties and methods which will be used by my several derived classes. I want these properties and methods to be exactly protected so that they will only be visible in derived class and not to any external class. But it always gives me some errors.
#interface BasePerson : NSObject
#end
#interface BasePerson ()
#property (nonatomic, strong) NSMutableArray<Person*>* savedPersons;
#property (nonatomic) BOOL shouldSavePerson;
#end
#interface DerivedPerson1 : BasePerson
#end
#implementation DerivedPerson1
- (instancetype)init
{
if (self = [super init]) {
self.savedPersons = [NSMutableArray array];
self.shouldSavePerson = NO;
}
return self;
}
It always gives me an error that
Property 'savedPersons' not found on object of type 'DerivedPerson1 *'
Property 'shouldSavePerson' not found on object of type 'DerivedPerson1 *'
How i can make use of inheritance in Objective C, I don't want savedPersons and shouldSavePerson properties to be visible to external classes. I only want them to visible in my base class and all the derived classes.
Any help will be great. Thanks
This is not something that the objectiveC really support. There are some ways though. So lets see.
If you put a property in the source file class extension then it is not exposed and you can not access it in the subclass either.
One way is to put all of the subclasses into the same source file as the base class. This is not a good solution at all as you do want to have separate files for separate classes.
It seems logical to import the BaseClass.m in the SubClass source file but that will produce a linker error saying that you have duplicate symbols.
And the solution:
Separate the extension into a separate header. So you have a MyClass
Header:
#import <Foundation/Foundation.h>
#interface MyClass : NSObject
#end
Source:
#import "MyClass.h"
#import "MyClassProtected.h"
#implementation MyClass
- (void)foo {
self.someProperty = #"Some text from base class";
}
#end
Then you create another header file (only the header) MyClassProtected.h which has the following:
#import "MyClass.h"
#interface MyClass ()
#property (nonatomic, strong) NSString *someProperty;
#end
And the subclass MyClassSubclass
Header:
#import "MyClass.h"
#interface MyClassSubclass : MyClass
#end
And the source:
#import "MyClassSubclass.h"
#import "MyClassProtected.h"
#implementation MyClassSubclass
- (void)foo {
self.someProperty = #"We can set it here as well";
}
#end
So now if the user MyClassSubclass he will not have the access to the protected property which is essentially what you want. But the downside is the user may still import MyClassProtected.h after which he will have the access to the property.
Objective-C doesn't have member access control for methods, but you can emulate it using header files.
BasePerson.h
#interface BasePerson : NSObject
#property (strong,nonatomic) SomeClass *somePublicProperty;
-(void) somePublicMethod;
#end
BasePerson-Private.h
#import "BasePerson.h"
#interface BasePerson ()
#property (nonatomic, strong) NSMutableArray<Person*>* savedPersons;
#property (nonatomic) BOOL shouldSavePerson;
#end
BasePerson.m
#import "BasePerson-Private.h"
...
DerivedPerson1.h
#import "BasePerson-Private.h"
#inteface DerivedPerson1 : BasePerson
...
#end
Now any class that #imports BasePerson.h will only see the public methods. As I said though, this is only emulating access control since if a class #imports *BasePerson-Private.h" they will see the private members; this is just how C/Objective-C is.
We can achieve using #protected access specifier
#interface BasePerson : NSObject {
#protected NSMutableArray *savedPersons;
#protected BOOL shouldSavePerson;
}
DerivedPerson1.m
#implementation DerivedPerson1
- (instancetype)init
{
if (self = [super init]) {
self->savedPersons = [NSMutableArray array];
self->shouldSavePerson = NO;
}
return self;
}
#end
OtherClass.m
#import "OtherClass.h"
#import "BasePerson.h"
#implementation OtherClass
- (void)awakeFromNib {
BasePerson *base = [[BasePerson alloc]init];
base->savedPersons = #[];//Getting Error. Because it is not a subclass.
}
#end

I need to pass a string from a NSObject class to a UIViewController with a delegate

I need to pass a string from a NSObject class to a UIViewController, I understand that the best way is delegation but the delegate method isn't being called. I'm trying to set the UILabel an DieFacesViewController as the selectedOption from TemporarySelection.
A tableview shows the value of CustomOptionStore, once it's tapped passes its value to TemporarySelection and opens the modal view DieFacesViewCountroller which should, at least in my mind, take the label value from TemporarySelection. The reason I created TemporarySelection is because the DieFacesViewController will be used by other classes, not only by CustomOptionStore, and it will need to load the label from all those classes when different tableViews are selected.
I tried to set the delegate as self in both viewDidLoad and viewWillAppear with no luck, I don't understand if the view loads before being able to call the delegate method or if there's something wrong the way I set the method up.
I've been stuck here for two days, this is the first time I post a question so please forgive me if it's a bit confused.
my delegator class TemporarySelection.h is
#import <Foundation/Foundation.h>
#import "CustomOptionsStore.h"
#class DieFacesViewController;
#protocol TemporarySelectionDelegate <NSObject>
-(void)sendSelection;
#end
#interface TemporarySelection : NSObject
#property (nonatomic, weak) id <TemporarySelectionDelegate> delegate;
#property (nonatomic, strong) NSString *selectedOption;
-(void)addSelection: (CustomOptionsStore *) selection;
#end
and my TemporarySelection.m is
#import "TemporarySelection.h"
#implementation TemporarySelection
-(void)addSelection: (CustomOptionsStore *) selection{
self.selectedOption = selection.description;
[self.delegate sendSelection];
}
#end
the delegate class DiewFacesViewController.h is
#import <UIKit/UIKit.h>
#import "SelectedStore.h"
#import "TemporarySelection.h"
#interface DieFacesViewController : UIViewController <TemporarySelectionDelegate>
#property (strong, nonatomic) IBOutlet UILabel *SelectionName;
#end
and the DieFacesViewController.m is
#import "DieFacesViewController.h"
#interface DieFacesViewController ()
#end
#implementation DieFacesViewController
- (void)viewDidLoad {
TemporarySelection *ts = [[TemporarySelection alloc]init];
ts.delegate = self;
[super viewDidLoad];
}
-(void)sendSelection{
TemporarySelection *ts = [[TemporarySelection alloc]init];
self.SelectionName.text = ts.selectedOption;
}
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:YES];
}
You are not setting the delegate object properly.Check the above code
#import "DieFacesViewController.h"
#interface DieFacesViewController ()<TemporarySelectionDelegate>
{
//global object
TemporarySelection *ts;
}
#end
#implementation DieFacesViewController
- (void)viewDidLoad {
ts = [[TemporarySelection alloc]init];
ts.delegate = self;
[super viewDidLoad];
}
-(void)sendSelection{
//Use the object to extract
self.SelectionName.text = ts.selectedOption;
}
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:YES];
}

Objective-c protocol to Swift class. unrecognized method

I'm trying to migrate some code from Objective-c to Swift, but i have problems at the beggining when i want to conform a Swift class with a Objective-c protocol and access this class from a objetive-c class. I'm doing something wrong, but i don't see it.
Objective-c protocol and obj-c class for testing (test_class.h)
#import <Foundation/Foundation.h>
#protocol test_delegate
-(void)returnData:(NSString*)data InMethod:(NSString*)method;
#end
#interface test_class : NSObject
#property (weak, nonatomic) id<test_delegate> delegate;
-(void)sendData:(NSString * )data;
#end
Objective-c implementation
#import "test_class.h"
#implementation test_class
-(id) init{
self = [super init];
if (self != nil){
self.delegate= nil;
}
return self;
}
-(id) initWithDelegate:(id<test_delegate>) delegate{
self = [super init];
if (self != nil){
self.delegate = delegate;
}
return self;
}
-(void)sendData:(NSString *)data{
[self.delegate returnData:data InMethod:#"method test"];
}
#end
Objetive-c brigde file
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "test_class.h"
Swift file (FirstViewController.swift)
import UIKit
class FirstViewController: UIViewController, test_delegate {
var test : test_class!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
test = test_class()
test.delegate=self
test.sendData("show in log")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func returnData(data: String!, inMethod method: String!) {
NSLog(data)
}
}
Finally the error compile gives me
2015-01-27 08:21:05.787 Test[4566:51164] -[Test.FirstViewController returnData:InMethod:]: unrecognized selector sent to instance 0x7fa6abd0aa20
2015-01-27 08:21:05.792 Test[4566:51164] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Test.FirstViewController returnData:InMethod:]: unrecognized selector sent to instance 0x7fa6abd0aa20'
This code, using only Obj-c classes works fine. What I'm doing wrong?
Using Xcode 6.1.1 and deployment target iOS 8.0
Thank you in advance.
UPDATE:
Swift has a weird behaviour when implement some method from a objective-c protocol: param's name must start with a tiny letter at the the begginig, else, compiler give you a compile-time error, and if not, give you a runtime error.
The unique solution I've found, is modify objective-c protocol method definition this way:
#protocol test_delegate
-(void)returnData:(NSString*)data InMethod:(NSString*)method;
#end
to:
#protocol test_delegate
-(void)returnData:(NSString*)data inMethod:(NSString*)method;
#end
Doing this change, it runs perfectly.
If anyone has an answer for this behaviour, is welcome to post and explain why this happens.
UPDATE 2:
Thank you to #Adam Freeman to point out another weird issue of Swift with classes names and variables names. Copied (with his permission) the code here:
Another thing to watch out for that is if your protocol delegate method takes its class as one of its parameters. Using this example:
#import <Foundation/Foundation.h>
#class TestClass;
#protocol TestDelegate
-(void)TestClass:(TestClass*)TestClass returnData:(NSString*)data inMethod:(NSString*)method;
#end
#interface TestClass : NSObject
#property (weak, nonatomic) id<TestDelegate> delegate;
-(void)sendData:(NSString * )data;
#end
This will cause problems with TestClass being found in your Swift code. The fix is:
#import <Foundation/Foundation.h>
#class TestClass;
#protocol TestDelegate
-(void)testClass:(TestClass*)testClass returnData:(NSString*)data inMethod:(NSString*)method;
#end
#interface TestClass : NSObject
#property (weak, nonatomic) id<TestDelegate> delegate;
-(void)sendData:(NSString * )data;
#end
Incidentally the Swift spec states that such things as classes and delegates should start with an upper case letter and methods and parameter names should start with a lower case letter.
Change:
func returnData(data: String!, inMethod method: String!) {
to
func returnData(data: String!, InMethod method: String!) {
and it should work. You use a capital letter in inMethod.
Another thing to watch out for that I ran into and this post helped me fix (thanX!) and that others may run into is if your protcol delegate method takes its class as one of its parameters. Such as ==>
#import <Foundation/Foundation.h>
#class TestClass;
#protocol TestDelegate
-(void)TestClass:(TestClass*)TestClass returnData:(NSString*)data inMethod:(NSString*)method;
#end
#interface TestClass : NSObject
#property (weak, nonatomic) id<TestDelegate> delegate;
-(void)sendData:(NSString * )data;
#end
This will cause problems with TestClass being found in your Swift code. The fix is:
#import <Foundation/Foundation.h>
#class TestClass;
#protocol TestDelegate
-(void)testClass:(TestClass*)testClass returnData:(NSString*)data inMethod:(NSString*)method;
#end
#interface TestClass : NSObject
#property (weak, nonatomic) id<TestDelegate> delegate;
-(void)sendData:(NSString * )data;
#end
Incidentally the Swift spec states that such things as classes and delegates should start with an upper case letter and methods and parameter names should start with a lower case letter. I know this is nit-picky but test_class really should be TestClass according to Apple's spec.

iOS: how class notify something to UI view?

Starting point
I have a class, say A, used by an UI view.
A has a delegate that should notify UI view and this one should be write something on screen.
Question
What is the best approach to achieve this feature?
Seems something like observer-observable pattern
Code
---A.h
#interface A : NSObject
#end
---A.m
#implementation A
-(void)fooDelegate:(FooType *)sender {
/* Here I need to notify UI (that change notificationArea.text) */
}
---UIView.h
#interface UIView : UIViewController
#property(strong, nonatomic, retain) A* a;
#property(strong, nonatomic) IBOutlet UITextField *notificationArea;
#end
Based on the comments, I guess just code is what you're looking for...
Create your delegate protocol:
#protocol ADelegate;
#interface A : NSObject
#property (nonatomic, weak) id <ADelegate> delegate;
#end
#protocol ADelegate <NSObject>
#optional
-(void)fooDelegate:(A *)a;
#end
Notify your delegate:
#implementation A
-(void)fooDelegate:(FooType *)sender {
if ([[self delegate] respondsToSelector:#selector(fooDelegate:)]) {
[[self delegate] fooDelegate:self];
}
}
#end
Conform to the delegate protocol:
#import "A.h"
#import "MyView.h"
#interface MyView <ADelegate>
#end
#implementation MyView
-(void)fooDelegate:(A *)a {
// update text field here
}
#end
Finally, whenever you create an instance of A, set the delegate (where self in this example is an instance of MyView:
A *a = [[A alloc] init];
[a setDelegate:self];

Resources