iOS 8 CGAffineTransformConcat with Scale and Translate cause a position jump - ios

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!

Related

Obj-C - Dismiss keyboard by tapping anywhere on screen from UITextView?

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];
}

iOS keyboard animation conflict

I am currently writing an iOS app and encountered some animation issue. The following is the test code to show my question. It's just a very simple animation that lets the textField to move up when the keyboard shows and move down when the keyboard hides. Interestingly, if it is an English keyboard, it seems that the UIView animation somehow conflicts with the keyboard animation and no animations are performed, but if I switch to the Chinese keyboard, it works fine. I just want to confirm that whether it is a bug from apple....
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UITextField *textField = [[UITextField alloc] init];
self.textField = textField;
textField.placeholder = #"Click me!!";
textField.frame = CGRectMake(100, 350, 150, 40);
[self.view addSubview:self.textField];
self.textField.delegate = self;
}
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
[UIView animateWithDuration:0.25 animations:^{
CGRect frame = self.textField.frame;
frame.origin.y -= 100;
self.textField.frame = frame;
}];
return YES;
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
[self.textField resignFirstResponder];
[UIView animateWithDuration:0.25 animations:^{
CGRect frame = self.textField.frame;
frame.origin.y += 100;
self.textField.frame = frame;
}];
return YES;
}
UPDATE
It seems that it is a bug from apple... I tried to use iOS 8.3 simulator, and it works super smoothly... In iOS 9.3, if you just try showing and hiding the keyboard, even without moving the textField, the animation will get stuck after several operations...
I'd advise you to move the animation blocks to methods from the notification UIKeyboardWill{Show|Hide}Notification. The delegate methods from your text field are asking about the text field and its behavior – nothing to do with the keyboard itself (and you can even synchronize the duration, curve and delay).
You're returning YES to -textFieldShouldReturn: which is only supposed to check if the text field should process the return key tap.
From my experience with animations, sometimes everything works fine when testing with a real device. I made the suggested changes over here and everything worked fine on an iPhone 5c (iOS 8.1) and iPhone 6s Plus (iOS 10, Beta 1).
Here's a snippet of how it looks like:
- (void)viewDidLoad {
[super viewDidLoad];
[self setupNotifications];
[self initUIElements];
}
- (void)initUIElements {
CGRect frame = CGRectMake(0.f, 20.f, 200.f, 44.f);
self.textField = [[UITextField alloc] initWithFrame:frame];
self.textField.center = CGPointMake(self.view.center.x, 32.f);
self.textField.placeholder = #"Placeholder";
self.textField.delegate = self;
[self.view addSubview:self.textField];
self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 50.f, 50.f)];
self.imageView.center = self.view.center;
self.imageView.backgroundColor = [UIColor magentaColor];
[self.view addSubview:self.imageView];
}
- (void)setupNotifications {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
}
- (void)keyboardWillHide:(id)sender {
[UIView animateWithDuration:1.f animations:^{
self.textField.center = CGPointMake(self.view.center.x, 32.f);
self.imageView.center = self.view.center;
}];
}
- (void)keyboardWillShow:(id)sender {
[UIView animateWithDuration:1.f animations:^{
self.textField.center = CGPointMake(self.view.center.x, 62.f);
self.imageView.center = CGPointMake(self.view.center.x, (self.view.center.y - 50.f));
}];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return YES;
}
#lin fu
.m
- (void)viewDidLoad {
[super viewDidLoad];
UITextField *textField = [[UITextField alloc] init];
textField.placeholder = #"Click me!!";
textField.delegate = self;
textField.frame = CGRectMake(100, 350, 150, 40);
[self.view addSubview:textField];
}
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
[UIView animateWithDuration:0.25 animations:^{
CGRect frame = textField.frame;
frame.origin.y -= 100;
textField.frame = frame;
}];
return YES;
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
[UIView animateWithDuration:0.25 animations:^{
CGRect frame = textField.frame;
frame.origin.y += 100;
textField.frame = frame;
}];
return YES;
}
in .h
Try this out. It works for me.
you have another option you can use the third party library call TPAvoidingKeyBoard it will help you out in there

How can I display a fullscreen image with a black background?

