I am trying to display location of object that is moving by gesture. Everythink works fine untill I want to display its position. Below is code:
- (void)panDetected:(UIPanGestureRecognizer *)panRecognizer
{
CGPoint translation = [panRecognizer translationInView:self.view];
CGPoint imageViewPosition = _iconCamera.center;
imageViewPosition.x += translation.x;
// This line generates error
_labelCameraPosition.text = [NSString stringWithFormat: #"%.1f", _iconCamera.center.x];
self.iconCamera.center = imageViewPosition;
[panRecognizer setTranslation:CGPointZero inView:self.view];
}
When I want to display this value image is shaking but not moving. What should I do?
This answer is based on that you are using auto layout.
in viewDidAppear, set your label and imageView like this.
self.label.translatesAutoresizingMaskIntoConstraints = YES;
self.imageView.translatesAutoresizingMaskIntoConstraints = YES;
since you are changing frame when using auto layout. set translatesAutoresizingMaskIntoConstraints to yes automatically translate its new frame to constraints.
Related
I'm following Ray Wenderlich's 'iOS Games by Tutorials' & I got everything in my world setup & working: The entire game is in Landscape mode & there's one SKNode called _worldNode & everything, except _uiNode (in-game UI), is added to it. Player character walks to a touched location & _worldNode moves under him like a treadmill. However, like all functionality (or as they call it: "juice") addicts I wanted to add zoom in/out functionality through UIPinchGestureRecognizer by scaling _worldNode, which I did. But now every time I zoom in, the "camera" moves to the bottom left. Zooming out moves the view to the top right of the screen. It's a mess. I need the view to stay centered on the player character & I've tried everything I could come up with & find online. The closest thing I came to was using the technique from SKNode scale from the touched point but I still get the bloody bottom left/top right mess. I realized this mess happens only when I update the camera/view (it's really _worldNode.position). Therefore, 'didSimulatePhysics' or 'didFinishUpdate' methods don't help. In fact, even a one time button that slightly moves/updates the camera view (_worldNode.position) still gives me the bottom left/top right problem. Here is my code. I hope someone can take a look & tell me what to modify to get things working.
#interface GameScene () <SKPhysicsContactDelegate, UIGestureRecognizerDelegate>
{
UIPinchGestureRecognizer *pinchGestureRecognizer;
}
//Properties of my GameScene.
#property SKNode *worldNode;
#property etc. etc.
//Called by -(id)initWithSize:(CGSize)size method & creates the in-game world.
-(void)createWorld
{
[_worldNode addChild:_backgroundLayer];
[self addChild:_worldNode];
self.anchorPoint = CGPointMake(0.5, 0.5); //RW tutorial did it this way.
_worldNode.position = CGPointMake(-_backgroundLayer.layerSize.width/2, -_backgroundLayer.layerSize.height/2); //Center.
//Then I add every node to _worldNode from this point on.
}
//Neccessary for gesture recognizers.
-(void)didMoveToView:(SKView *)view
{
pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(handleZoomFrom:)];
[view addGestureRecognizer:pinchGestureRecognizer];
}
//"Camera" follows player character. 'didSimulatePhysics' doesn't help either.
-(void)didFinishUpdate
{
//IF '_pinchingScreen' == YES then screen pinching is in progress. Thus, camera position update will seize for the duration of pinching.
if (!_pinchingScreen)
{
_worldNode.position = [self pointToCenterViewOn:_player.position];
}
}
//Method that is called by my UIPinchGestureRecognizer. Answer from: https://stackoverflow.com/questions/21900614/sknode-scale-from-the-touched-point?lq=1
-(void)handleZoomFrom:(UIPinchGestureRecognizer*)recognizer
{
CGPoint anchorPoint = [recognizer locationInView:recognizer.view];
anchorPoint = [self convertPointFromView:anchorPoint];
if (recognizer.state == UIGestureRecognizerStateBegan)
{
// No code needed for zooming...
_player.movementMode = 2; //Stop character from moving from touches.
_pinchingScreen = YES; //Notifies 'didFinishUpdate' method that pinching began & camera position update should stop for now.
}
else if (recognizer.state == UIGestureRecognizerStateChanged)
{
//Technique from the above Stack Overflow link - Commented out.
// CGPoint anchorPointInMySkNode = [_worldNode convertPoint:anchorPoint fromNode:self];
//
// [_worldNode setScale:(_worldNode.xScale * recognizer.scale)];
//
// CGPoint mySkNodeAnchorPointInScene = [self convertPoint:anchorPointInMySkNode fromNode:_worldNode];
// CGPoint translationOfAnchorInScene = CGPointSubtract(anchorPoint, mySkNodeAnchorPointInScene);
//
// _worldNode.position = CGPointAdd(_worldNode.position, translationOfAnchorInScene);
//
// recognizer.scale = 1.0;
//Modified scale: 2.0
if(recognizer.scale > _previousWorldScale)
{
_previousWorldScale = recognizer.scale;
CGPoint anchorPointInMySkNode = [_worldNode convertPoint:anchorPoint fromNode:self];
[_worldNode setScale:2.0];
CGPoint worldNodeAnchorPointInScene = [self convertPoint:anchorPointInMySkNode fromNode:_worldNode];
CGPoint translationOfAnchorInScene = CGPointSubtract(anchorPoint, worldNodeAnchorPointInScene);
_worldNode.position = CGPointAdd(_worldNode.position, translationOfAnchorInScene);
//[_worldNode runAction:[SKAction scaleTo:2.0 duration:0]]; //This works too.
}
//Original scale: 1.0
if(recognizer.scale < _previousWorldScale)
{
_previousWorldScale = recognizer.scale;
CGPoint anchorPointInMySkNode = [_worldNode convertPoint:anchorPoint fromNode:self];
[_worldNode setScale:1.0];
CGPoint worldNodeAnchorPointInScene = [self convertPoint:anchorPointInMySkNode fromNode:_worldNode];
CGPoint translationOfAnchorInScene = CGPointSubtract(anchorPoint, worldNodeAnchorPointInScene);
_worldNode.position = CGPointAdd(_worldNode.position, translationOfAnchorInScene);
//[_worldNode runAction:[SKAction scaleTo:1.0 duration:0]]; //This works too.
}
}
else if (recognizer.state == UIGestureRecognizerStateEnded)
{
// No code needed here for zooming...
_pinchingScreen = NO; //Notifies 'didFinishUpdate' method that pinching has stopped & camera position update should resume.
_player.movementMode = 0; //Resume character movement.
}
}
So could anyone please tell me, by looking at the above code, why the camera/view shifts to the bottom left upon zooming in? I've sat several days on this problem & I still can't figure it out.
Thanks to JKallio, who wrote detailed code in his answer to Zoom and Scroll SKNode in SpriteKit, I've been able to find a piece of code that solves the problem. There's a method called 'centerOnNode' that is small, elegant & solves my problem perfectly. Here it is for anyone that just needs that:
-(void) centerOnNode:(SKNode*)node
{
CGPoint posInScene = [node.scene convertPoint:node.position fromNode:node.parent];
node.parent.position = CGPointMake(node.parent.position.x - posInScene.x, node.parent.position.y - posInScene.y);
}
Then you call that method inside your 'didSimulatePhysics' or inside 'didFinishUpdate' like so:
//"Camera" follows player character.
-(void)didFinishUpdate
{
//IF '_pinchingScreen' == YES then screen pinching is in progress. Thus, camera position update will seize for the duration of pinching.
if (!_pinchingScreen)
{
if (_previousWorldScale > 1.0) //If _worldNode scale is greater than 1.0
{
[self centerOnNode:_player]; //THIS IS THE METHOD THAT SOLVES THE PROBLEM!
}
else if (_previousWorldScale == 1.0) //Standard _worldNode scale: 1.0
{
_worldNode.position = [self pointToCenterViewOn:_player.position];
}
}
}
P.S. Just remember the question wasn't HOW to zoom in. But how to fix the issue with the camera once the world is ALREADY zoomed in.
I am developing a board game, and I have 10x10 cells in my board, each cell is taken by an UIImageView, so when player tried to place a piece into the cell, the corresponding cell should be set to an image.
What I want to achieve is when user drag a piece and try to put it onto the board, the piece should fit itself into the UIImageView of the cell. The problem now is I can put the piece anywhere on the board, even between two cells. What can I do to fix it? thanks
----------------Updated:----------------
Since I did everything in storyboard, so there's very few code I can share hare. I have following line of code in .h file:
#property(retain) IBOutletCollection(UIImageView) NSArray *images;
They are an array of UIImageView with length 100, each one of them corresponds to a cell in the board, like the following:
the icons currently on the board are auto generated, the target icon which I want to drag onto the board is located on the upper left of the board ('X' icon). and the following method which handles the drag:
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:self.view];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
}
The problem now is I can place the 'X' anywhere on the board, like the following:
but instead I want to put it to fit into a cell in the board.
You can achieve this by detecting which cell the translation has ended in. For example:
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:self.view];
CGPoint origin = yourGrid.frame.origin; //where yourGrid refers to the main game board
float cellHeight = 30; //enter the corresponding value for cellHeight and cellWidth
float cellWidth = 30; //I've chosen 30 as an arbitrary value.
int translationXIndex = (recognizer.view.center.x + translation.x - origin.x)/cellWidth;
int translationYIndex = (recognizer.view.center.y + translation.y - origin.y)/cellHeight;
//Assuming you count left to right and then up to down
[[images objectAtIndex: (translationXIndex + (10*translationYIndex))] setImage:[UIImage imageNamed:#"YourImage.png"]];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
//You would have to remove the object you just dragged now, since you've changed the image of the corresponding cell
}
How this works: The recogniser looks for the centre of the translation and finds out which cell it lies inside. It then accesses the imageView of that cell and changes the image to the target image.
Hope this helps.
I have a view, inside that view I have an UIImageview but inside this UIImageview I have 3 more UIImageview, this 3 UIImageview I can drag them and resize them, the problem is that when i resize one of them all of them return to the original position, why is this happening ?
UPDATE:
I use this for dragging the image
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:_photo];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:_photo];}
This for resizing, using an slider
- (IBAction)sizePhoto:(UISlider *)sender {
switch (cambiaSize) {
case 1:
[_imagen1 setTransform:CGAffineTransformMakeScale(sender.value, sender.value)];
break;
case 2:
[_imagen2 setTransform:CGAffineTransformMakeScale(sender.value, sender.value)];
break;
case 3:
[_imagen3 setTransform:CGAffineTransformMakeScale(sender.value, sender.value)];
break;}}
I have also used this for resizing
_imagen1.transform = CGAffineTransformScale(self.view.transform,sender.value, sender.value);
and I also flip horizontally the image using this
_imagen2.transform = CGAffineTransformScale(_imagen2.transform,1.0, 1.0);
I would like to keep the transformation while doing any of this actions...
This could be a consequence of using auto layout. If you directly change the frame of an object, it will revert to the frame defined by the constraints when some other action causes the view to redraw. You either need to turn off auto layout, or change the position of your views by modifying their constraints.
Use CGAffineTransformScale instead of CGAffineTransformMakeScale. 'MakeScale' creates a whole new identity transform with applied scale whereas the former method simply applies a scale to the pre-existing transform.
UIImageView *imageView = [[UIImageView alloc] init];
CGFloat exampleValueA = 90.0f, exampleValueB = 90.0f;
imageView.transform = CGAffineTransformScale(imageView.transform, exampleValueA, exampleValueB);
I have a pan gesture recogniser acting as a slider, code as follows:
- (void)minutePan:(UIPanGestureRecognizer *)gesture
{
if ((gesture.state == UIGestureRecognizerStateChanged) ||
(gesture.state == UIGestureRecognizerStateEnded)){
CGPoint translation = [gesture translationInView:gesture.view.superview];
float leftBound = gesture.view.bounds.size.width / 2.0f;
float rightBound = gesture.view.bounds.size.width + (leftBound - 60);
float position;
if(gesture.view.center.x < leftBound){
position = leftBound;
}else if(gesture.view.center.x > rightBound){
position = rightBound;
}else{
position = gesture.view.center.x + translation.x;
}
gesture.view.center = CGPointMake(position, gesture.view.center.y);
[gesture setTranslation:CGPointZero inView:gesture.view.superview];
}
}
I have a UIView with the gesture recogniser attached inside another UIView acting as the boundary for it. Its working fine except when I get to the left and right edges. The position variable should not get set lower than half the panned views width but it seems to be updating the view on the screen before the code runs. This causes the view to go outside the bounds and flicker back as long as the user is dragging. Any help would be greatly appreciated thanks.
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(pan:)];
[self addGestureRecognizer:panRecognizer];
- (void)pan:(UIPanGestureRecognizer *)gesture {
NSLog(#"%f", [gesture translationInView:self].x);
}
The above code will log the relative position of my current pan, but how can I get the absolute position for the view I'm in?
I'm simply just wanting to slide a UIImageView to wherever the user's finger is.
translationInView gives you the pan translation (how much x has changed) and not the position of the pan in the view (the value of x). If you need the position of the pan, you have to use the method locationInView.
You can find the coordinates relatively to the view as follows:
- (void)pan:(UIPanGestureRecognizer *)gesture {
NSLog(#"%f", [gesture locationInView:self].x);
}
Or relatively to the superview:
- (void)pan:(UIPanGestureRecognizer *)gesture {
NSLog(#"%f", [gesture locationInView:self.superview].x);
}
Or relatively to the window:
- (void)pan:(UIPanGestureRecognizer *)gesture {
NSLog(#"%f", [gesture locationInView:self.window].x);
}
Swift 5
Use the method .location() that returns a CGPoint value. [documentation]
For example, relative location of your gesture to self.view:
let relativeLocation = gesture.location(self.view)
print(relativeLocation.x)
print(relativeLocation.y)
I think a simple way of something like this is to get the x and y of the touch and tracking it, once it has 2 points (say X:230 Y:122) you set the scroll of the UIScroll view to the x and y.