View jumps back after setting center in iOS 8 - objective-c

I’m beginner with Xcode, and i have problem with counter in my app.
When i put timer to count points the player(paddle) jump in the place where it was
in first time(in the middle)
everytime when timer count point.
So what i should do to keep paddle where it is?
Is there something other way to count points?
Its doesnt matter how to control player with swipe or g-sensor,
but in this example i control it with swipe.
And this problem appears only in ios 8.0> , not in ios 7.
Here some code:
.h file:
int ScoreNumber;
#interface ViewController : UIViewController{
IBOutlet UILabel *ScoreLabel;
NSTimer *Timer;
}
#property (nonatomic, strong)IBOutlet UIImageView *paddle;
#property (nonatomic) CGPoint paddleCenterPoint;
#end
.m file:
#implementation ViewController
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
CGFloat yPoint = self.paddleCenterPoint.y;
CGPoint paddleCenter = CGPointMake(touchLocation.x, yPoint);
self.paddle.center = paddleCenter;
}
-(void)Score{
ScoreNumber = ScoreNumber +1;
ScoreLabel.text = [NSString stringWithFormat:#"%i", ScoreNumber];
}
-(void)viewDidLoad {
Timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(Score) userInfo:nil repeats:YES];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}

In iOS 8, autolayout is king, and setting center doesn't change the layout constraints. This has nothing to do with the timer. It has to do with changing the label's text. When that happens, it triggers a layout of the whole view, and that reasserts the constraints (via layoutIfNeeded). Even though you're probably not setting any constraints in IB, Xcode will automatically insert them during the build.
There are several solutions. First, you could insert the paddle programmatically in viewDidLoad, which would bypass the constraints system. For this specific problem, that's probably ok (and it might even be how I'd do it personally). Something like this:
#interface ViewController ()
#property (nonatomic, strong) UIView *paddle;
#end
#implementation ViewController
- (void)viewDidLoad {
self.paddle = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 200, 50)];
self.paddle.backgroundColor = [UIColor blueColor];
[self.view addSubview:self.paddle];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
self.paddle.center = CGPointMake(touchLocation.x, self.paddle.center.y);
}
#end
But how can we solve this without throwing away constraints? Well, we could just modify the constraint. Add your paddle view in IB, and add constraints for the left, bottom, width, and height. Then create an IB outlet for the horizontal and width constraints.
Edit the Horizontal Space Constraint to remove "Relative to margin" from both the first and second items:
Now, you can modify the constraint rather than the center:
#interface ViewController ()
#property (nonatomic, weak) IBOutlet UIView *paddle;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *paddleHorizontalConstraint;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *paddleWidthConstraint;
#end
#implementation ViewController
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
CGFloat width = self.paddleWidthConstraint.constant;
self.paddleHorizontalConstraint.constant = touchLocation.x - width/2;
}
#end
You need to read the width constraint because the frame or bounds may not be correct until viewDidLayoutSubviews: is called. But you don't want to update the constraint there, since that'll force another layout.

Related

With autolayout, label position is reset when I change label text

