The following will pass the respondsToSelector test, but SIGABRT on the actual call to [viewController selector] or [viewController action:selector]. The stack trace states 'NSInvalidArgumentException, reason: -[MyViewController selector]: unrecognized selector'.
[viewController #selector(selector)] will cause a compile error (error: expected ':' before 'selector').
When the selector is hard coded, all works well.
How do I send a message to an object through a selector?
-(void) notifyViewControllers:(NSString*) message
{
if(!message) return;
SEL selector = NSSelectorFromString(message);
if(!selector) return;
NSArray* viewControllers = [self.tabBarController viewControllers];
if(!viewControllers) return;
for (UIViewController* viewController in viewControllers)
{
if(!viewController) continue;
if ([viewController respondsToSelector:selector]) {
// [viewController selector];
[viewController action:selector];
}
}
}
[self performSelector:#selector(notifyViewControllers:) withObject: message];
Try
[viewController performSelector:selector];
Also check other methods NSObject in performSelector 'family' - with them you can easily call selector with delay and/or on background thread.
Could be useful to know how to do this where you cannot use performSelector, maybe because the selector string must be used within a protocol method:
In order to allow the selector supporting parameters, it should be specified as follow:
NSString *stringForSelector = #"doSomethingAwesome:"; // notice the colon
Let's say we're going to handle a tap gesture
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:NSSelectorFromString(stringForSelector)];
UIGestureRecognizer class allow to use the recognizer itself in the action callback:
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer;
So to do something awesome on Tap, we could write:
- (void)doSomethingAwesome:(UITapGestureRecognizer *)tapGesture
{
// gesture handling with UIGestureRecognizer availability
}
Related
I'm looking through the code, specifically the Main View Controller that initiates calls with the call button. Both users must be on this View Controller after inputting their names to be found in a database.
But I'm confused as to how the callee is notified of a call and how it segues into a Calling View Controller that shows that they can answer or hangup.
I know that prepareForSegue sets the call to be whoever called, but I'm still confused with the remaining few lines after that.
So note the last two delegate methods: the first delegate method performs a segue, which makes sense. But what about the second one because I'm confused as to how it segues into call view controller that lets the callee answer or decline.
MainViewController.m
#import "MainViewController.h"
#import "CallViewController.h"
#import <Sinch/Sinch.h>
#interface MainViewController () <SINCallClientDelegate>
#end
#implementation MainViewController
- (id<SINClient>)client {
return [(AppDelegate *)[[UIApplication sharedApplication] delegate] client];
}
- (void)awakeFromNib {
self.client.callClient.delegate = self;
}
- (IBAction)call:(id)sender {
if ([self.destination.text length] > 0 && [self.client isStarted]) {
id<SINCall> call = [self.client.callClient callUserWithId:self.destination.text];
[self performSegueWithIdentifier:#"callView" sender:call];
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
CallViewController *callViewController = [segue destinationViewController];
callViewController.call = sender;
}
#pragma mark - SINCallClientDelegate
// Outgoing Call?
- (void)client:(id<SINCallClient>)client didReceiveIncomingCall:(id<SINCall>)call {
[self performSegueWithIdentifier:#"callView" sender:call];
}
// Incoming Call?
- (SINLocalNotification *)client:(id<SINClient>)client localNotificationForIncomingCall:(id<SINCall>)call {
SINLocalNotification *notification = [[SINLocalNotification alloc] init];
notification.alertAction = #"Answer";
notification.alertBody = [NSString stringWithFormat:#"Incoming call from %#", [call remoteUserId]];
return notification;
}
(void)client:(id)client didReceiveIncomingCall:(id)call {
[self performSegueWithIdentifier:#"callView" sender:call];
}
is called when the app is in the foreground and an incoming call is in prgress, it will push the viewcontroller with teh call and since its direction is incoming you will be presented with an answer decline button,
(SINLocalNotification *)client:(id)client localNotificationForIncomingCall:(id)call {
SINLocalNotification *notification = [[SINLocalNotification alloc] init];
notification.alertAction = #"Answer";
notification.alertBody = [NSString stringWithFormat:#"Incoming call from %#", [call remoteUserId]];
return notification;
}
is called when the app is the background and you have enabled push
But what about the second one because I'm confused as to how it segues into call view controller that lets the callee answer or decline
The header explains to you what happens:
The return value will be used by SINCallClient to schedule ... a UILocalNotification. That UILocalNotification, when triggered and taken action upon by the user, is supposed to be used in conjunction with
-[SINClient relayLocalNotification:].
-(void)didMoveToView:(SKView *)view
{
UITapGestureRecognizer* doubleTapGestureRecognizer = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(doubleTap:)];
doubleTapGestureRecognizer.numberOfTapsRequired = 2;
[self.view addGestureRecognizer:doubleTapGestureRecognizer];
}
-(void)doubleTap:(id)sender
{
NSLog(#"double tap");
}
I am using this to add a gesture recognizer to my SKView. It works fine, double tap gets called when i double tap, but when i touch the screen and move my finger, app crashes with this error:
[UITapRecognizer name]: unrecognized selector sent to instance 0x1756c600
Why is this happenning? Im not calling any "name" selector on this recognizer.
I have this method:
#pragma mark - SKPhysicsContactDelegate
-(void)didBeginContact:(SKPhysicsContact *)contact
{
if(![contact.bodyA.node.name isEqualToString:#"player"])
{
[contact.bodyA.node performSelector:#selector(removeFromParent) withObject:nil afterDelay:1];
}
if(![contact.bodyB.node.name isEqualToString:#"player"])
{
[contact.bodyB.node performSelector:#selector(removeFromParent) withObject:nil afterDelay:1];
}
}
i noticed that if i comment this out, there is no such error, but then i replaced it with this:
-(void)didBeginContact:(SKPhysicsContact *)contact
{
if(![contact.bodyA.node respondsToSelector:#selector(name)])
{
NSLog(#"%# , %# \n",contact.bodyA, contact.bodyA.node);
}
if(![contact.bodyB.node respondsToSelector:#selector(name)])
{
NSLog(#"%# , %#",contact.bodyB, contact.bodyB.node);
}
}
and there is nothing in debug log, just the same error. What is going on?
turned out is was a duplicate of -> UITapGestureRecognizer causes app to crash
"I had the same issue in one of my SpriteKit Games, it is caused when you use gestures during transition between scenes, i solved it by setting gestureRecognizer.enable property (documentation) to NO before the transition."
That answer helped
My Application is getting crashed with the following error.
-[PreviewViewController applicationWillSuspend]: message sent to deallocated instance 0x1806d9e0
My application have two view controllers one is HomeViewController and other one is PreviewViewController.
In home view controller i am displaying a table view. When selecting the row of table view i am presenting the preview view controller.
I selected one row then preview view controller is presented.
PreviewViewController *previewController = [[PreviewViewController alloc]initWithPreviewImage:[[kfxKEDImage alloc] initWithImage:imgCaptured] withSourceofCapture:_typeOfCapture typeOfDocumentCaptured:PHOTO];
[self presentViewController:previewController animated:YES completion:nil];
Dismissed the preview view controller.
[self dismissViewControllerAnimated:YES completion:nil];
Application goes into background then it is not crashed.
I selected two rows one after another. Application goes into background then it is crashed. I don't know why it is behaving like that. If anyone know the solution please tell me.
Thanks In Advance
I had this problem, it was caused by someone overriding 'dealloc' in a UIViewController category.
https://github.com/taphuochai/PHAirViewController/issues/13
#chrishulbert
Remove this:
- (void)dealloc
{
self.phSwipeHander = nil;
}
Replace dealloc with this:
/// This is so that phSwipeGestureRecognizer doesn't create a swipe gesture in *every* vc's dealloc.
- (BOOL)phSwipeGestureRecognizerExists {
return objc_getAssociatedObject(self, SwipeObject) ? YES : NO;
}
- (void)ph_dealloc
{
if (self.phSwipeGestureRecognizerExists) {
self.phSwipeHander = nil;
}
[self ph_dealloc]; // This calls the original dealloc.
}
/// Swizzle the method into place.
void PH_MethodSwizzle(Class c, SEL origSEL, SEL overrideSEL) {
Method origMethod = class_getInstanceMethod(c, origSEL);
Method overrideMethod = class_getInstanceMethod(c, overrideSEL);
if (class_addMethod(c, origSEL, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
class_replaceMethod(c, overrideSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
} else {
method_exchangeImplementations(origMethod, overrideMethod);
}
}
/// Swizzle dealloc at load time.
+ (void)load {
SEL deallocSelector = NSSelectorFromString(#"dealloc"); // Because ARC won't allow #selector(dealloc).
PH_MethodSwizzle(self, deallocSelector, #selector(ph_dealloc));
}
The Short Version:
I ran into an issue where a method was being invoked on a different instance of an object than the NSNotificationCenter was pushing notifications to (though only one instance was created).
The Long Version:
I have a "Puzzle" object instance which is being operated on by an OpenGL update/draw loop.
I created a Control singleton to manage different touch events like so:
#implementation Controls
static Controls *SharedGameControls = nil;
-(id)init {
self = [super init];
return self;
}
+ (Controls*)SharedGameControls{
if (SharedGameControls == nil){
SharedGameControls = [[super allocWithZone:NULL] init];
}
return SharedGameControls;
}
+ (id)allocWithZone:(NSZone *)zone
{
return self.SharedGameControls;
}
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
-(void)oneFingerSwipeDelegator:(UISwipeGestureRecognizer *)swipe{
switch (GState.currentMode)
{
case PuzzleLayer: {
CGPoint Origin = [swipe locationInView: _view];
//I believe this line should call oneFingerSwipe on the object instance
//provided to the singleton
[_oneFingerSwipeDelegate oneFingerSwipe:Origin Direction:swipe.direction];
break;
}
default:{
break;
}
}
}
-(void)setDefaultState{
GState = [GameState SharedGameState];
UISwipeGestureRecognizer *oneFingerSwipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(oneFingerSwipeDelegator:)];
[oneFingerSwipeUp setDirection:UISwipeGestureRecognizerDirectionUp];
[_view addGestureRecognizer:oneFingerSwipeUp];
UISwipeGestureRecognizer *oneFingerSwipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(oneFingerSwipeDelegator:)];
[oneFingerSwipeDown setDirection:UISwipeGestureRecognizerDirectionDown];
[_view addGestureRecognizer:oneFingerSwipeDown];
UISwipeGestureRecognizer *oneFingerSwipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(oneFingerSwipeDelegator:)];
[oneFingerSwipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];
[_view addGestureRecognizer:oneFingerSwipeLeft];
UISwipeGestureRecognizer *oneFingerSwipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(oneFingerSwipeDelegator:)];
[oneFingerSwipeRight setDirection:UISwipeGestureRecognizerDirectionRight];
[_view addGestureRecognizer:oneFingerSwipeRight];
UITapGestureRecognizer * single = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(singleTapDelegator:)];
single.numberOfTapsRequired = 1;
[_view addGestureRecognizer:single];
}
-(void)singleTapDelegator:(UITapGestureRecognizer *)tap{
CGPoint origin = [tap locationInView: _view];
NSValue *Origin = [NSValue valueWithCGPoint:origin];
switch (GState.currentMode)
{
case PuzzleLayer: {
[[NSNotificationCenter defaultCenter] postNotificationName:#"puzzleTap" object:nil userInfo:[NSDictionary dictionaryWithObject:Origin forKey:#"Origin"]];
break;
}
default:{
break;
}
}
}
#end
The '_oneFingerSwipeDelegate' is defined in the .h file as
#property (nonatomic, assign) id oneFingerSwipeDelegate;
Then in the Puzzle class the events were handled thusly:
#implementation Puzzle
-(id)init{
self =[super init];
if (self){
GControls = [Controls SharedGameControls];
//I believe, possibly wrongly, that the line below will set this object instance
//to be the object oneFingerSwipe is called on
GControls.oneFingerSwipeDelegate = self;
GControls.twoFingerSwipeDelegate = self;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(singleTap:) name:#"puzzleTap" object:nil];
_Started = false;
//other code omitted
}
return self;
}
//implementation of the delegate in Controls.h
-(void)oneFingerSwipe:(CGPoint)touchPoint Direction:(UISwipeGestureRecognizerDirection)direction{
//do some stuff with objects inside puzzle
}
//observer for 'puzzleTap'
-(void)singleTap:(NSNotification*)note{
GLKVector3 Near,Far;
NSDictionary *dict = [note userInfo];
CGPoint origin = [[dict objectForKey:#"Origin"] CGPointValue ];
//Do stuff with objects inside puzzle
}
//other code omitted
#end
So I was testing the gesture recognition to make sure everything was working and I noticed that my swipes weren't being handled.
Tapping correctly sent the notification to the singleTap method in the Puzzle object (which sets a 'selected' flag on a child object of the Puzzle instance).
Swiping correctly invoked the oneFingerSwipe method on the puzzle object, but for some reason was unable to detect which object had been 'selected' by the tap.
After taking a closer look I noticed that when I stepped in to singleTap I was being shown an address for the puzzle object which was different than the address shown when stepping into the oneFingerSwipe method called by the Control singleton.
So effectively, for some reason, the Control singleton is operating on a twin instance of the object that the notification is being sent to (or the reverse). All the objects within each instance seem to have different memory addresses than the respective twin.
As a result, when the oneFingerSwipe is called, the 'selected' flag hasn't been updated on the child object and so the logic for swiping isn't being invoked.
When I switched to using notifications for the swipe, the issue went away.
I don't really understand what is going on here. Is the Control singleton creating a copy of the Puzzle instance when I assign the puzzle instance to the oneFingerSwipe property?
I've only been working with Objective C for about 6 months and there is a great deal I don't understand.
Thanks for pushing through that wall of text, I appreciate you taking the time to have a look.
The reason it seemed that the direct method invocation was happening on a different object than the NSNotificationCenter was pushing notifications to is that it was, in fact, a different object. I had instantiated a number of different puzzle objects in a loop and each one was, in turn, setting itself as the target for oneFingerSwipe. When I was stepping through I happened to be looking at two incredibly similar but different objects and wrongly came to the conclusion that one was a copy of the other. Many thanks to the commenters for taking the time to look at this lengthy question and pointing me in the right direction. For now I've switched to solely using NSNotifications, but I'm going to do a little more reading into using delegates to see if that might better suit my needs.
i´ve made lots of my own "CustomUIButton" in a for-loop in my viewcontroller.
In this "CustomUIButton"-class i´ve implemented an UIGestureRecognizer like this:
(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// custom things.
UILongPressGestureRecognizer* longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPress:)];
longPress.minimumPressDuration = 1.0;
[self addGestureRecognizer:longPress];
[longPress release];
}
}
- (void) handleLongPress:(UILongPressGestureRecognizer*) recognizer{
if (recognizer.state == UIGestureRecognizerStateEnded) {
NSLog(#"Long press Ended");
}
else {
NSLog(#"Long press detected.");
// Do something
}
}
If i init the target with "self", my "handleLongPress"-function in this class will be called. It´s cool. If i init the target with "nil", it should check the parent viewcontroller, right?
Any ideas why an additional function with the same name in my viewcontroller won´t be called? (For this test i´ve commented the "longpress"-function of the button-class out.)
In the docs for UIGestureRecognizer's initWithTarget:action: method, for the target parameter it says:
An object that is the recipient of
action messages sent by the receiver
when it recognizes a gesture. nil is
not a valid value.
Note the last sentence.
The docs also say this which should explain why it doesn't work:
A gesture recognizer does not
participate in the view’s responder
chain.
You must specify a value for target.