Apply PanGesture to uiview in another class with action - iphone

I have a ViewController iDragHomeViewController
and another
NSObject class iDrag
"iDragHomeViewController.m"
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *dragView = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 200, 200)];
[dragView setBackgroundColor:[UIColor greenColor]];
[self.view addSubview:dragView];
iDrag *drag = [[iDrag alloc]init];
[drag makeDraggableView:dragView];
}
"iDrag.m"
-(void)makeDraggableView: (UIView *)dragView {
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(cellPan:)];
[dragView addGestureRecognizer:panRecognizer];
}
- (void)cellPan:(UIPanGestureRecognizer *)iRecognizer {
UIView *viewToDrag = [[UIView alloc]init];
viewToDrag = iRecognizer.view;
CGPoint translation = [iRecognizer translationInView:[viewToDrag superview]];
viewToDrag.center = CGPointMake(iRecognizer.view.center.x + translation.x,
iRecognizer.view.center.y + translation.y);
[iRecognizer setTranslation:CGPointMake(0, 0) inView:[viewToDrag superview]];
}
Now what I am trying here is to make this "dragView"(belongs to iDragHomeViewController) draggable in iDrag class by applying it PanGesture.
But the code is crashing.
I know some people will suggest me to use NSNotification to handle Pan action in another class but I dont want to write a single line in iDragHomeViewController and handle everything in iDrag Class only.
Is it Possible ??
Please Help.

To be sure I need to know the error output but a guess...
From UIGestureRecognizer doc:
- (id)initWithTarget:(id)target action:(SEL)action
target parameter:
An object that is the recipient of action messages sent by the receiver when it recognizes a gesture. nil is not a valid value.
Thats the reason why your app crashes. The drag object is already released while the recognizer tries to call the cellPan: method.
You initialize the iDrag object in viewDidLoad and is not retained. (It is not a member variable and not used anywhere else....). End of the viewDidLoad the iDrag object is released by ARC.
I would not make any other object responsible for handling pan gestures, unless I had a good reason for that. And would make the view controller responsible for creating gesture recognizer and handling the events.
I assume you have really good reason for that, like the handling is used by multiple views, etc... If it is the case then a better approach would be making iDrag object singleton(shared instance).

Got the answer
Just need to declare iDrag object as a property
#property(nonatomic,strong) iDrag *drag;

Related

ViewDidLoad method calling behavior [duplicate]

