Cocos2d: Testing for collisions, says that the two sprites always intersect - objective-c

Hello I am making a cocos2d side scroller. I am trying to test for collisions between two sprites. I checked and the rects I am making for the sprites are what they are supposed to be, but it says that the two rects intersect all of the time whether or not they actually do. Here is the code:
-(void)checkForRedEnemyCollisions{
CGRect playerRect = CGRectMake(player.position.x - (player.playerSprite.contentSize.width/2),
player.position.y - (player.playerSprite.contentSize.height/2),
player.playerSprite.contentSize.width,
player.playerSprite.contentSize.height);
CGRect redEnemyRect = CGRectMake(redEnemy.position.x - (redEnemy.bulletSprite.contentSize.width / 2) ,
redEnemy.position.y - (redEnemy.bulletSprite.contentSize.height /2 ),
redEnemy.bulletSprite.contentSize.width,
redEnemy.bulletSprite.contentSize.height);
if (CGRectIntersectsRect(playerRect, redEnemyRect)) {
CCLOG(#"collision");
}
}
Here is more code:
-(id)init{
if((self = [super init])){
CGSize size = [[CCDirector sharedDirector]winSize];
screenWidth = size.width;
screenHeight = size.height;
gravity = 2;
playerSprite = [CCSprite spriteWithFile:#"thefinalcharacter.png"];
playerSprite.scale = 1.5;
playerSprite.position = ccp(screenWidth/3.4, screenHeight/2);
[self addChild:playerSprite z:-3];
[self schedule: #selector(flight:)interval:1.0f/7.0f];
}
return self;
}
-(void)flight:(ccTime)delta{
flightCounter ++;
if (flightCounter % 2){
[playerSprite setTexture:[[CCSprite spriteWithFile:#"thefinalcharacter.png"]texture]];
}else{
[playerSprite setTexture:[[CCSprite spriteWithFile:#"thefinalcharacter2.png"]texture]];
}
[self schedule:#selector(updatePosition:)interval:1.0f/30.0f];
}
-(void)updatePosition:(ccTime)delta{
if(playerSprite.position.y < 35){
gravity = 0;
}else if(playerSprite.position.y > screenHeight - 150) {
playerSprite.position = ccp(playerSprite.position.x, playerSprite.position.y - 100);
}else{
gravity = 2;
}
playerSprite.position = ccp(playerSprite.position.x, playerSprite.position.y - gravity);
}

make sure that you are creating the bounding rectangles in world coordinates.
try to convert the points to world coordinates like the following;
CGPoint playerWorldPosition = [player ConvertToWorldSpace:player.position];
CGRect playerRect = CGRectMake(playerWorldPosition.x - (player.playerSprite.contentSize.width/2),
playerWorldPosition.y - (player.playerSprite.contentSize.height/2),
player.playerSprite.contentSize.width,
player.playerSprite.contentSize.height);
do the same for redEnemy. this will make sure you are comparing them in world space but not in another space down the hierarchy of layers.
if you using anchor point instead of the default (0.0,0.0) on your player or redEnemies. use the ConvertToWorldSpaceAR function instead of the ConvertToWorldSpace.

Related

Keeping the contents of a scaled NSScrollView centered and visible when resizing the window

I am trying to magnify an NSScrollView which contains NSTextView and keep it centered to its content at all times. The NSTextView has left/right insets to keep the word wrapping consistent and to keep the paragraphs nicely at the center of the view.
Both [NSScrollView scaleUnitSquareToSize:...] and setMagnification:... have their own quirks and problems, but for now setMagnification seems a better option, as it is not relative.
Here's what happens (among other strange stuff):
On resizing, I update the insets:
CGFloat inset = self.textScrollView.frame.size.width / 2 - _documentWidth / 2;
self.textView.textContainerInset = NSMakeSize(inset, TEXT_INSET_TOP);
self.textView.textContainer.size = NSMakeSize(_documentWidth, self.textView.textContainer.size.height);
Zooming in:
CGFloat magnification = [self.textScrollView magnification];
NSPoint center = NSMakePoint(self.textScrollView.frame.size.width / 2, self.textScrollView.frame.size.height / 2);
if (zoomIn) magnification += .05; else magnification -= .05;
[self.textScrollView setMagnification:magnification centeredAtPoint:center];
Everything kind of works for a while. Sometimes, depending on from which window corner the window is resized, the ScrollView loses its center, and I haven't found a solution for re-centering the view of a magnified NSScrollView.
After magnification, layout constraints can get broken too when resizing the window, especially when the textContainer is clipped out of view, and the app crashes with the following error:
*** Assertion failure in -[NSISLinearExpression addVariable:coefficient:], /Library/Caches/com.apple.xbs/Sources/Foundation/Foundation-1349.91/Layout.subproj/IncrementalSimplex/NSISLinearExpression.m:716
One problem might be that I am setting the insets according to UIScrollView frame size, because the contained NSTextView's coordinates don't seem to be relative but absolute after magnification.
Is there any safe way to magnifying this sort of view and keeping it centered to its content at all times? And why are my constraints breaking?
I've run into similar problems, and unfortunately I ended up doing the centering myself. Here are some of the highlights of my solution.
needs recursion prevention! (otherwise stackoverflow :)
create a non-drawable NSView as the documentView, and then add your drawable view as a subview which is centered manually, and manually set the frame to the visibleRect of the parent.
override visibleRect, call it a second time if its invalid, and debug to make sure it is valid!
zooming layered backed views sux. You could try using an NSTiledLayer, but I've tried and abandoned that solution multiple times.
Code below:
#interface FlippedParentView : NSView
#end
#implementation FlippedParentView
- (BOOL) isFlipped { return YES; }
#end
- (void)awakeFromNib
{
[self resetMouseInfo];
[[self window] setAcceptsMouseMovedEvents:YES];
needsFullRedraw = YES;
[self setAcceptsTouchEvents:YES];
// problem: when zoomed-in, CALayer backed NSOpenGLView becomes too large
// and hurts performance.
// solution: create a fullsizeView for the NSScrollView to resize,
// and make NSOpenGLView a subview. Keep NSOpenGLView size the same as visibleRect,
// positioning it as needed on the fullsizeView.
NSScrollView *scrollvw = [self enclosingScrollView];
[scrollvw setBackgroundColor:[NSColor darkStrokeColor]];
fullsizeView = [[FlippedParentView alloc] initWithFrame: [self frame]];
[scrollvw setDocumentView:fullsizeView];
[fullsizeView setAutoresizesSubviews:NO];
//printf("mask %d\n", [self autoresizingMask]);
[fullsizeView setAutoresizingMask: NSViewHeightSizable | NSViewWidthSizable | NSViewMinYMargin | NSViewMaxYMargin | NSViewMaxXMargin | NSViewMinXMargin];
[self setAutoresizingMask: NSViewNotSizable];
[fullsizeView addSubview:self];
}
- (NSRect) visibleRect
{
NSRect visRect = [super visibleRect];
if ( visRect.size.width == 0 )
{
visRect = [[self superview] visibleRect];
if ( visRect.size.width == 0 )
{
// this jacks up everything
DUMP( #"bad visibleRect" );
}
visRect.origin = NSZeroPoint;
}
return visRect;
}
- (void) _my_zoom: (double)newZoom
{
mouseFocusPt = [self focusPt];
NSRect oldVisRect = [[self superview] visibleRect];
if ( newZoom < 1.0 )
newZoom = 1.0;
if ( newZoom > kZoomFactorMax ) newZoom = kZoomFactorMax;
float xpct = (mouseFocusPt.x - oldVisRect.origin.x) /
( NSMaxX(oldVisRect) - oldVisRect.origin.x );
float ypct = (mouseFocusPt.y - oldVisRect.origin.y) /
( NSMaxY(oldVisRect) - oldVisRect.origin.y );
float oldZoom = zoomFactor;
zoomFactor = newZoom;
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Stay locked on users' relative mouse location, so user can zoom in and back out without
// the view scrolling out from under the mouse location.
NSPoint newFocusPt = NSMakePoint (mouseFocusPt.x * newZoom/oldZoom,
mouseFocusPt.y * newZoom/oldZoom) ;
NSRect myFrame = fullsizeFrame; // [self frame];
float marginPercent = (myFrame.size.height - drawableSizeWithMargins.height) / drawableSizeWithMargins.height;
[self updateContext];
NSRect newVisRect;
newVisRect.size = [self visibleRect].size;
newVisRect.origin.x = (newFocusPt.x) - (xpct * newVisRect.size.width);
//DLog( #"xpct %0.2f, zoomFactor %0.2f, newVisRect.origin.x %0.2f", xpct, zoomFactor, newVisRect.origin.x);
myFrame = fullsizeFrame; // [self frame];
float marginPercent2 = (myFrame.size.height - drawableSizeWithMargins.height) / drawableSizeWithMargins.height;
float marginDiff = (marginPercent - marginPercent2) * drawableSizeWithMargins.height;
newVisRect.origin.y = (newFocusPt.y ) - (ypct * newVisRect.size.height) - marginDiff;
//DLog( #"ypct %0.2f, zoomFactor %0.2f, newVisRect.origin.y %0.2f", ypct, zoomFactor, newVisRect.origin.y);
//DLog( #"marginPercent %0.2f newVisRect %#", marginPercent, NSStringFromRect(newVisRect) );
if ( newVisRect.origin.x < 1 ) newVisRect.origin.x = 1;
if ( newVisRect.origin.y < 1 ) newVisRect.origin.y = 1;
// NSLog( #"zoom scrollRectToVisible %# bounds %#", NSStringFromRect(newVisRect), NSStringFromRect([[self superview] bounds]) );
// if ( iUseMousePt || isSlider )
[[self superview] scrollRectToVisible:newVisRect];
}
// - zoomFactor of 1.0 is defined as the zoomFactor needed to show entire selected context within visibleRect,
// including margins of 5% of the context size
// - zoomFactor > 1.0 will make pixels look bigger (view a subsection of a larger total drawableSize)
// - zoomFactor < 1.0 will make pixels look smaller (selectedContext size will be less than drawableSize)
-(void)updateContext
{
static BOOL sRecursing = NO;
if ( sRecursing ) return; // prevent recursion
sRecursing = YES;
//NSRect scrollRect = [[self superview] frame];
NSRect clipViewRect = [[[self enclosingScrollView] contentView] frame];
NSRect visRect = [[self superview] visibleRect]; // careful... visibleRect is sometimes NSZeroRect
float layoutWidth = clipViewRect.size.width;
float layoutHeight = clipViewRect.size.height;
marginPct = layoutHeight / (layoutHeight - (overlayViewMargin*2) );
// Satisfy the constraints fully-zoomed-out case:
// 1) the drawable rect is centered in the view with at margins.
// Allow for 5% margins (1.025 = 2.5% left, right, top, bottom)
// 2) guarantee the drawable rect does not overlap the mini-map in upper right corner.
NSRect baseRect = NSZeroRect;
baseRect.size = visRect.size;
NSRect drawableBaseRect = getCenteredRectFloat(baseRect, metaUnionRect.size );
//drawableSizeWithMargins = nsIntegralSize( nsScaleSize( drawableBaseRect.size, zoomFactor ) );
drawableSizeWithMargins = nsScaleSize( drawableBaseRect.size, zoomFactor );
// drawableSize will NOT include the margins. We loop until we've satisfied
// the constraints above.
drawableSize = drawableSizeWithMargins;
do
{
NSSize shrunkSize;
shrunkSize.width = layoutWidth / marginPct;
shrunkSize.height = layoutHeight / marginPct;
//drawableSize = nsIntegralSize( nsScaleSize( drawableBaseRect.size, zoomFactor / marginPct ));
drawableSize = nsScaleSize( drawableBaseRect.size, zoomFactor / marginPct );
[self calculateMiniMapRect]; // get approx. size. Will calculate once more below.
NSRect shrunkRect = getCenteredRectNoScaling(baseRect, shrunkSize );
// DLog( #"rough miniMapRect %# shrunk %#", NSStringFromRect(miniMapRect), NSStringFromRect(shrunkRect));
// make sure minimap doesn't overlap drawable when you scroll to top-left
NSRect topMiniMapRect = miniMapRect;
topMiniMapRect.origin.x -= visRect.origin.x;
topMiniMapRect.origin.y = 0;
if ( !NSIntersectsRect( topMiniMapRect, shrunkRect ) )
{
topMarginPercent = fabs(shrunkRect.origin.y - drawableBaseRect.origin.y) / baseRect.size.height;
break;
}
float topMarginOffset = shrunkRect.size.height + (baseRect.size.height * 0.025);
shrunkRect.origin.y = NSMaxY(baseRect) - topMarginOffset;
if ( !NSIntersectsRect( topMiniMapRect, shrunkRect ) )
{
topMarginPercent = fabs(shrunkRect.origin.y - drawableBaseRect.origin.y) / baseRect.size.height;
break;
}
marginPct *= 1.025;
} while (1);
fullsizeFrame.origin = NSZeroPoint;
fullsizeFrame.size.width = fmax(drawableSizeWithMargins.width, layoutWidth);
fullsizeFrame.size.height = fmax(drawableSizeWithMargins.height, layoutHeight);
[fullsizeView setFrame:fullsizeFrame];
NSRect myNewFrame = [fullsizeView visibleRect];
if (myNewFrame.size.width > 0)
[self setFrame: myNewFrame]; //NSView
sRecursing = NO;
}

UILabel Dynamic Content auto size trouble

Please, I need some help with some UILabels that must hold dynamic content. They are contained in a UIScrollView.
I can't get them to resize properly depending on screen orientation. I also want to keep an exact distance between them.
Right now, I'm having trouble with 2 of them (many to come).
I'm using the following code to resize them but it doesn't get me the expected results:
- (void) updateLabelsPositionForPortrait
{
summary_PersonalMonth.numberOfLines = 0;
summary_PersonalDay.numberOfLines = 0;
summary_PersonalMonth.frame = CGRectMake(summary_PersonalMonth.frame.origin.x,
summary_PersonalMonth.frame.origin.y,
summary_PersonalMonth.frame.size.width + 15,
summary_PersonalMonth.frame.size.height);
[summary_PersonalMonth sizeToFit];
summary_PersonalDay.frame = CGRectMake(summary_PersonalDay.frame.origin.x,
summary_PersonalDay.frame.origin.y + summary_PersonalMonth.bounds.origin.y + 10,
summary_PersonalDay.frame.size.width + 15,
summary_PersonalDay.frame.size.height);
[summary_PersonalDay sizeToFit];
[self updateScrollViewContentSize];
}
- (void) updateLabelsPositionForLandscape
{
summary_PersonalMonth.numberOfLines = 1;
summary_PersonalDay.numberOfLines = 1;
summary_PersonalMonth.frame = CGRectMake(summary_PersonalMonth.frame.origin.x,
summary_PersonalMonth.frame.origin.y,
summary_PersonalMonth.frame.size.width,
summary_PersonalMonth.frame.size.height);
[summary_PersonalMonth sizeToFit];
summary_PersonalDay.frame = CGRectMake(summary_PersonalDay.frame.origin.x,
summary_PersonalDay.frame.origin.y - summary_PersonalMonth.bounds.origin.y - 10,
summary_PersonalDay.frame.size.width,
summary_PersonalDay.frame.size.height);
[summary_PersonalDay sizeToFit];
}
and I call each method in shouldAutorotateToInterfaceOrientation: in the corresponding orientation.
if (UIInterfaceOrientationIsPortrait([UIDevice currentDevice].orientation))
{
[self updateLabelsPositionForPortrait];
}
else
{
[self updateLabelsPositionForLandscape];
}
The results are these:
Initial result on view load
Result after rotation in Landscape
Result after rotating back to portrait
Please, I need advice !
Thank you !
Store all labels starting form first to last in position in NSArray.
Now making one function which set frames of labels according to orientation:
-(void)setFramesOfLabel:(BOOL)flag
{
if(flag) //portrait
{
int height = 20;
int spaceBetweenLabels = 20;
itn xspace = 20 //
}
else//landscape changes
{
int height = 20;
int spaceBetweenLabels = 20;
itn xspace = 20 /
}
int count = 0;
for(UILabel *label in arrLabels)
{
NSString *strText = [arrTexts objectAtIndex:count]; //as u may having text array to fill in labels as i assume
CGSize expectedLabelSize = [strText sizeWithFont:label.font constrainedToSize:maximumLabelSize lineBreakMode:UILineBreakModeWordWrap]; //
CGrect *newFrame = CGRectMake(xspace,height,expectedLabelSize.width,expectedLabelSize.height);
[label setFrame:newFrame];
height += expectedLabelSize.height;
count ++;
}
[scrollView setContentSize(scrollView.width,height)];
}
Use
if (UIInterfaceOrientationIsPortrait([UIDevice currentDevice].orientation))
{
[self setFramesOfLabel:TRUE];
}
else
{
[self setFramesOfLabel:FALSE];
}
You seem to be re-operating on the same frames over and over again. Try using this code:
// Declare the below variables as globals
CGRect defaultRectPortrait = CGRectZero;
CGRect defaultRectLandscape = CGRectZero;
- (void) updateLabelsPositionForPortrait
{
summary_PersonalMonth.numberOfLines = 0;
summary_PersonalDay.numberOfLines = 0;
if(defaultRectPortait == CGRectZero)
defaultRectPortait = CGRectMake(summary_PersonalMonth.frame.origin.x,
summary_PersonalMonth.frame.origin.y,
summary_PersonalMonth.frame.size.width + 15,
summary_PersonalMonth.frame.size.height);
[summary_PersonalMonth setFrame:defaultRectPortrait];
[summary_PersonalMonth sizeToFit];
// Do a similar thing for summary_PersonalDay
[self updateScrollViewContentSize];
}
- (void) updateLabelsPositionForLandscape
{
// Use similar logic for summary_PersonalMonth and summary_PersonalDay in landscape as well
}
And finally:
if (UIInterfaceOrientationIsPortrait([UIDevice currentDevice].orientation))
{
[self updateLabelsPositionForPortrait];
}
else
{
[self updateLabelsPositionForLandscape];
}

UIView hitTest:withEvent: and pointInside:withEvent

I'm working with Mixare AR SDK for iOS and I need to solve some bugs that happends, one of them is show the information of a POI when the POI's view is tapped.
Prelude:
Mixare has an overlay UIView within MarkerView views are placed, MarkerView views are moving around the screen to geolocate the POIs and each one has two subviews, an UIImageView and an UILabel.
Issue:
Now, for example, there are 3 visible POIs in the screen, so there are 3 MarkerView as overlay subviews. If you touch anywhere in the overlay, a info view associated to a random POI of which are visible is showed.
Desired:
I want that the associated POI's info is shown only when the user tapped a MarkerView
Let's work. I've see that MarkerView inherits from UIView and implements hitTest:withEvent
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
viewTouched = (MarkerView*)[super hitTest:point withEvent:event];
return self;
}
I've put a breakpoint and hitTest is called once for each visible MarkerView but loadedView always is null so I can't work with it, so I've tried to check if the hit point is inside the MarkerView frame implementing pointInside:withEvent: by this way
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
NSLog(#"ClassName: %#", [[self class] description]);
NSLog(#"Point Inside: %f, %f", point.x, point.y);
NSLog(#"Frame x: %f y: %f widht:%f height:%f", self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height);
if (CGRectContainsPoint(self.frame, point))
return YES;
else
return NO;
return YES;
}
But this function always returns NO, even when I touch the MarkerView. When I check the log I saw that X and Y point values has negative values sometimes and width and height of the view are very small, 0.00022 or similar instead of 100 x 150 that I set the MarkerView frame on its initialization.
Here you are a extract of my log in which you can see the class name, the point and the MarkerView frame values.
ClassName: MarkerView
2011-12-29 13:20:32.679 paisromanico[2996:707] Point Inside: 105.224899, 49.049023
2011-12-29 13:20:32.683 paisromanico[2996:707] Frame x: 187.568573 y: 245.735138 widht:0.021862 height:0.016427
I'm very lost with this issue so any help will be welcome. Thanks in advance for any help provided and I'm sorry about this brick :(
Edit:
At last I've found that the problem is not in hitTest:withEvent: or pointInside:withEvent, problem is with CGTransform that applies to the MarkerView for scaling based on distande and rotating the view, if I comment any code related to this, the Mixare AR SDK works fine, I mean, info view is shown correctly if you touch a marker and doesn't do anything if any other place in the screen is touched.
So, by the moment, I've not solved the problem but I applied a patch removing the CGTransform related code in AugmentedViewController.m class - (void)updateLocations:(NSTimer *)timer function
- (void)updateLocations:(NSTimer *)timer {
//update locations!
if (!ar_coordinateViews || ar_coordinateViews.count == 0) {
return;
}
int index = 0;
NSMutableArray * radarPointValues= [[NSMutableArray alloc]initWithCapacity:[ar_coordinates count]];
for (PoiItem *item in ar_coordinates) {
MarkerView *viewToDraw = [ar_coordinateViews objectAtIndex:index];
viewToDraw.tag = index;
if ([self viewportContainsCoordinate:item]) {
CGPoint loc = [self pointInView:ar_overlayView forCoordinate:item];
CGFloat scaleFactor = 1.5;
if (self.scaleViewsBasedOnDistance) {
scaleFactor = 1.0 - self.minimumScaleFactor * (item.radialDistance / self.maximumScaleDistance);
}
float width = viewToDraw.bounds.size.width ;//* scaleFactor;
float height = viewToDraw.bounds.size.height; // * scaleFactor;
viewToDraw.frame = CGRectMake(loc.x - width / 2.0, loc.y-height / 2.0, width, height);
/*
CATransform3D transform = CATransform3DIdentity;
//set the scale if it needs it.
if (self.scaleViewsBasedOnDistance) {
//scale the perspective transform if we have one.
transform = CATransform3DScale(transform, scaleFactor, scaleFactor, scaleFactor);
}
if (self.rotateViewsBasedOnPerspective) {
transform.m34 = 1.0 / 300.0;
double itemAzimuth = item.azimuth;
double centerAzimuth = self.centerCoordinate.azimuth;
if (itemAzimuth - centerAzimuth > M_PI) centerAzimuth += 2*M_PI;
if (itemAzimuth - centerAzimuth < -M_PI) itemAzimuth += 2*M_PI;
double angleDifference = itemAzimuth - centerAzimuth;
transform = CATransform3DRotate(transform, self.maximumRotationAngle * angleDifference / (VIEWPORT_HEIGHT_RADIANS / 2.0) , 0, 1, 0);
}
viewToDraw.layer.transform = transform;
*/
//if we don't have a superview, set it up.
if (!(viewToDraw.superview)) {
[ar_overlayView addSubview:viewToDraw];
[ar_overlayView sendSubviewToBack:viewToDraw];
}
} else {
[viewToDraw removeFromSuperview];
viewToDraw.transform = CGAffineTransformIdentity;
}
[radarPointValues addObject:item];
index++;
}
float radius = [[[NSUserDefaults standardUserDefaults] objectForKey:#"radius"] floatValue];
if(radius <= 0 || radius > 100){
radius = 5.0;
}
radarView.pois = radarPointValues;
radarView.radius = radius;
[radarView setNeedsDisplay];
[radarPointValues release];
}
Any CoreGrapics or UI expert could give us his point of view about this issue??
You should either try to hittest as attached:
if ([self pointInside:point withEvent:event]) {
// do something
}
I would suggest you add the hit test on the superview, and do the following in the hit test of the parent of the markerViews
if ([markerView pointInside:point withEvent:event]) {
// extract the tag and show the relevant info
}
Hope this helps

How can i make an image to rotate based on the direction of my finger swipe

I'm trying to spin an circle image based on user swipe.Now I've done by considering the as two parts. one is the left side and other is the right side.
If the user swipes down from right half means it rotates clockwise and swipe up means anti clock wise. In left side I've done the vice versa. So now my image will rotate fine only when I touch the left and right half.. on touching top and bottom .. its behaving differently.
I've even tried ny calculating the radians.. its also not working
Can any one suggest me to identify clockwise or anticlock wise in a better way...
Thank u,
Lakshmi jones
You should approach this problem with trignometry. Assuming you know the starting point of swipe (a1,b1) and the ending point of swipe (a2,b2)
The circles centre is at (x,y)
If we know the difference of angles made by lines (x,y)->(a1,b1) and (x,y)->(a2,b2) we will know whether to rotate clockwise or anticlockwise based on whether the above said angle is positive or negative.
Angle made by a line is calculated below.
Let the red angle be red
if(a1-x==0){
if(b1-y>0) red=pi/2
else red = 3*pi/2
}
else{
tan(red) = abs((b1-y)/(a1-x))
red = tan-inverse( abs((b1-y)/(a1-x)) )
if(a1-x<0){
if(b1-y<=0)
red+=pi;
else
red+=pi/2
}
else if(a1-x>0 && b1-y<0){
red+=3*pi/2
}
}
See here to know how to calculate tan-inverse.
Similarly calculate the value of angle green. After doing that just comparing the value of green and red will let you know what to do.
if(red - green == pi || red - green == 0){
do_nothing();
}else if(red - green > 0){
rotate_clockwise();
}else{
rotate_anticlockwise();
}
By using the acceleration/velocity data of the swipe you could rotate the circle with the same acceleration/velocity.
Have you ever tried this tutorial for your problem. This will help you for sure.
The .h file for calculating the swipe
#import <UIKit/UIKit.h>
#import "SMRotaryProtocol.h"
#interface SMRotaryWheel : UIControl
#property (weak) id <SMRotaryProtocol> delegate;
#property (nonatomic, strong) UIView *container;
#property int numberOfSections;
#property CGAffineTransform startTransform;
#property (nonatomic, strong) NSMutableArray *cloves;
#property int currentValue;
- (id) initWithFrame:(CGRect)frame andDelegate:(id)del withSections:(int)sectionsNumber;
And the .m file is
#import "SMRotaryWheel.h"
#import <QuartzCore/QuartzCore.h>
#import "SMCLove.h"
#interface SMRotaryWheel()
- (void)drawWheel;
- (float) calculateDistanceFromCenter:(CGPoint)point;
- (void) buildClovesEven;
- (void) buildClovesOdd;
- (UIImageView *) getCloveByValue:(int)value;
- (NSString *) getCloveName:(int)position;
#end
static float deltaAngle;
static float minAlphavalue = 0.6;
static float maxAlphavalue = 1.0;
#implementation SMRotaryWheel
#synthesize delegate, container, numberOfSections, startTransform, cloves, currentValue;
- (id) initWithFrame:(CGRect)frame andDelegate:(id)del withSections:(int)sectionsNumber {
if ((self = [super initWithFrame:frame])) {
self.currentValue = 0;
self.numberOfSections = sectionsNumber;
self.delegate = del;
[self drawWheel];
}
return self;
}
- (void) drawWheel {
container = [[UIView alloc] initWithFrame:self.frame];
CGFloat angleSize = 2*M_PI/numberOfSections;
for (int i = 0; i < numberOfSections; i++) {
UIImageView *im = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"segment.png"]];
im.layer.anchorPoint = CGPointMake(1.0f, 0.5f);
im.layer.position = CGPointMake(container.bounds.size.width/2.0-container.frame.origin.x,
container.bounds.size.height/2.0-container.frame.origin.y);
im.transform = CGAffineTransformMakeRotation(angleSize*i);
im.alpha = minAlphavalue;
im.tag = i;
if (i == 0) {
im.alpha = maxAlphavalue;
}
UIImageView *cloveImage = [[UIImageView alloc] initWithFrame:CGRectMake(12, 15, 40, 40)];
cloveImage.image = [UIImage imageNamed:[NSString stringWithFormat:#"icon%i.png", i]];
[im addSubview:cloveImage];
[container addSubview:im];
}
container.userInteractionEnabled = NO;
[self addSubview:container];
cloves = [NSMutableArray arrayWithCapacity:numberOfSections];
UIImageView *bg = [[UIImageView alloc] initWithFrame:self.frame];
bg.image = [UIImage imageNamed:#"bg.png"];
[self addSubview:bg];
UIImageView *mask = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 58, 58)];
mask.image =[UIImage imageNamed:#"centerButton.png"] ;
mask.center = self.center;
mask.center = CGPointMake(mask.center.x, mask.center.y+3);
[self addSubview:mask];
if (numberOfSections % 2 == 0) {
[self buildClovesEven];
} else {
[self buildClovesOdd];
}
[self.delegate wheelDidChangeValue:[self getCloveName:currentValue]];
}
- (UIImageView *) getCloveByValue:(int)value {
UIImageView *res;
NSArray *views = [container subviews];
for (UIImageView *im in views) {
if (im.tag == value)
res = im;
}
return res;
}
- (void) buildClovesEven {
CGFloat fanWidth = M_PI*2/numberOfSections;
CGFloat mid = 0;
for (int i = 0; i < numberOfSections; i++) {
SMClove *clove = [[SMClove alloc] init];
clove.midValue = mid;
clove.minValue = mid - (fanWidth/2);
clove.maxValue = mid + (fanWidth/2);
clove.value = i;
if (clove.maxValue-fanWidth < - M_PI) {
mid = M_PI;
clove.midValue = mid;
clove.minValue = fabsf(clove.maxValue);
}
mid -= fanWidth;
NSLog(#"cl is %#", clove);
[cloves addObject:clove];
}
}
- (void) buildClovesOdd {
CGFloat fanWidth = M_PI*2/numberOfSections;
CGFloat mid = 0;
for (int i = 0; i < numberOfSections; i++) {
SMClove *clove = [[SMClove alloc] init];
clove.midValue = mid;
clove.minValue = mid - (fanWidth/2);
clove.maxValue = mid + (fanWidth/2);
clove.value = i;
mid -= fanWidth;
if (clove.minValue < - M_PI) {
mid = -mid;
mid -= fanWidth;
}
[cloves addObject:clove];
NSLog(#"cl is %#", clove);
}
}
- (float) calculateDistanceFromCenter:(CGPoint)point {
CGPoint center = CGPointMake(self.bounds.size.width/2.0f, self.bounds.size.height/2.0f);
float dx = point.x - center.x;
float dy = point.y - center.y;
return sqrt(dx*dx + dy*dy);
}
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint touchPoint = [touch locationInView:self];
float dist = [self calculateDistanceFromCenter:touchPoint];
if (dist < 40 || dist > 100)
{
// forcing a tap to be on the ferrule
NSLog(#"ignoring tap (%f,%f)", touchPoint.x, touchPoint.y);
return NO;
}
float dx = touchPoint.x - container.center.x;
float dy = touchPoint.y - container.center.y;
deltaAngle = atan2(dy,dx);
startTransform = container.transform;
UIImageView *im = [self getCloveByValue:currentValue];
im.alpha = minAlphavalue;
return YES;
}
- (BOOL)continueTrackingWithTouch:(UITouch*)touch withEvent:(UIEvent*)event
{
CGPoint pt = [touch locationInView:self];
float dist = [self calculateDistanceFromCenter:pt];
if (dist < 40 || dist > 100)
{
// a drag path too close to the center
NSLog(#"drag path too close to the center (%f,%f)", pt.x, pt.y);
// here you might want to implement your solution when the drag
// is too close to the center
// You might go back to the clove previously selected
// or you might calculate the clove corresponding to
// the "exit point" of the drag.
}
float dx = pt.x - container.center.x;
float dy = pt.y - container.center.y;
float ang = atan2(dy,dx);
float angleDifference = deltaAngle - ang;
container.transform = CGAffineTransformRotate(startTransform, -angleDifference);
return YES;
}
- (void)endTrackingWithTouch:(UITouch*)touch withEvent:(UIEvent*)event
{
CGFloat radians = atan2f(container.transform.b, container.transform.a);
CGFloat newVal = 0.0;
for (SMClove *c in cloves) {
if (c.minValue > 0 && c.maxValue < 0) { // anomalous case
if (c.maxValue > radians || c.minValue < radians) {
if (radians > 0) { // we are in the positive quadrant
newVal = radians - M_PI;
} else { // we are in the negative one
newVal = M_PI + radians;
}
currentValue = c.value;
}
}
else if (radians > c.minValue && radians < c.maxValue) {
newVal = radians - c.midValue;
currentValue = c.value;
}
}
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.2];
CGAffineTransform t = CGAffineTransformRotate(container.transform, -newVal);
container.transform = t;
[UIView commitAnimations];
[self.delegate wheelDidChangeValue:[self getCloveName:currentValue]];
UIImageView *im = [self getCloveByValue:currentValue];
im.alpha = maxAlphavalue;
}
- (NSString *) getCloveName:(int)position {
NSString *res = #"";
switch (position) {
case 0:
res = #"Circles";
break;
case 1:
res = #"Flower";
break;
case 2:
res = #"Monster";
break;
case 3:
res = #"Person";
break;
case 4:
res = #"Smile";
break;
case 5:
res = #"Sun";
break;
case 6:
res = #"Swirl";
break;
case 7:
res = #"3 circles";
break;
case 8:
res = #"Triangle";
break;
default:
break;
}
return res;
}
#end
Main Methods which will help you to track the swipe are
- (float) calculateDistanceFromCenter:(CGPoint)point
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
- (BOOL)continueTrackingWithTouch:(UITouch*)touch withEvent:(UIEvent*)event
- (void)endTrackingWithTouch:(UITouch*)touch withEvent:(UIEvent*)event
May this will help you :)
Though trigonometry is one approach to the math, it's simpler and requires much less processor power to do this with vectors.
A picture for reference:
The center of the dial you want to spin is point C.
From the user interface you must get a swipe start point A and "swipe vector" s that shows how the user's finger is moving. If the OS provides only a second point B some time after A, then compute s = B - A.
You want to compute the component of s that's tangent to the circle centered at C passing through A. This will allow the user to start his/her swipe anywhere and have it treated as a torque about point C. This ought to be intuitive.
This is not hard. The radius of the circle is shown as vector r = A - C. The perpendicular to this vector is called "r perp" shown with the "thumbtack" symbol in the picture. It is just the point (-y, x) where x and y are the components of r.
The signed length of the projection of p onto perp(r) is just a normalized dot product:
This is a scalar that is positive if rotation is counter clockwise around C and negative if clockwise. So the sign tells you the direction of rotation. The absolute value tells you how much or how fast to rotate.
Suppose we already have swipe vector s stored as sx and sy and center C as cx and cy. Then the pseudo code is just:
r_perp_x = cy - ay; r_perp_y = ax - cx;
signed_length_p = (sx * r_perp_x + sy * r_perp_y) / sqrt(r_perp_x ^ 2 + r_perp_y ^ 2)
The desired number is signed_length_p.
The only caveat is to ignore touches A that are close to C. They can produce very large output values or division by zero. This is easy to fix. Just check the length of r and quit if it's less than some reasonable value.
If your current solution is "almost fine" for you - simplest thing to do would be ...just fixing it with two more areas:
Now you spin your image clockwise whenever user swiped
- right or up (started in area A)
- right or down (started in area B)
- left or down (started in area D)
- left or up (started in area C)
....else - spin it anticlockwise.
I have done something similar with Xamarin.iOS but I doubt you want to see C# code so maybe this GitHub project will give you the necessary info:
https://github.com/hollance/MHRotaryKnob

NSTextFieldCell vertical alignment, solutions seem to squash the horizontal alignment

I have a NSTextFieldCell that I wish to display with middle vertical alignment. Thanks to an older question here and a blog entry I have two working solutions.
However, both solutions seem to squash my ability to set the cell as right aligned. Can anyone help me make either of these solutions support both forms of alignment?
Here is the code for one solution:
#implementation MiddleAlignedTextFieldCell
- (NSRect)titleRectForBounds:(NSRect)theRect {
NSRect titleFrame = [super titleRectForBounds:theRect];
NSSize titleSize = [[self attributedStringValue] size];
titleFrame.origin.y = theRect.origin.y - .5 + (theRect.size.height - titleSize.height) / 2.0;
return titleFrame;
}
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSRect titleRect = [self titleRectForBounds:cellFrame];
[[self attributedStringValue] drawInRect:titleRect];
}
#end
The alternative solution is (obtained from this blog):
#implementation RSVerticallyCenteredTextFieldCell
- (NSRect)drawingRectForBounds:(NSRect)theRect
{
NSRect newRect = [super drawingRectForBounds:theRect];
if (mIsEditingOrSelecting == NO)
{
// Get our ideal size for current text
NSSize textSize = [self cellSizeForBounds:theRect];
// Center that in the proposed rect
float heightDelta = newRect.size.height - textSize.height;
if (heightDelta > 0)
{
newRect.size.height -= heightDelta;
newRect.origin.y += (heightDelta / 2);
}
}
return newRect;
}
- (void)selectWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject start:(int)selStart length:(int)selLength
{
aRect = [self drawingRectForBounds:aRect];
mIsEditingOrSelecting = YES;
[super selectWithFrame:aRect inView:controlView editor:textObj delegate:anObject start:selStart length:selLength];
mIsEditingOrSelecting = NO;
}
- (void)editWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject event:(NSEvent *)theEvent
{
aRect = [self drawingRectForBounds:aRect];
mIsEditingOrSelecting = YES;
[super editWithFrame:aRect inView:controlView editor:textObj delegate:anObject event:theEvent];
mIsEditingOrSelecting = NO;
}
#end
I'm posting this answer to the question since it does work, however, I find the fact that I couldn't find another way to check the alignment setting from IB is very annoying. Accessing _cFlags just seems a little dirty, and I'd love to find a cleaner method.
Based on the code posted earlier from this blog entry.
- (NSRect)drawingRectForBounds:(NSRect)theRect
{
// Get the parent's idea of where we should draw
NSRect newRect = [super drawingRectForBounds:theRect];
if (mIsEditingOrSelecting == NO)
{
// Get our ideal size for current text
NSSize textSize = [self cellSizeForBounds:theRect];
// Center that in the proposed rect
float heightDelta = newRect.size.height - textSize.height;
if (heightDelta > 0)
{
newRect.size.height -= heightDelta;
newRect.origin.y += (heightDelta / 2);
}
// For some reason right aligned text doesn't work. This section makes it work if set in IB.
// HACK: using _cFlags isn't a great idea, but I couldn't find another way to find the alignment.
// TODO: replace _cFlags usage if a better solution is found.
float widthDelta = newRect.size.width - textSize.width;
if (_cFlags.alignment == NSRightTextAlignment && widthDelta > 0) {
newRect.size.width -= widthDelta;
newRect.origin.x += widthDelta;
}
}
return newRect;
}
You can use NSParagraphStyle/NSMutableParagraphStyle to set the alignment (and other attributes). Add an appropriately-configured NSParagraphStyle object to the full range of your attributed string.
There are a couple of potential solutions posted in a similar question which I asked a while back.
In all honesty, I still use the undocumented _cFlags.vCentered boolean (tsk tsk, bad programmer!) to get the job done. It's simple, and it works. I'll reinvent the wheel later on if I have to.
update:
OK, I think I've figured it out. Both solutions rely on a call to super to get the default rect, and then modify origin.y and size.height to perform the vertical centering. The calls to super, however, return a rectangle whose width has already been adjusted to fit the text horizontally.
The solution is to use origin.x and size.width from the bounds rect that is passed in to the method:
In solution #1:
- (NSRect)titleRectForBounds:(NSRect)theRect {
NSRect titleFrame = [super titleRectForBounds:theRect];
NSSize titleSize = [[self attributedStringValue] size];
// modified:
theRect.origin.y += (theRect.size.height - titleSize.height)/2.0 - 0.5;
return theRect;
}
In solution #2:
- (NSRect)drawingRectForBounds:(NSRect)theRect
{
NSRect newRect = [super drawingRectForBounds:theRect];
// modified:
newRect.origin.x = theRect.origin.x;
newRect.size.width = theRect.size.width;
if (mIsEditingOrSelecting == NO)
{
// Get our ideal size for current text
NSSize textSize = [self cellSizeForBounds:theRect];
// Center that in the proposed rect
float heightDelta = newRect.size.height - textSize.height;
if (heightDelta > 0)
{
newRect.size.height -= heightDelta;
newRect.origin.y += (heightDelta / 2);
}
}
return newRect;
}