I implemented the feature to display a fullscreen image when it is tapped. However this only enlarge the image but does not show it with a black background (the other elements of the view are still in the bakcground). How can I do this?
//Setup touch up inside of the imageview (in viewdidload)
userPictImageView.userInteractionEnabled = YES;
userPictImageView.contentMode = UIViewContentModeScaleAspectFit;
UITapGestureRecognizer *tapper = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imageViewFullScreenTouchHandler:)];
tapper.numberOfTapsRequired = 1;
[userPictImageView addGestureRecognizer:tapper];
//present a full screen image (without black background)
-(IBAction)imageViewFullScreenTouchHandler:(id)sender {
//goto new ViewController and set the image
UIImageView *imageView = userPictImageView;
if (!imageIsFullScreen) {
[UIView animateWithDuration:0.5 delay:0 options:0 animations:^{
//save previous frame
prevFrame = imageView.frame;
blackView=[[UIView alloc]initWithFrame:self.view.frame];
[blackView setBackgroundColor:[UIColor blackColor]];
[self.view addSubview:blackView];
CGRect frame = self.view.frame;
[imageView setFrame:frame];
[blackView addSubview:imageView]; }
completion:^(BOOL finished){
imageIsFullScreen = TRUE;
}];
return;
}
else{
self.view.backgroundColor = [UIColor clearColor];
[UIView animateWithDuration:0.5 delay:0 options:0 animations:^{
[imageView setFrame:prevFrame];
}completion:^(BOOL finished){
imageIsFullScreen = FALSE;
}];
return;
}
}
You could change to UIViewContentModeScaleToFill, or, probably better, build a UIView with a black background whose frame matches the view frame. Make the image view a subview of that black view.
I'd do it something like this. Create a method that I can call to zoom and unzoom the imageView. To zoom, build a background view and attach the image. To unzoom, do the reverse, and then destroy the background view.
Since the background view will be full screen, attach a gesture recognizer to it so the user can tap to dismiss.
- (void)setUserPictImageViewZoomed:(BOOL)zoom animated:(BOOL)animated {
UIView *blackView = [self.view viewWithTag:128];
BOOL isZoomed = blackView != nil;
// if our current state (isZoomed) matches the desired state (zoomed), then we're done
if (isZoomed == zoom) return;
NSTimeInterval duration = (animated)? 0.3 : 0.0;
if (zoom) {
blackView = [[UIView alloc] initWithFrame:self.view.frame];
blackView.backgroundColor = [UIColor blackColor];
blackView.tag = 128;
blackView.alpha = 0.0;
[self.view addSubview:blackView];
UITapGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(unzoom:)];
[blackView addGestureRecognizer:tapGR];
UIImageView *zoomImageView = [[UIImageView alloc] initWithImage:self.userPictImageView.image];
zoomImageView.contentMode = UIViewContentModeScaleAspectFit;
zoomImageView.tag = 129;
zoomImageView.frame = [self.userPictImageView convertRect:self.userPictImageView.frame toView:blackView];
[blackView addSubview:zoomImageView];
[UIView animateWithDuration:duration animations:^{
zoomImageView.frame = blackView.bounds;
blackView.alpha = 1.0;
}];
} else {
UIImageView *zoomImageView = (UIImageView *)[blackView viewWithTag:129];
[UIView animateWithDuration:duration animations:^{
zoomImageView.frame = self.userPictImageView.frame;
blackView.alpha = 0.0;
} completion:^(BOOL finished) {
[blackView removeFromSuperview];
}];
}
}
- (void)unzoom:(UIGestureRecognizer *)gr {
[self setUserPictImageViewZoomed:NO animated:YES];
}
I tested this quickly in a simulator and it looked pretty slick.

Show half sreen view that slides down from the top

I have a mapView which has a size of (320, 182). Once the UIBarButtonItem is pressed, I would like to show this mapView sliding down from top of the screen. To achieve this, I have written a code like this.
UIBarButtonItem *mapButton = [[UIBarButtonItem alloc]
initWithImage:[UIImage imageNamed:#"map_1.png"]
style:UIBarButtonItemStylePlain
target:self
action:#selector(showMap:)
];
self.navigationItem.rightBarButtonItem = mapButton;
-(void)showMap:(id)sender{
self._mapView = [[MKMapView alloc] initWithFrame:CGRectMake(0,-182,320,182)];
[self.scrollView addSubview:self._mapView];
//show view
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:1.0];
CGRect rect = [self._mapView frame];
rect.origin.y = 0;
[self._mapView setFrame:rect];
[UIView commitAnimations];
}
This will show the map view which slides down from top of the screen. However, how do I make the mapView dismiss when the UIBarButtonItem been pressed on the second time? Moreover, I also would like to dismiss the mapView when tapped outside the mapView.
This is a vary basic implementation of what you are asking for. But it will do the trick.
#import "ViewController.h"
#import "MKMapView.h"
#interface ViewController () {
MKMapView *mapView;
BOOL showingMap;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//
UIBarButtonItem *mapButton = [[UIBarButtonItem alloc]
initWithTitle:#"Map"
style:UIBarButtonItemStylePlain
target:self
action:#selector(toggleMap:)
];
self.navigationItem.rightBarButtonItem = mapButton;
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dismissView:)];
[self.view addGestureRecognizer:tapRecognizer];
}
-(void)toggleMap:(id)sender{
if (!mapView) {
mapView = [[MKMapView alloc] initWithFrame:CGRectMake(0,-182,320,182)];
mapView.backgroundColor = [UIColor redColor]; // red background for testing
[self.view addSubview:mapView];
}
CGRect rect = mapView.frame;
rect.origin.y = showingMap ? -rect.size.height : 0;
showingMap = !showingMap;
[UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
mapView.frame = rect;
} completion:^(BOOL finished) {
}];
}
- (void)dismissView:(UITapGestureRecognizer*)recognizer {
// bail out since map isn't showing
if (!showingMap) return;
CGPoint pt = [recognizer locationInView:self.view];
UIView *v = [self.view hitTest:pt withEvent:nil];
if (![v isKindOfClass:[MKMapView class]]) {
NSLog(#"dismiss");
[self toggleMap:nil];
return;
}
}
#end
Just reverse the animation (which you should do in the more modern block methods):
rect.origin.y = -182;
[UIView animateWithDuration:1.0 animations^{
self.mapView.frame = rect;
}

UITapGestureRecognizer not responding on animated subview

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
...
}

Resources