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];
}
Related
This is so strange - for some reason, when my UIView animates up
(once I tap my UITextView), my UITapGesture doesn't execute? E.g. tapping anywhere on the view once the view is animated and UITextView is active doesn't dismiss the animated view? However if I tap anywhere on the view BEFORE it animates, UITapGesture executes just fine - why is this?
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.replyField.delegate = self;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(dismissKeyboard)];
[self.view addGestureRecognizer:tap];
}
-(void)dismissKeyboard {
[self animateTextView:NO];
}
- (void)textViewDidBeginEditing:(UITextView *)textView
{
[self animateTextView: YES];
}
- (void)textViewDidEndEditing:(UITextView *)textView
{
[self animateTextView:NO];
}
- (void) animateTextView:(BOOL) up
{
const int movementDistance = 206; // tweak as needed
const float movementDuration = 0.3f; // tweak as needed
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];
}
Once try like,
- (void) animateTextView:(BOOL) up
{
UIViewAnimationOptions options = UIViewAnimationOptionAllowUserInteraction;
const int movementDistance = 206; // tweak as needed
const float movementDuration = 0.3f; // tweak as needed
int movement= movement = (up ? -movementDistance : movementDistance);
[UIView animateWithDuration:movementDuration
delay:0.0
options:options
animations:^{
self.view.frame = CGRectOffset(self.inputView.frame, 0, movement);
}
completion:nil];
}
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.
How to move View up when Keyboard appeared in small screen like 4s, 5 and 5s but view should not move up when its enter inside iphone 6 and 6S.
I am sharing my code which is i have done in my app for all screen but in my condition view should not move up when its enter in 6 or 6s.
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
[self animateTextField:textField up:YES];
}
-(void)textFieldDidEndEditing:(UITextField *)textField
{
[self animateTextField:textField up:NO];
}
-(void)animateTextField:(UITextField*)textField up:(BOOL)up
{
const int movementDistance = -60; // tweak as needed
const float movementDuration = 0.3f; // tweak as needed
int movement = (up ? movementDistance : -movementDistance);
[UIView beginAnimations: #"animateTextField" context: nil];
[UIView setAnimationBeginsFromCurrentState: YES];
[UIView setAnimationDuration: movementDuration];
self.view.frame = CGRectOffset(self.view.frame, 0, movement);
[UIView commitAnimations];
}
I suggest you use IQKeyboardManager It will manage everything for you with less efforts
// Add a scrollview on main view and add UITextField on that scrollview
-(void) viewDidLoad
{
UIScrollView *myScrollView = [ [UIScrollView alloc] initWithFrame:[UIScreen mainScreen ].bounds];
[myScrollView .contentSize =CGSizeMake(320, 500);
[myScrollView .contentInset = UIEdgeInsetsMake(0, 0, 60, 0);
[self.view addSubView:myScrollView ];
UITextField *myTextField = [ [UITextField alloc] initWithFrame:CGRectMake(20,30,100,33)];
[myScrollView addSubView:myTextField ];
myTextField.delegate = self;
}
// Set the scrollview content offset to make the myTextField move up
- (void) textFieldDidBeginEditing:(UITextField *)textField
{
[myScrollView setContentOffset:CGPointMake(0,textField.center.y-80) animated:YES];
// here '80' can be any number which decide the height that textfiled should move
}
//To move the textfield to its original position
- (BOOL) textFieldShouldReturn:(UITextField *)textField
{
[[myScrollView setContentOffset:CGPointMake(0,0) animated:YES];
[textField resignFirstResponder];
return YES;
}
I am having an issue with CGAffineTransformConcat that is present in iOS 8, but not iOS 7. When animating a UIView using a concatenation with a scale and translate causes the position to jump. Running the same code in iOS 7, achieves the desired results. Is there something that has changed that causes this issue?
Here is my example code...
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//add a view
self.lilView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
self.lilView.backgroundColor = [UIColor greenColor];
[self.view addSubview:self.lilView];
//add a tap gesture
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(poop:)];
[self.view addGestureRecognizer:tap];
}
-(void) poop:(UITapGestureRecognizer*) gesture {
if (!self.pooped) {
[UIView animateWithDuration:1 delay:0 options:0 animations:^{
self.lilView.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(2.0, 2.0), CGAffineTransformMakeTranslation(200, 300));
self.pooped = YES;
} completion:nil];
} else {
[UIView animateWithDuration:1 delay:0 options:0 animations:^{
self.lilView.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.5, 1.5), CGAffineTransformMakeTranslation(100, 200));
self.pooped = NO;
} completion:nil];
}
}
Thank you for your help!
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
...
}