This question already has an answer here:
Method calling via performSelectorOnMainThread Vs Normal method calling
(1 answer)
Closed 9 years ago.
In my viewDidLoad, I call a function:
[common startActivityIndicatorOnView:self.view];
This method adds a view with Activity indicator, in the center of self.view.
My current view is pushed on a Navigation Stack. This is how the view looks after this method returns (the activity indicator view is not in center):
However, if I call the same method this way:
[common performSelectorOnMainThread:#selector(startActivityIndicatorOnView:) withObject:self.view waitUntilDone:NO];
The view looks like the following image (the activity indicator view is in center):
I do not get, How does it make a difference if the calling line is written in viewDidLoad.
If any one can help me get this, thanks in advance.
Just for reference,
the method looks like this:
-(void) startActivityIndicatorOnView:(UIView *)view {
if ([NSRunLoop currentRunLoop] != [NSRunLoop mainRunLoop]) {
[self performSelectorOnMainThread:#selector(startActivityIndicatorOnView:) withObject:view waitUntilDone:NO];
return;
}
view.userInteractionEnabled = NO;
activityBgView = [[UIView alloc] initWithFrame:CGRectMake((view.frame.size.width/2) - 50, (view.frame.size.width/2) - 50, 100, 100)];
activityBgView.center = view.center;
activityBgView.backgroundColor = [UIColor grayColor];
activityBgView.alpha = 0.8;
spinner = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake((activityBgView.frame.size.width/2)-10, (activityBgView.frame.size.width/2)-10, 20, 20)];
spinner.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge;
spinner.center = view.center;
[view addSubview:activityBgView];
[view addSubview:spinner];
[spinner startAnimating];
}
ViewDidLoad is called when you load viewControllers (via nib/xib or created programmatically in the loadView method), meaning xcode create all views and instantiate and alloc all objects in xib...
but, your viewController view (and subViews) are still not added in any view...
yourViewController.view.superView = nil;
so, its view has got the frame that you set in xib, but if you tell it to resize inside its superview, when you add it (e.g. with a push or an addsubview), its frame changes, but your
spinner won't change its position.
calling a performSelectorOnMainThread just will call your method later, when your current thread step ahead and may have pushed your viewController.view, so, when executed, yourViewController.view.superView exists, and so view.frame has already changed.
try to move your call to
[common startActivityIndicatorOnView:self.view];
in a viewWillAppear method: at that point yourViewController.view should been already resized to fit its superView
EDIT:
# pavel question:
after what moment yourViewController.view.superView will be not nil?
banally: when you add its view to a view. that is, firts you allocate and init it (init with a nib or via code)
something like:
yourViewControllerInstance = [[YourViewController alloc]initWithNibName:#"yourViewControllerNib" bundle:nil];
at this point the method viewDidLoad in your class is called (but yourViewController.view.superview 0 nil)
later, you usually use your new viewController, so you "push" it in the stack of a navigationController, or you just add its view to the current viewController.view... something like:
[currentViewController.view addSubview:yourViewController.view];
after this line, as you may imagine, yourViewController.view.superView = currentViewController.view, and the method viewWillAppear of yourViewController is called, so you can use superView inside it.
Notice that at this point your viewController.view is still not visible on screen, so you can adjust sizes, move or add views here without users see any changes.
after this, yourViewController will show, and at the end, and the method viewDidAppear of yourViewController is called (for any other code, in case)

How can I access the name of the selector that's attached to a UIGestureRecognizer?

I have a gesture recognizer attached to a view and I'd like to be able to unit test which method it calls when the tap occurs. My gesture recognizer is created like so...
- (void)setupMyView {
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(myViewTapped)];
self.myView.userInteractionEnabled = YES;
[self.myView addGestureRecognizer:tap];
}
How can I access the name of the selector (myViewTapped) that is called when the tap occurs?
Thanks so much in advance for your wisdom!
Unfortunately, neither UIGestureRecognizer nor UITapGestureRecognizer exposes this information.
UIControl, for example, exposes allTargets and allControlEvents, which is basically what you are looking for, but it is unfortunately unavailable for UIGestureRecognizer
As a result, I do not believe what you want is possible without using private methods.
Use this inside the method myViewTapped,
NSLog(#"method name: %#", NSStringFromSelector(_cmd))
This one also can print the method name,
NSLog(#"%s", __PRETTY_FUNCTION__);
Source:

How do you initialize a UIGestureRecognizer?

I want to add a gesture recognizer to my button so that I can run code if the user swiped past the buttons frame. I also want this code to be different if the swipe was up, right, left, or down the button.
-(void)viewDidLoad
{
[super viewDidLoad];
UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(0, 0, 100, 100);
[self.view addSubview:button];
UIGestureRecognizer *swipe=[[UIGestureRecognizer alloc]initWithTarget:button action:#selector(detectSwipe)];
[button addGestureRecognizer:swipe];
}
so, did I do the initWithTarget:action: thing correct? And now that I do this how do i Implement the detectSwipe method?
here is my idea on how to implement detectSwipe
-(IBAction)detectSwipe:(UIButton *)sender
{
/* I dont know how to put this in code but i would need something like,
if (the swipe direction is forward and the swipe is > sender.frame ){
[self ForwardSwipeMethod];
} else if //same thing for right
else if //same thing for left
else if //same thing for down
}
No, it isn't correct. The target of the gesture recognizer is not the button, it's the object on which it calls the action method when detecting a gesture (otherwise how would it know on which object call that method? In OO, a method call/message send needs an explicit method name and an instance or class).
So you would most likely want
recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(didSwipe:)];
You also don't create an instance of UIGestureRecognizer directly but one if its concrete subclasses, UISwipeGestureRecognizer in this case.
After alloc-initting the recognizer, you attach it to the view you want to be recognized:
[button addGestureRecognizer:recognizer];
Then in the didSwipe: method, you can use the gesture recognizer's properties to decide what the size/distance/other property of the swipe was.
You better read some docs next time.
You probably want to be using a UISwipeGestureRecognizer. UIGestureRecognizer usually shouldnt be used unless you are subclassing it. Your code should look similar to the following.
UISwipeGestureRecognizer *swipe=[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(detectSwipe)];
swipe.direction = UISwipeGestureRecognizerDirectionRight;
[button addGestureRecognizer:swipe];
You got all right except for the target of gesture recogniser. The target is an object that receives given selector message so your initWithTarget: call should accept self as an argument unless you're implementing detectSwipe method in a subclass of your button.
H2CO3's answer is complete. Just don't forget that you're missing a colon ":" at the end of your selector! It should be like this: #selector(detectSwipe:)
The colon ":" is because your method has an argument: (UIButton *)sender

Where does the init *really* happen in a UIView placed in Interface builder?

I have a subclass of UIView called SlideOut. I want to capture the IB placement of the view when it loads, so I have this in my implementation:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
IBframe.origin.x = frame.origin.x;
IBframe.origin.y = frame.origin.y;
IBframe.size.height = frame.size.height;
IBframe.size.width = frame.size.width;
// Initialization code.
NSLog(#"init if self x: %f, y: %f", IBframe.origin.x, IBframe.origin.y);
}
NSLog(#"init x: %f, y: %f", IBframe.origin.x, IBframe.origin.y);
return self;
}
... with a matching prototype in the .h file.
Upon starting up the simulator, I get all my other diagnostics, but neither of these log messages get called. So how does the instance actually get initialized? Or am I missing something? The position function works, but then fails because I haven't captured the actual frame of the thing. In any case, that's how I know I made the IB connections correctly.
Take a look here: Subclassing UIView, "Methods to Override", from UIView Class Reference.
In particular:
initWithCoder: - Implement this method
if you load your view from an
Interface Builder nib file and your
view requires custom initialization.
Objects that are loaded from an xib are actually unarchived. Thus, you should be using the initWithCoder: method. Alternatively, you may way to look at awakeFromNib instead.
I believe the function that gets called when the view is loaded from a NIB is initWithCoder not initWithFrame

Subclasses of UIButton loaded from nibs are not initialized properly

I have a very simple subclass of UIButton:
#interface MyButton : UIButton
#end
#implementation MyButton
- (id) initWithCoder:(NSCoder *)decoder
{
if (!(self = [super initWithCoder:decoder]))
return nil;
NSLog(#"-[%# initWithCoder:%#]", self, decoder);
return self;
}
#end
In Interface Builder I add a UIButton, set its button type to Rounded Rect and its class identity to MyButton.
When running, I have the following log:
-[<MyButton: 0x5b23970; baseClass = UIButton; frame = (103 242; 114 37); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x5b23a90>> initWithCoder:<UINibDecoder: 0x6819200>]
but the button is not a round rect button anymore.
Observed on both iOS 3.2 and iOS 4.
Is this a bug or am I missing something obvious?
Create an instance of MyButton programmatically is not an acceptable answer, thanks.
Programmatically, you instantiate a button with +[UIButton buttonWithType:] which is actually a factory that returns a subclass of UIButton. So if you derive from UIButton you actually don't derive from your round rect button class (UIRoundedRectButton) but from a generic button class. But you are not allowed to subclass from UIRoundedRectButton AFAIK since it's an internal class.
It seems to be problematic to derive from UIButton, I've seen a lot of people recommed to derive from UIControl instead and implement the drawing yourself.
But you might find these articles helpful:
How to override -drawrect in UIButton subclass?
http://www.cocoabuilder.com/archive/cocoa/284622-how-to-subclass-uibutton.html
http://www.cimgf.com/2010/01/28/fun-with-uibuttons-and-core-animation-layers/
Also, I don't know why you want to derive from UIButton, but if you want to do some customization that does not involve overwriting any other methods it might be helpful to use the fact that you can do something like this:
- (id)initWithCoder:(NSCoder *)decoder {
// Decode the frame
CGRect decodedFrame = ...;
[self release];
self = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self setFrame:decodedFrame];
// Do the custom setup to the button
return self;
}
I’m not sure if this is acceptable for your needs, but I tend to prefer to override -awakeFromNib instead of -initWithCoder: in these circumstances. Does doing this resolve the issue you’re seeing?