I am developing a very simple application in Objective-c.
In the app, the user can change a position of the label by dragging the screen.
Tap & drag the screen, the label moves up and down to match the position of the finger.
When you release your finger, the coordinates information of the label is set to its text.
But the label position is reset at the moment its text is changed.
// IBOutlet UILabel *label
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint touch = [[touches anyObject] locationInView:self.view];
label.center = CGPointMake(label.center.x, touch.y);
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint touch = [[touches anyObject] locationInView:self.view];
label.center = CGPointMake(label.center.x, touch.y);
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint touch = [[touches anyObject] locationInView:self.view];
label.text = [NSString stringWithFormat:#"y = %f", touch.y];
}
In touchesEnded event, just only change the text of the label,
but the position of it has reset.
I tried to change touchesEnded event like following but it haven't solved the problem.
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint touch = [[touches anyObject] locationInView:self.view];
label.text = [NSString stringWithFormat:#"y = %f", touch.y];
label.center = CGPointMake(label.center.x, touch.y); // add this line
}
I want to resolve this weird behavior without uncheck "Use auto layout".
I'd like to keep using auto layout.
I have 4 screenshots for my app.
4 Screenshots
The first image is a Storyboard.
I have a label with auto layout constraints.
The second image is a screenshot right after launching app.
The third image is when a user drag the screen,
and the label move down to match the finger.
The forth image is right after release your finger
and the label text has changed.
You cannot use auto layout and change the center / frame of things; those are opposites. Do one or the other - not both.
So, you don't have to turn off auto layout, but if you don't, then you must use auto layout, and auto layout only, to position things.
When you move the label, do not change its center - change its constraints. Or at least, having changed its center, change its constraints to match.
Otherwise, when layout occurs, the constraints will put it back where you have told them to put it. And layout does occur when you change the text, and at many other times.
Solution by OP.
Constraint can be treated as an IBOutlet on Storyboard:
Associating constraints as well as label or other IBOutlet.
// ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController {
IBOutlet UILabel *label;
IBOutlet NSLayoutConstraint *lcLabelTop;
}
#end
// ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panAction:)];
[self.view addGestureRecognizer:pan];
}
- (void)panAction : (UIPanGestureRecognizer *)sender
{
CGPoint pan = [sender translationInView:self.view];
lcLabelTop.constant += pan.y;
if (sender.state == UIGestureRecognizerStateEnded) {
label.text = [NSString stringWithFormat:#"y = %f", lcLabelTop.constant];
}
[sender setTranslation:CGPointZero inView:self.view];
}

implementing touches to drag various images with finger

I'm trying to be able to drag various images around using touches. So far, I'm trying to get it to work with just 1 image, but it's not working (i.e. I can't move the image with my finger). What am I doing wrong?
I have several files with the following code:
DragView.h
#interface DragView : UIImageView {
}
#end
DragView.m
#include "DragView.h"
#implementation DragView
- (void)touchesMoved:(NSSet *)set withEvent:(UIEvent *)event {
CGPoint p = [[set anyObject] locationInView:self.superview];
self.center = p;
}
#end
ViewController.h
#import <UIKit/UIKit.h>
#import "DragView.h"
#interface ViewController : UIViewController {
}
#property (nonatomic, strong) DragView *basketView;
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize basketView;
- (void)viewDidLoad
{
[super viewDidLoad];
basketView = [[DragView alloc]
initWithImage:[UIImage imageNamed:#"basket.png"]];
basketView.frame = CGRectMake(140, 340.2, 60, 30);
[self.view addSubview:basketView];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Try this:
basketView.userInteractionEnabled = YES;
Documentation says that:
New image view objects are configured to disregard user events by default. If you want to handle events in a custom subclass of UIImageView, you must explicitly change the value of the userInteractionEnabled property to YES after initializing the object.

Implementing UITapGestureRecognizer directly into UIView instead of ViewController does not work

As explained in this SO-answer I am trying to implement UITapGestureRecognizer in a subclass of UIIMageView. My approach can be found below.
An instance of MenuButtonImageView is a subview of homeScreenViewController.view.
I implemented touchesBegan:withEvent:, touchesMoved:withEvent: and touchesEnded:withEvent: too and do work fine.
Problem: Somehow handleSingleFingeredTap: is never called. What could be the problem with this?
#interface MenuButtonImageView : UIImageView
#property (nonatomic, weak) IBOutlet HomescreenViewController *homeScreenViewController;
#property (nonatomic, readwrite) Visibility visibility;
- (void)handleSingleFingeredTap:(UITapGestureRecognizer *)recognizer;
#end
#implementation MenuButtonImageView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.visibility = HIDDEN;
self.userInteractionEnabled = YES;
UITapGestureRecognizer * singleFingeredTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleFingeredTap:)];
singleFingeredTap.numberOfTapsRequired = 1;
[self addGestureRecognizer:singleFingeredTap];
}
return self;
}
- (void)handleSingleFingeredTap:(UITapGestureRecognizer *)recognizer {
NSLog(#"handleTap was called"); // This method is not called!
[self toggleMainMenu];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { ... }
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { ... }
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { ... }
#end
You add the recognizer in initWithFrame method. Check if your view is initialized this way. It may be created with another constructor like initWithCoder, and then your code is not executed. Put a breakpoint inside initWithFrame method to find out.
You can put the code in a separate method and call it from both constructors. It depends on how you use it. The initWithFrame is used quite common when you create a view from code.

UIImageview and TouchesEnded Event

Im trying to write a simple program that takes 5 images and allows you to drag them from the bottom of the screen and snap them on to 5 other images on the top in any order you like. I have subclassed the UIImageView with a new class and added the touches began, touches moved and touches ended. Then on my main viewcontroller I place the images and set their class to be the new subclass I created. The movement works great in fact I am NSLogging the coordinates in the custom class.
Problem is I'm trying to figure out how to get that CGpoint info from the touches end out of the custom class and get it to call a function in my main view controller that has the image objects so i can test whether or not the image is over another image. and move it to be centered on the image its over (snap onto it).
here is the code in my custom class m file. MY view controller is basically empty with a xib file with 10 image views 5 of which are set to this class and 5 are normal image views..
#import "MoveImage.h"
CGPoint EndPoint;
#implementation MoveImage
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
startPoint = [[touches anyObject] locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint newPoint = [[touches anyObject] locationInView:self.superview];
newPoint.x -= startPoint.x;
newPoint.y -= startPoint.y;
CGRect frm = [self frame];
frm.origin = newPoint;
[self setFrame:frm];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *end = [[event allTouches] anyObject];
EndPoint = [end locationInView:self];
NSLog(#"end ponts x : %f y : %f", EndPoint.x, EndPoint.y);
}
#end
You could write a MoveImageDelegate protocol which your app controller would then implement. This protocol would have methods that your MoveImage class can call to get its information.
In your header file ( if you don't know how to write a protocol )
#protocol MoveImageDelegate <TypeOfObjectsThatCanImplementIt> // usually NSObject
// methods
#end
then you can declare an instance variable of type id, but still have the compiler recognize that that variable responds to your delegate selectors ( so you can code without those annoying warnings ).
Something like:
#interface MoveImage : UIImageView
{
id <MoveImageDelegate> _delegate;
}
#property (assign) id <MoveImageDelegate> delegate;
Lastly:
#implementation MoveImage
#synthesize delegate = _delegate;
And your delegate is complete.

-(void)touchesBegan (TO A SPECIFIED UIIMAGEVIEW)

Hi I want to make it so that if you touch an image that I put on the view the -(void)checkcollision happens. The -(void)checkcollision happens, but when I touch anything. How can I say it only works if i touch a specified image. e.g. a pimple:
IBOutlet UIImageView *pimple;
#property (nonatomic, retain) UIImageView *pimple;
here is the touchesBegan code
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *myTouch = [[event allTouches] anyObject];
[self checkcollision];
}
-(void)checkcollision {
if (label.text = #"0") {
label.text = #"1";
}
pimple.hidden = YES;
}
CGPoint point = [myTouch locationInView:pimple];
if ( CGRectContainsPoint(pimple.bounds, point) ) {
... Touch detected.
}
Alternatively you can consider gesture recognizers. You can use a tap recognizer for this case.
There are two options:
Subclass UIImageView and define your own handler
Check sender - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event and compare it to your UIImageView instance.