I added a -(void) detectTouch: (UIPanGestureRecognizer *) event for UIScrollView and detecting the angle on which user is moving his finger. My task is to scroll the UIScrollView horizontally only when user is moving the finger between 0 - 30 degrees (just to make sure he is drawing a horizontal straight line) otherwise I have to disable the UIScrollView scroll.
I am detecting the angle by drawing a triangle using the touch starting point and ending point.
Problem: I enabled the UIScrollView scroll when the angle is < 30 degrees but this is not working on the first time. Although I enabled scroll using scrollEnabled = YES it is working only when user is stopped touching the screen (taking the finger from the screen).
The following code I used to
- (void)viewDidLoad
{
[super viewDidLoad];
[self PanGesture:self.view callBack:#selector(detectTouch:) delegate:self];
incrementer = 0;
}
-(void) detectTouch: (UIPanGestureRecognizer *) event{
// Calculating point A on gesture starts
if(event.state == UIGestureRecognizerStateBegan){
pointA.x = fabs([event translationInView:event.view].x);
pointA.y = fabs([event translationInView:event.view].y);
NSLog(#"A: %f, %f", pointA.x, pointA.y);
}
incrementer += 1;
// Start calculating Point B, Point C on calling this function 3 times
if(incrementer >= 3){
// Calculating point C
pointC.x = fabs([event translationInView:event.view].x);
pointC.y = fabs([event translationInView:event.view].y);
NSLog(#"C: %f, %f", pointC.x, pointC.y);
// calculate pointB using A, C
pointB.x = fabs(pointC.x);
pointB.y = fabs(pointA.y);
NSLog(#"B: %f, %f", pointB.x, pointB.y);
float X = pointB.x - pointA.x;
float Y = pointC.y - pointB.y;
float angle = (atan(fabs(Y) / fabs(X)) * 180 / M_PI);
if(angle > 30){
// This disable is not working on while user is moving the finger
self.myScrollView.scrollEnabled = NO;
NSLog(#"UIScroll Disabled");
}else{
// This enable is not working on while user is moving the finger
self.myScrollView.scrollEnabled = YES;
NSLog(#"UIScroll Enabled");
}
incrementer = 0;
}
}
How can I enable UIScrollView scroll while user is moving the touch?
Since UIScrollView seems to accept scrolling only with a new touch after you set .scrollEnabled to YES, I would do the following:
set .scrollEnabled to NO
Track the touch (e. g. with touchesMoved::)
check whether you 30 degrees condition is met
use the movement to adjust UIScrollView.contentOffset
Related
I have a UICollectionView with 6 pages, and paging enabled, and a UIPageControl. What I want is, when I came to the last page, if I drag to right, UICollectionView reloads from first page seamlessly.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender
{
// The key is repositioning without animation
if (collectionView.contentOffset.x == 0) {
// user is scrolling to the left from image 1 to image 10.
// reposition offset to show image 10 that is on the right in the scroll view
[collectionView scrollRectToVisible:CGRectMake(collectionView.frame.size.width*(pageControl.currentPage-1),0,collectionView.frame.size.width,collectionView.frame.size.height) animated:NO];
}
else if (collectionView.contentOffset.x == 1600) {
// user is scrolling to the right from image 10 to image 1.
// reposition offset to show image 1 that is on the left in the scroll view
[collectionView scrollRectToVisible:CGRectMake(0,0,collectionView.frame.size.width,collectionView.frame.size.height) animated:NO];
}
pageControlUsed = NO;
}
It doesn't work like I want. What can I do?
Here's what I ended up with for my UICollectionView (horizontal scrolling like the UIPickerView):
#implementation UIInfiniteCollectionView
- (void) recenterIfNecessary {
CGPoint currentOffset = [self contentOffset];
CGFloat contentWidth = [self contentSize].width;
// don't just snap to center, since this might be done in the middle of a drag and not aligned. Make sure we account for that offset
CGFloat offset = kCenterOffset - currentOffset.x;
int delta = -round(offset / kCellSize);
CGFloat shift = (offset + delta * kCellSize);
offset += shift;
CGFloat distanceFromCenter = fabs(offset);
// don't always recenter, just if we get too far from the center. Eliza recommends a quarter of the content width
if (distanceFromCenter > (contentWidth / 4.0)) {
self.contentOffset = CGPointMake(kCenterOffset, currentOffset.y);
// move subviews back to make it appear to stay still
for (UIView *subview in self.subviews) {
CGPoint center = subview.center;
center.x += offset;
subview.center = center;
}
// add the offset to the index (unless offset is 0, in which case we'll assume this is the first launch and not a mid-scroll)
if (currentOffset.x > 0) {
int delta = -round(offset / kCellSize);
// MODEL UPDATE GOES HERE
}
}
}
- (void) layoutSubviews { // called at every frame of scrolling
[super layoutSubviews];
[self recenterIfNecessary];
}
#end
Hope this helps someone.
I've been using the Street Scroller sample to create an infinite scroller for images. That works fine until I wanted to set pagingEnabled = YES; Tried tweaking around the recenterIfNecessary code and finally realized that it's the contentOffset.x that has to match the frame of the subview that i want visible when paging stops. This really isn't going to work in recenterIfNecessary since you have no way of knowing it will get called from layoutSubviews. If you do get it adjusted right, the subview may pop out from under your finger. I do the adjustment in scrollViewDidEndDecelerating. So far I haven't had problems with scrolling fast. It will work and simulate paging even when pagingEnabled is NO, but it looks more natural with YES.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[super scrollViewDidEndDecelerating:scrollView];
CGPoint currentOffset = [self contentOffset];
// find the subview that is the closest to the currentOffset.
CGFloat closestOriginX = 999999;
UIView *closestView = nil;
for (UIView *v in self.visibleImageViews) {
CGPoint origin = [self.imageContainerView convertPoint:v.frame.origin toView:self];
CGFloat distanceToCurrentOffset = fabs(currentOffset.x - origin.x);
if (distanceToCurrentOffset <= closestOriginX) {
closestView = v;
closestOriginX = distanceToCurrentOffset;
}
}
// found the closest view, now find the correct offset
CGPoint origin = [self.imageContainerView convertPoint:closestView.frame.origin toView:self];
CGPoint center = [self.imageContainerView convertPoint:closestView.center toView:self];
CGFloat offsetX = currentOffset.x - origin.x;
// adjust the centers of the subviews
[UIView animateWithDuration:0.1 animations:^{
for (UIView *v in self.visibleImageViews) {
v.center = [self convertPoint:CGPointMake(v.center.x+offsetX, center.y) toView:self.imageContainerView];
}
}];
}
I have not used UICollectionView for infinite scrolling, but when doing it with a UIScrollView you first adjust your content offset (instead of using scrollRectToVisible) to the location you want. Then, you loop through each subview in your scroller and adjust their coordinates either to the right or left based on the direction the user was scrolling. Finally, if either end is beyond the bounds you want them to be, move them to the far other end. Their is a very good WWDC video from apple about how to do infinite scrolling you can find here: http://developer.apple.com/videos/wwdc/2012/
I have a gesture recignizer, and I need to rotate a body:
- (void) rotate:(UIGestureRecognizer*)recognizer node:(CCNode*)node
{
b2Body *body = (b2Body*)[node.parent userData];
UIRotationGestureRecognizer* rotate = (UIRotationGestureRecognizer*)recognizer;
b2Vec2 pos = body->GetPosition();
body->SetTransform(pos, (- rotate.rotation));
}
offcorse, when I starting rotation, it starts from zero angle. *But how to continue rotation from current angle? * I cant just add rotate.rotation to tu current angle: this method called on every move, and angle is calculated from very begining of gesture. keep track on actual current angle (without anctive gesture's angle), will be a pretty hard task, I think
I found solution:
I checked state of gesture (there is a begining state):
- (void) rotate:(UIGestureRecognizer*)recognizer node:(CCNode*)node
{
b2Body *body = (b2Body*)[node.parent userData];
UIRotationGestureRecognizer* rotate = (UIRotationGestureRecognizer*)recognizer;
if (rotate.state == UIGestureRecognizerStateBegan)
{
baseAngle = body->GetAngle();
}
b2Vec2 pos = body->GetPosition();
body->SetTransform(pos, (baseAngle - rotate.rotation));
}
I am developing a cocos2d game. In that game I hace a character, and according to the touch positions on the caracter different actions should be triggered,How can i Implement this in my cocos2d game.
Is there any method to implement transparent button in cocos2d.
Thanks in advance
When you create a CCMenuItemSprite (a button), you assign it a sprite to use for display.
You can then change the appearance of the button by changing the opacity property of the sprite, or by making it not visible at all.
CCSprite *buttonSpr = [CCSprite spriteWithSpriteFrameName:#"spr.png"];
CCMenuItem *button = [CCMenuItemSprite itemFromNormalSprite:buttonSpr selectedSprite:buttonSpr target:self selector:#selector(buttonTapped:)];
//opacity
buttonSpr.opacity = 50;
//invisible
buttonSpr.visible = false;
I'm not entirely sure that I understand the question and more information would be helpful, but I'll answer it the best I can.
Assuming you have a character class I would implement checkTouchesBegan and do something like this:
-(BOOL) checkTouchesBegan: (CGPoint*) location
{
//conver the touch coordinates to fit your system
int converty = location->y-160;
int convertx = location->x-240;
//determine where the touch is in relation to the center of the character
float ydif = (1.0)*(converty - character_y);
float xdif = (1.0)*(convertx - character_x);
//determine the angle of the touch
float degrees = atan2f(xdif, ydif) * 57;
//determine the distance between the character and the touch
float squared = xdif*xdif + ydif*ydif;
//if the touch is above the character and within a certain distance
if(degrees >= 45 && degrees < 135 && sqrt(squared) < 100)
{
doSomething;
return YES;
}
//if the touch is below the character and within a certain distance
else if(degrees < -45 && degrees >= -135 && sqrt(squared) < 100)
{
doSomething;
return YES;
}
//if the touch is to the right of the character and within a certain distance
else if(degrees >= -45 && degrees < 45 && sqrt(squared) < 100)
{
doSomething;
return YES;
}
//if the touch is to the left of the character and within a certain distance
else if(sqrt(squared) < 100)
{
doSomething;
return YES;
}
return NO;
}
Hope this helps some!
Edit: See the answer below.
I finally give up and come here to ask you for my problem...
I'm using a UIScrollView for a scrolling menus with little icons.
On each page, with paging enabled, there's an icon in the center, and 2 and a half other visible icons on the left and right. I can move from one icon to its neighbour, and that is fine, but the point is that if I do a fast scrolling, it will not move from more than 3 icons, which is the width of the screen.
What I would want is to be able to scroll on more than 3 icons, and that the magnet behaviour is only triggered when it's slowing down.
I've tried to schedule the scroll view to calculate its velocity, and set the pagingEnabled attribute to NO when it's moving fast and YES again when it's slowing down, but as soon as it is set to YES, the view comes back very fast at its original position, as if it was not detecting that I had brought it to a new page. Would anyone know why it does this? And if I have a way to tell the view "ok, now the paging is enabled but look, you're 15 pages later. Just center on the current page, don't come back at the beginning."
Here's my update function (if it can help):
-(void)update:(ccTime)dt
{
float velocity = fabsf((self.previousOffset-self.scrollView.contentOffset.y)/dt);
self.previousOffset = self.scrollView.contentOffset.y;
CCLOG(#"Velocity: %f", velocity);
if(self.scrollView.pagingEnabled)
{
if(velocity > 100)
{
self.scrollView.pagingEnabled = NO;
}
}
else
{
if(velocity < 100)
{
self.scrollView.pagingEnabled = YES;
}
}
}
I finally found a solution, which was pretty obvious, but that I did not see at the beginning, by using setContentOffset on the scrollView.
Here is the new update function:
-(void)update:(ccTime)dt
{
float velocity = 1000;
if(self.previousOffset)
{
velocity = fabsf((self.previousOffset-self.scrollView.contentOffset.y)/dt);
}
self.previousOffset = self.scrollView.contentOffset.y;
if(velocity < 300)
{
CGSize screenSize = [[CCDirector sharedDirector] winSize];
float halfScreen = screenSize.width/2;
CCLayer *panel = (CCLayer *)[self getChildByTag:1];
SQScrollViewMenu *menu = (SQScrollViewMenu *)[panel getChildByTag:1];
SQMissionItem *currentItem = (SQMissionItem *)[menu getChildByTag:currentPage];
float contentOffsetY = [self.scrollView contentOffset].y;
CCLOG(#"Currentpage: %i ; currentoffsetY: %f", currentPage, contentOffsetY);
float distance = contentOffsetY + [currentItem position].x - halfScreen + panel.position.x + menu.position.x + panel.parent.position.x - 60;
CCLOG(#"Which gives a distance of: %f", distance);
[self.scrollView setContentOffset:CGPointMake(0, distance) animated:YES];
self.previousOffset = 0;
[self unschedule:#selector(update:)];
CCLOG(#"Is unscheduled");
}
}
And it is almost working... at least, it is on simulator. But as soon as I try it on my iPhone 4, it does not work anymore. It always go into that update function, but 7 times among 8, it just blocks the scrollView as it is and does not drags it back to the position I give it... but sometimes it does.
Would anyone have an idea? I found similar issues on the net, but none of them could resolve this...
I have a pretty simple bit of code for dragging and drawing.
Three views: The root view is basically the window (with a few interface controls).
Within it, I have a "canvas" view. Within the canvas view, I have a third view (a small graphic of a character), which the user is meant to "drag" around within the canvas. The dragging works fine.
The problem is that I can't seem to constrain my "character" within the bounds of the canvas. Ideally, when he runs into the edge of the canvas, he should stop (not spill over into the root view)
The canvas view clips subviews; so he does disappear once he goes over the edge - but I don't want him to disappear, I want him to not be able to go any further.
I also call CGRectIsWithinRect before my animation, which in principle should do the trick. The problem is that the frame of the character isn't updated while the animation is occurring - so as long as he's already moving, he'll keep on moving right of the side of the page.
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UIView * charView = [[canvasView floatingView] retain];
CGPoint pt = [[touches anyObject] locationInView:canvasView];
if (CGRectContainsPoint([canvasView bounds], pt)) {
CGRect charFrame = charView.frame;
CGRect containerFrame = canvasView.frame;
BOOL inTheYard= CGRectContainsRect(containerFrame, charFrame);
if (inTheYard == NO) {
//if we're at the edge, don't go any further
return;
}
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:.2];
CGFloat x = floatingView.center.x + (pt.x - startPoint.x);
CGFloat y = floatingView.center.y + (pt.y - startPoint.y);
CGPoint middle = CGPointMake(x,y);
floatingView.center = middle;
[UIView commitAnimations];
[floatingView release];
}
}
How can I keep him from moving past the edges of his superview?
Instead of checking if the touch event is inside the canvas you can clamp it to be inside. Check the x and y points for the touch event separately and if they are outside the canvas move the character up to the wall on that side of the canvas.
CGFloat w = canvasFrame.frame.size.width;
CGFloat h = canvasFrame.frame.size.height;
if (pt.x < 0) pt.x = 0;
if (pt.x > w) pt.x = w;
if (pt.y < 0) pt.y = 0;
if (pt.y > h) pt.y = h;
This will allow you to keep dragging the character along the wall of the canvas even when the touch event is outside (if you remove the inTheYard check).