Gesture recogniser in different class? - ios

I was wondering if this was possible.
To have a class which adds new imageViews to the main view and assigns a gesture recogniser to it.
So in my view builder class, I have the following:
UIImageView *headerPlusIcon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"plusIcon.png"]];
headerPlusIcon.frame = CGRectMake(header.frame.size.width - 2.5*(logoSize - 8), yPosition*1.6, logoSize*0.9, logoSize*0.9);
headerPlusIcon.userInteractionEnabled = YES;
UIGestureRecognizer *headerTapGesture = [[UIGestureRecognizer alloc] initWithTarget:mainView action:#selector(testTapGesture:)];
[headerPlusIcon addGestureRecognizer:headerTapGesture];
The tap gesture method goes like this:
-(void)testTapGesture:(UITapGestureRecognizer *)gesture
{
dispatch_async(dispatch_get_main_queue(), ^{
mainView.backgroundColor = [UIColor redColor];
});
}
mainView is passed into this class via the constructor and is simply the main view.
This is called like this:
mainViewbuilder = [[MainViewBuilder alloc] initWithBaseView:self.view];
[mainViewbuilder buildHeader];
Unfortunately the tap gesture method never gets called... how would this be done properly?
Thanks!

Try UITapGestureRecognizer
Apple Docs on UITapGestureRecognizer

The code you posted never adds your headerPlusIcon to the view hierarchy. You say in your answer to Adam that you got it working, but I don't see how if you don't add the headerPlusIcon to your view controller's content view hierarchy somewhere.
Note that mucking around in a view controller's view hierarchy from outside is not good object-oriented design.
It would be better to have the view controller ask another object for a view (probably through a protocol) and then add that view itself.

Related

willRemoveSubview does not remove subview when called from a class outside the superView's ViewController?

I don't understand the different behavior I am observing with my iOS application when I am programmatically removing subviews using Objective-C and the UIView willRemoveSubview method.
Method 1
In my ViewController class I add a UIVisualEffectView as a subview for the ViewController's view:
- (void)blurView {
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
blurEffectView.frame = self.view.bounds;
blurEffectView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view insertSubview:blurEffectView atIndex:5];
}
This method is called in response to some action by the user.
The blurEffectView itself is a private variable of my ViewController class defined in the implementation, which will point to the new subview
#interface MyViewController ()
#end
#implementation
UIVisualEffectView *blurEffectView;
...
I have a Tap Gesture recognizer which calls another method to "unblur" by removing the subview that was added
- (void) tapped {
blurEffectView.hidden = true;
[self.view willRemoveSubview:blurEffectView];
}
This method works fine, the blur shows up and covers the view. When the tap happens the blur disappears, and everything looks ok.
Method 2
Next I tried something a little more complex, and it now behaves differently. I created a small Objective-C class to wrap the blur/unblur functions in a separate utility object that I can reuse more easily.
Basically the MyViewController now has a new private variable to reference my new class ViewBlurrer which just extends NSObject. ViewBlurrer now encapsulates the reference to the UIVisualEffectView *blurEffectView, and it's interface has public methods for blur and unblur.
So the hierarchy is something like
MyViewController -> ViewBlurrer -> UIVisualEffectView
ViewBlurrer also maintains a reference to the MyViewController.view, which it uses in the blur and unblur methods. Call this pointer parentView.
This behaves exactly the same for blur, it correctly adds the blur effect that covers up the ViewController's view.
What I observed differently, is that I could not hide and remove the blurEffectView now that it was in this utility class ViewBlurrer. The only way I could make it get removed was to lookup the UIVisualEffectView with arbitrary tag number such as this in the ViewBlurrer.unblur method:
-(void)unblurView {
UIView *theSubView = [self.parentView viewWithTag:66];
theSubView.hidden = YES;
[self.parentView willRemoveSubview:theSubView];
}
My question is why I can't just call
[self.parentView willRemoveSubview:blurEffectView], but I have to look up by tag isn't the pointer to blurEffectView that I inserted to the parentView still equivalent as it was in the original approach in Method 1?
As already mentioned in the comments to your question, you are not supposed to call willRemoveSubview yourself. It is automatically invoked (by the system) when you remove a subview from the view hierarchy. From the documentation of willRemoveSubview:
This method is invoked when subview receives a removeFromSuperview() message or subview is removed from the view due to it being added to another view with addSubview(_:).
Hence you should rather write:
- (void) tapped {
blurEffectView.hidden = true;
[blurEffectView removeFromSuperview];
}
or shorter:
- (void) tapped {
[blurEffectView removeFromSuperview];
}

Put gesture recognizers in view or controller class

