I created a UIView in a separate class, and I am trying to animate it in my ViewController. It is supposed to work like the notification screen on the iPhone that appears when you swipe down, and then you can swipe it back up.
I can get my custom view to swipe down, but when I try to swipe it back up, the swipe up gesture is not being initiated.
I am a novice, so any help is greatly appreciated!
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NotificationView *notificationView = [[NotificationView alloc]init];
[self.view addSubview:notificationView];
UISwipeGestureRecognizer *swipeDownGestureRecognizer = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(swipeDown:)];
swipeDownGestureRecognizer.direction = UISwipeGestureRecognizerDirectionDown;
UISwipeGestureRecognizer *swipeUpGestureRecognizer = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(swipeUp:)];
swipeUpGestureRecognizer.direction = UISwipeGestureRecognizerDirectionUp;
[self.view addGestureRecognizer:swipeDownGestureRecognizer];
[notificationView addGestureRecognizer:swipeUpGestureRecognizer];
}
-(void) swipeUp: (UISwipeGestureRecognizer *) recognizer{
UIView *notificationView = [[NotificationView alloc]init];
notificationView = recognizer.view;
[UIView animateWithDuration:2.0 animations:^{
notificationView.frame = CGRectMake(0, 0, 414, 723);
}];
}
-(void) swipeDown: (UISwipeGestureRecognizer *) recognizer{
UIView *notificationView = [[NotificationView alloc]init];
notificationView = recognizer.view;
[UIView animateWithDuration:2.0 animations:^{
notificationView.frame = CGRectMake(0, 723, 414, 723);
}];
}
notificationView in ViewDidLoad should be assigned to the viewControllers property, and you should not alloc init views in the gesture recognizer actions.
You should create a property with your notificationView and keep a reference to it, and not create a new one over and over.
#property (strong, nonatomic) NotificationView *notificationView;
And in your viewDidLoad:
_notificationView = [[NotificationView alloc] init];
// Important line to solve your problems on gestures not fired off on your notification view
_notificationView.userInteractionEnabled = YES;
[self.view addSubview:_notificationView];
and simply change:
-(void)swipeDown:(UISwipeGestureRecognizer *)recognizer {
[UIView animateWithDuration:2.0 animations:^{
_notificationView.frame = CGRectMake(0, 723, 414, 723);
}];
}
You should look into a tutorial on how properties work.
And you should look into This Thread to avoid animations getting called multiple times etc by handling gesture states.
Related
When my user taps on my UITextView (NOT uitextfield...), an animation occurs. However after the UITextView begins editing, no other UITapGesture seems to be recognized (e.g. if I add a tapGesture to my UIView to dismiss this animation, it doesn't execute at all). I've been trying to fix this for what feels like forever. Help is appreciated, I'm stumped.
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
self.replyField.delegate = self;
[self.replyField setUserInteractionEnabled:YES];
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(textViewTapped)];
[self.view addGestureRecognizer:gestureRecognizer];
}
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
-(void)textViewTapped {
NSLog(#"DISMISS PLEASE!");
[self animateTextView:NO];
}
- (void)textViewDidBeginEditing:(UITextView *)textView
{
[self animateTextView: YES];
self.replyField.gestureRecognizers = nil;
}
- (void)textViewDidEndEditing:(UITextView *)textView
{
[self animateTextView:NO];
}
- (void) animateTextView:(BOOL) up
{
const int movementDistance = 206;
const float movementDuration = 0.3f;
int movement= movement = (up ? -movementDistance : movementDistance);
NSLog(#"%d",movement);
[UIView beginAnimations: #"anim" context: nil];
[UIView setAnimationBeginsFromCurrentState: YES];
[UIView setAnimationDuration: movementDuration];
self.view.frame = CGRectOffset(self.inputView.frame, 0, movement);
[UIView commitAnimations];
}
- (void)textViewDidChange:(UITextView *)textView
{
}
Your problem is not related to gesture recognizer.
Problem caused by line:
self.view.frame = CGRectOffset(self.inputView.frame, 0, movement);
You set frame for self.view from self.inputView with offset.
And when self.view size is changed, its subviews (including text view) may have positions out of superview bound. And UIView doesn't respond touch event if touch point is out of superview bounds.
You can easily check this by setting background color for views and show they frames in log:
...
- (void) animateTextView:(BOOL) up
{
NSLog(#"self.view state1:%#", self.view);
NSLog(#"self.replyField state1:%#", self.replyField);
self.view.backgroundColor = [UIColor redColor];
self.replyField.backgroundColor = [UIColor blueColor];
const int movementDistance = 206;
const float movementDuration = 0.3f;
int movement= movement = (up ? -movementDistance : movementDistance);
NSLog(#"%d",movement);
[UIView beginAnimations: #"anim" context: nil];
[UIView setAnimationBeginsFromCurrentState: YES];
[UIView setAnimationDuration: movementDuration];
//self.view.frame = CGRectOffset(self.inputView.frame, 0, movement);
self.replyField.frame = CGRectOffset(self.replyField.frame, 0, movement);
[UIView setAnimationDidStopSelector:#selector(afterAnimationStops)];
[UIView commitAnimations];
}
- (void)afterAnimationStops {
NSLog(#"self.view state2:%#", self.view);
NSLog(#"self.replyField state2:%#", self.replyField);
}
...
Also what is self.inputView? is it properly set?
BTW: What goal of the code? Do you want to move text view to avoid overlapping by keyboard? Then consider placing it in UIScrollView.
e.g see discussion here: How to make a UITextField move up when keyboard is present?.
I have encountered similar issues use follow below code to dismiss keyboard by tapping anywhere on screen view. Hope will helpful to you.
Objective c
- (void)viewDidLoad
{
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dismissKeyboard)];
[self.view addGestureRecognizer:gestureRecognizer];
gestureRecognizer.cancelsTouchesInView = NO;
}
- (void)dismissKeyboard
{
[self.view endEditing:YES];
}
Swift 4
func viewDidLoad() {
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))
view.addGestureRecognizer(gestureRecognizer)
gestureRecognizer.cancelsTouchesInView = false
}
func dismissKeyboard() {
view.endEditing(true)
}
In ViewDidLoad :
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(dismissKeyboard)];
[self.view addGestureRecognizer:tap];
Create one method :
-(void)dismissKeyboard {
[textView resignFirstResponder];
}
Not clearly understand what you want BTW let me explain.
First of all if you want to hide your keyboard you can do it by single line code.
Objective C
[self.view endEditing:YES];
Swift
view.endEditing(true)
Now in your code, you added tap gesture on self.view it means if you touch main view anywhere then your gesture method will be call. If you want that my keyboard will be hide when I touch on main view then in tap gesture's method you should write above code ([self.view endEditing:YES];) But make sure keyboard will be open if you tap on your textView.
But If you want keyboard should not open when I click on UITextView then there are many ways but follow below
Objective C
UIView* dummyInputView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
myTextView.inputView = dummyInputView;
Swift
let dummyInputView = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
myTextView.inputView = dummyInputView
Here keyboard will not be open but cursor will be blinking if you want to hide cursor then you can do by below code
Objective C
myTextView.tintColor = [UIColor clearColor];
Swift
myTextView.tintColor = UIColor.clear
If you have any issue then do comment.
In viewController you can do this
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self.view endEditing:Yes];
}
I tried to use initWithFrame but I am not able to display the UIView, This kind of code solved many questions as I saw here but not in my case:
- (id)initWithFrame:(CGRect)frame myParams:(NSArray*)params;
.m file
- (id)initWithFrame:(CGRect)frame myParams:(NSArray*)params{
self = [super initWithFrame:frame];
if(self){
UIView *view = [UIView alloc] initWithFrame:CGRectMake(0,0,100,100)];
//Trying to send an uiview with gesture include
view.userInteractionEnabled = YES;
UITapGestureRecognizer *tap = [UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(test)];
[view addGestureRecognizer:tap];
[self addSubview:view];
}
return self;
}
Here is how I am trying to add the view in UIViewController:
Maybe here I am doing it wrong, right?
Custom *view = [Custom alloc] initWithFrame:self.view.bounds myParams:array];
[self.view addSubview:view];
So, my question is it possible to add UITapGesture in the UIView generated in this class, cause it is not firing:
.h file
+ (UIView *)viewParams:(NSArray*)params;
.m file
+ (UIView *)viewWithParams:(NSArray*)params{
UIView *view = [UIView alloc] initWithFrame:CGRectMake(0,0,100,100)];
NSLog(#"read params %#", params);
//Trying to send an uiview with gesture include
view.userInteractionEnabled = YES;
UITapGestureRecognizer *tap = [UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(test)];
[view addGestureRecognizer:tap];
return view;
}
- (void)test{
NSLog('it works...');
}
UPDATE : As instance method is working. But as class method I can not make it work. somebody know if possible?
1st Part
If you are using XIB for your customView then you need to use awakeFromNib method. But if you are trying to create through code only then just use initWithFrame: method and add your gesture recognizer in the initializer.
2nd Part
For firing tap gesture recognizer you are adding recognizer target self i.e your customView. So event will fire in your customViewClass class only then to get these events in your controller you need to use protocol and delegate. And set the delegate of customView to whatever controller you want to use in the controller.
View will add after firing the initwithcoder just use this code in init with coder ...
dispatch_after(0.1, dispatch_get_main_queue(), ^(void)
{
UIView *view = [UIView alloc] initWithFrame:CGRectMake(0,0,100,100)];
//Trying to send an uiview with gesture include
view.userInteractionEnabled = YES;
UITapGestureRecognizer *tap = [UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(test)];
[view addGestureRecognizer:tap];
[self addSubview:view];
});
}
return self;
}
Minimum Time interval 0.1 max time interaval set as per your data load requirement
I have simple view controller and I add this controller like subview for window using this code:
UIWindow *window = [UIApplication sharedApplication].keyWindow;
if (!window)
window = [[UIApplication sharedApplication].windows objectAtIndex:0];
self.view.alpha = 0.0;
self.view.userInteractionEnabled = YES;
[window addSubview:self.view];
[self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeTriggered:)]];
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
self.view.alpha = 1.0;
}completion:nil];
But when I click at this view - nothing happens. Even event touchesBegan: are not called.
UPD:
Code above is in -(void)show method. I want to show one controller above all controllers.
In FirstViewController I create instance of CustonAlertViewController like that:
CustomAlertViewController *alertVC = [[CustomAlertViewController alloc]init];
[alertVC show];
In CustomAlertViewController I have show method presented at the top and viewDidLoad method with:
self.view.backgrondColor = [UIColor greenColor];
Hidden views (and equivalently, those with alpha == 0.0) do not respond to touches. If you require a fully transparent view, leave alpha as > 0.0, and say...
self.view.backgroundColor = [UIColor clearColor];
Alternatively, assign a nonzero alpha.
EDIT yes, the CustomAlertViewController instance is being deallocated immediately after show is invoked. The view controller that does the allocating needs to have a strong property to keep the alert around,
#property(nonatomic,strong) CustomAlertViewController *alertVC;
and adding...
CustomAlertViewController *alertVC = [[CustomAlertViewController alloc]init];
self.alertVC = alertVC;
[alertVC show];
This doesn't try to address some potential problems beyond the scope of this question (like rotation, or cleanly restoring when the alert is done).
// try like this , u need to give number of touches
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeTriggered:)];
gesture.numberOfTapsRequired = 1;
gesture.numberOfTouchesRequired = 1;
[self.view addGestureRecognizer:gesture];
You have been setting your UITapGestureRecognizer after you addSubview to your window. like:
You have been doing this
//...
[window addSubview:self.view];
[self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeTriggered:)]];
//...
Try this
Set UITapGestureRecognizer before you addSubview. And remember to add the UIGestureRecognizerDelegate to your ViewController
self.view.alpha = 0.2;
self.view.userInteractionEnabled = YES;
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeTriggered:)];
tapGesture.numberOfTapsRequired = 1;
[tapGesture setDelegate:self];
[self.view addGestureRecognizer:tapGesture];
// After you add your self.view to window
[window addSubview:self.view];
I hope this can help you.
In my project I would like to make a hidden image view appear when it is tapped for more than 3 seconds. I know I need to use NSTimer, but I have never created a UIImageView touch event. How can I combine the TapGestureRecognizer with NSTimer to achieve what I want to do? I am completely new to touch events in iOS, and I am just beginning to explore this. So, any help would be appreciated. Thank you!
UPDATE:
I implemented the UILongPressGestureRecognizer as below, but now, the hidden image appears even if I press somewhere outside of the image. How can I make it appear only if pressing the hidden image itself?
- (void)viewDidLoad
{
[super viewDidLoad];
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:#selector(handleLongPress:)];
longPress.numberOfTouchesRequired = 1;
longPress.minimumPressDuration = 3;
[self.view addGestureRecognizer:longPress];
}
-(void)handleLongPress:(UILongPressGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan)
{
BrokenGlass.hidden = NO;
}
}
You don't want a UITapGestureRecognizer and a timer, you want a UILongPressGestureRecognizer.
The image is appearing because you are using the gesture recognizer in the entire view.
[**self.view** addGestureRecognizer:longPress];
The gesture will not trigger on a hidden element.
Here is my solution:
#interface ViewController () <UIGestureRecognizerDelegate>
#property (nonatomic, strong) UIImageView *imageView;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// create the imageView
_imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
// enable the user interaction in the imageView (otherwise it will not receive events)
_imageView.userInteractionEnabled = YES;
// add as a subview of the main view
[self.view addSubview:_imageView];
// create the gesture recognizer
UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressHandler:)];
longPressGesture.delegate = self;
longPressGesture.minimumPressDuration = 3;
// add the gesture to the imageView
[_imageView addGestureRecognizer:longPressGesture];
}
#pragma mark - UIGestureRecognizerDelegate
- (void)longPressHandler:(UILongPressGestureRecognizer *)gestureRecognizer {
// show the image
_imageView.image = [UIImage imageNamed:#"cat.jpeg"];
}
I have a simple program that creates a subview and animates it across the screen.
As part of this program, I would like to add functionality when the subview is tapped. I am using the following method to create the subview, add the UITapGestureRecognizer and then animate the subview:
int randomName = arc4random() % ([pieceNames count] - 1);
int animationDuration = arc4random() % 5 + 5 ;
NSString *randomPiece = [pieceNames objectAtIndex:randomName];
float yStart = arc4random() % 650;
float yEnd = arc4random() % 650;
UIView *piece = [[PieceView alloc]initWithFrame:CGRectMake(100.0, yStart, 75.0, 75.0)];
[piece setValue:randomPiece forKey:#"name"];
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc]initWithTarget:self
action:#selector(handleTouch:)];
[piece addGestureRecognizer:recognizer];
[[self view] addSubview:piece];
[UIView animateWithDuration:animationDuration
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^(void){
piece.center = CGPointMake(950.0, yEnd);
} completion:^(BOOL done){
[piece removeFromSuperview];
}];
Here is the code that handles the tap:
PieceView *pv = (PieceView *) recognizer.view;
NSLog(#"%# was tapped", pv.name);
What happens is when a PieceView is touched the program does not respond. However, if I remove the animation block then the program responds to the tap.
Why does the UITapGestureRecognizer fail to respond to PieceView when it is animated?
I struggled with this same problem, and it boils down to this: the animated view only ever is in two places: the starting position, and the ending position. Core Animation simply renders the view's layer in interpolated positions between the start and end points over a period of time.
It's almost like when you look to the stars and realize that what you see is not actually what is happening right now. :)
Luckily, the solution is pretty simple. You can put a tap recognizer on the superview and then inspect your animated view's presentationLayer (which does give you an accurate frame at any point in time) to determine if your tap is a hit or not.
I've built a simple UIViewController that demonstrates both the problem and solution:
#import <UIKit/UIKit.h>
#interface MSMViewController : UIViewController
#end
And the implementation:
#import "MSMViewController.h"
#interface MSMViewController ()
#property (nonatomic, strong) UIView *animatedView;
#end
#implementation MSMViewController
- (void)viewDidLoad {
[super viewDidLoad];
CGRect startFrame = CGRectMake(125, 0, 70, 70);
CGRect endFrame = CGRectMake(125, 400, 70, 70);
// draw a box to show where the animated view begins
UIView *startOutlineView = [[UIView alloc] initWithFrame:startFrame];
startOutlineView.layer.borderColor = [UIColor blueColor].CGColor;
startOutlineView.layer.borderWidth = 1;
[self.view addSubview:startOutlineView];
// draw a box to show where the animated view ends
UIView *endOutlineView = [[UIView alloc] initWithFrame:endFrame];
endOutlineView.layer.borderColor = [UIColor blueColor].CGColor;
endOutlineView.layer.borderWidth = 1;
[self.view addSubview:endOutlineView];
self.animatedView = [[UIView alloc] initWithFrame:startFrame];
self.animatedView.backgroundColor = [UIColor yellowColor];
[self.view addSubview:self.animatedView];
[UIView animateWithDuration:10 delay:2 options:UIViewAnimationOptionAllowUserInteraction animations:^{
self.animatedView.frame = endFrame;
} completion:nil];
// this gesture recognizer will only work in the space where endOutlintView is
UITapGestureRecognizer *boxTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(boxTap:)];
[self.animatedView addGestureRecognizer:boxTap];
// this one will work
UITapGestureRecognizer *superviewTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(superviewTap:)];
[self.view addGestureRecognizer:superviewTap];
}
- (void)boxTap:(UITapGestureRecognizer *)tap {
NSLog(#"tap. view is at %#", NSStringFromCGPoint(self.animatedView.frame.origin));
}
- (void)superviewTap:(UITapGestureRecognizer *)tap {
CGRect boxFrame = [self.animatedView.layer.presentationLayer frame];
if (CGRectContainsPoint(boxFrame, [tap locationInView:self.view])) {
NSLog(#"we tapped the box!");
}
}
#end
The solution is much simpler, you just need to set the animation's option UIViewAnimationOptions.allowUserInteraction.
UIView.animate(withDuration: duration, delay: 0.1, options: [.allowUserInteraction], animations: {
...
}, completion: { (completed) in
...
}