I was looking for GestureRecognizer tutorials on the web and saw that most people used for example UIPanGestureRecognizer directly in the view. Is this common practice, if so, why not in the controller?
I generate my views in the controller and have methods that I would need to call, when the gesture is used. How should the delegation method look like, if my gesture recognizer is in the view class, and the method to be called in the controller class?
A simple UIGestureRecognizer implementation, and the most common one in my opinion is as follows:
//this is where you create the view that you want to add the gesture recognizer
UIImageView * imageView = [[UIImageView alloc] initWithFrame:frame];
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] init];
tapRecognizer.numberOfTapsRequired = 1;
[tapRecognizer addTarget:self action:#selector(imageViewTapped:)];
imageView.userInteractionEnabled = YES;
[imageView addGestureRecognizer:tapRecognizer];
After creating the gesture recognizer and adding it to a UIView subclass you can get the taps with the function you pointed above:
- (void)imageViewTapped:(UIGestureRecognizer *)gestureRecognizer
{
if ( gestureRecognizer.state == UIGestureRecognizerStateRecognized )
{
NSLog(#"tap recognized!");
}
}
So basically, you dont need to worry about the controller/view dilemma.
You always add the gesturerecognizer to a view. and when adding the gesture recognizer you add a target function, which you need to implement in the controller class.
P.S This SO Question might clarify in detail of the dilemma you are facing
In one sense, yes, this violate the MVC pattern. As you say, the view shouldn't have anything to do with how to control it, it's a better habit to group such code in another part of the application.
You can drag an gesture into the storyboard(into the view controller),too.Then from the document outline you make an action(in the vc) for that gesture

Where and when to initialize/create view programatically

I had this question when/where to create and initialize views that are created programatically, so I hope some discussions here will shed more light on this topic for me.
This slide:
says: "not to initialize something based on the geometry of the view in viewDidLoad" and suggests viewDidAppear.
Imagine my view controller has view. I want to add 10 dynamic UIButtons to it.
Shall I put the code like below to the viewDidAppear?
-(void) viewDidAppear:(BOOL)animated
{
...
UIButton *button1 = [[UIButton alloc] initWithFrame: rect1];
[self.view addSubview: button1];
UIButton *button2 = [[UIButton alloc] initWithFrame: rect2];
[self.view addSubview: button2];
...
}
But this creates the buttons each time the view is shown. Is it what we want?
On the other hand if I put the code in viewDidLoad slide suggest not to initialize geometry of these views there.
Or shall we create buttons in viewDidLoad and set their frames in viewDidAppear?
What approach do you usually take?
But this creates the buttons each time the view is shown. It's true.
So the best thing you can do is to add a boolean (lets name it isLaunched). You set it to FALSE in the method -(void)viewDidLoad
Then add a if condition in your -(void)viewDidAppear where you perform creation of buttons (or other stuff) and set the boolean to true at the end.
You should have something like that :
-(void)viewDidLoad
{
//some settings
isLaunched = FALSE;
}
-(void)viewDidAppear:(BOOL)animated
{
if(!isLaunched)
{
//creating and adding buttons
isLaunched = TRUE;
}
}
zbMax (and now Amar) offered good solutions to implement the view creations in viewDidAppear: I will provide the rational for doing this (over viewDidLoad).
It is pretty simple actually. In viewDidLoad none of the views are actually setup yet, so any attempt to set/create frames or bounds will be extremely inconsistent. Struts and springs (or autolayout) will take effect after this method which will create additional changes to your views. viewDidAppear: is the correct place to do this because you can now rely on existing views and setting frames.
Reason for not playing with the geometry in viewDidLoad is because view is still in the memory and not on the window. Once the view is put on the window, then you can specify geometry. That happens when viewDidAppear is called for your controller.
As recommended, you should do all the initialisation in viewDidLoad as this is one time task and need not be repeated. Hold references to the added subviews and give them appropriate frame in viewDidAppear.
When you are dealing with custom UIView and its subviews, layoutSubviews is the method you need to override in the custom view in order to rearrange the geometry of its subviews.
Hope that helps!

UIGesture returns UIImageView and not custom view

I have added a custom view to my rootview and added a UITapGesture. When calling on the 'sender.view' of the UITapGesture code it no longer is represented by my custom UIView, but instead 'sender.view' is of type UIImageView.
So it really makes it hard to send messages to my custom view when iOS is expecting a UIView - the warning I get is: "Incompatible pointer types sending UIView to parameter CardView (my custom view).
I think it has to do with chain of response; the documentation says - "When iOS recognizes an event, it passes the event to the initial object that seems most relevant for handling that event, such as the view where a touch occurred." - About Events in iOS
The keyword here is "seems" - how can I tell iOS that my custom View is the one that should handle the gesture?
Question: How can I get sender.view to refer directly to my custom view?
Here is some code below:
//Creates the Card's view
CardView *cardView = [[CardView alloc]initWithFrame:CGRectMake(0, 0, 67,99)];
//For Single Tap
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(handleSingleTapGesture:)];
[cardView addGestureRecognizer:tapGesture];
[self.view addSubview:cardView];
In the handleSingleTapGesture: will give u the (UITapGestureTecongnizer *)sender so cast like this:
CardView *card = (CardView *)sender.view;
//do you stuff with card

IOS: disable gesture in a viewcontroller

In my app I have this situation:
I have my main viewcontroller where I alloc a "flipwiecontroller" and add its view in this way
self.flipViewController.view.frame = CGRectMake (...);
[self.view addSubview:self.flipViewController.view];
and at this flipViewController I add some gesture (as pangesture, swipegesture and tapgesture for some control that I use on it)
when I press a button in this view I alloc another viewcontroller "paintingviewcontroller" in this way
[self.view addSubview:paintingViewController.view];
in this second view controller I have some buttons and another function, but when I try to do a swipegesture or a tapgesture it recognize the events of my "flipviewcontroller"
I don't understand, if I add a view controller over another viewcontroller, why gesture of flipviewcontroller are active yet?
thnaks
Maybe you are disabling userIntercation on the paintingViewController, then, it's events are sent to his superview.
Also you can use [UIView removeGestureRecognizer:UIGestureRecognizer] to remove gestures.
How did you add the gesture recognizers? They should be added to the view, not the window. Perhaps that's the issue.
In case you have something like this:
[self.view.window addGestureRecognizer:panGestureRecognizer];
You should change it to this:
[self.view addGestureRecognizer:panGestureRecognizer];

Resources