I am trying to detect the taps which could be anywhere on iPhone not just iPhone screen. Here is a link which shows that it is possible.
Basically what i want to do is send an alert if user taps 3 times on iPhone while the Phone is in his pocket.
What i have achieved is that i can detect the 3 taps but i also get the false alerts as well in these cases. 1) if user walking, 2) waving his phone 3) running. I need to just check if user has hit his iPhone 3 times.
Here is my code.
- (void)accelerometer:(UIAccelerometer *)accelerometer
didAccelerate:(UIAcceleration *)acceleration
{
if (handModeOn == NO)
{
if(pocketFlag == NO)
return;
}
float accelZ = 0.0;
float accelX = 0.0;
float accelY = 0.0;
accelX = (acceleration.x * kFilteringFactor) + (accelX * (1.0 - kFilteringFactor));
accelY = (acceleration.y * kFilteringFactor) + (accelY * (1.0 - kFilteringFactor));
accelZ = (acceleration.z * kFilteringFactor) + (accelZ * (1.0 - kFilteringFactor));
self.z.text = [NSString stringWithFormat:#"%0.1f", -accelZ];
if((-accelZ >= [senstivity floatValue] && timerFlag) || (-accelZ <= -[senstivity floatValue] && timerFlag)|| (-accelX >= [senstivity floatValue] && timerFlag) || (-accelX <= -[senstivity floatValue] && timerFlag) || (-accelY >= [senstivity floatValue] && timerFlag) || (-accelY <= -[senstivity floatValue] && timerFlag))
{
timerFlag = false;
addValueFlag = true;
timer = [NSTimer scheduledTimerWithTimeInterval:1.5 target:self selector:#selector(timerTick:) userInfo:nil repeats:YES];
}
if(addValueFlag)
{
if (self.xSwitch.on)
{
NSLog(#"X sWitch is on");
[self.accArray addObject:[NSNumber numberWithFloat:-accelX]];
}
if (self.ySwitch.on)
{
NSLog(#"Y Switch is on");
[self.accArray addObject:[NSNumber numberWithFloat:-accelY]];
}
if (self.zSwitch.on)
{
NSLog(#"Z Switch is on");
[self.accArray addObject:[NSNumber numberWithFloat:-accelZ]];
}
}
//}
}
- (void)timerTick:(NSTimer *)timer1
{
[timer1 invalidate];
addValueFlag = false;
int count = 0;
for(int i = 0; i < self.accArray.count; i++)
{
if(([[self.accArray objectAtIndex:i] floatValue] >= [senstivity floatValue]) || ([[self.accArray objectAtIndex:i] floatValue] <= -[senstivity floatValue]))
{
count++;
[self playAlarm:#"beep-1" FileType:#"mp3"];
}
if(count >= 3)
{
[self playAlarm:#"06_Alarm___Auto___Rapid_Beeping_1" FileType:#"caf"];
[self showAlert];
timerFlag = true;
[self.accArray removeAllObjects];
return;
}
}
[self.accArray removeAllObjects];
timerFlag = true;
}
Any help will be really appreciated.
Thanks
You should apply a high pass filter to the accelerometer data. That will give you just the spikes in the signal - sharp taps.
I did a quick search on "UIAccelerometer high pass filter" and found several hits. The simplest code takes a rolling average of the accelerometer input, then subtracts that average from the instantaneous reading to find sudden changes. There are no doubt more sophisticated methods as well.
Once you have code that recognizes sharp taps, you'll need to craft code that detects 3 sharp taps in a row.
This is, as suggested by another answer, all to do with filtering the taps from the stream of accelerometer data. The impulse-like tap's will have a characteristic spectrogram (combination of frequencies) that can be detected when the response from a proper filter is higher than a threshold.
This is a very common operation on iPhone, I would suggest you look at official documentation such as here
The sample code I have linked to gives you two important things: official example code for high-pass filter AND a sample app that will graph the accelerometer data. This you can use to visual your taps, steps and jumps, to better understand why your filter responds falsely.
Furthermore, the internet is a huge source of literature on filter design - if you need to make a very high quality filter, you may need to consult the literature. I think however that a suitable second order filter would likely be sufficient.
#implementation HighpassFilter
- (id)initWithSampleRate:(double)rate cutoffFrequency:(double)freq
{
self = [super init];
if (self != nil)
{
double dt = 1.0 / rate;
double RC = 1.0 / freq;
filterConstant = RC / (dt + RC);
}
return self;
}
- (void)addAcceleration:(UIAcceleration *)accel
{
double alpha = filterConstant;
if (adaptive)
{
double d = Clamp(fabs(Norm(x, y, z) - Norm(accel.x, accel.y, accel.z)) / kAccelerometerMinStep - 1.0, 0.0, 1.0);
alpha = d * filterConstant / kAccelerometerNoiseAttenuation + (1.0 - d) * filterConstant;
}
x = alpha * (x + accel.x - lastX);
y = alpha * (y + accel.y - lastY);
z = alpha * (z + accel.z - lastZ);
lastX = accel.x;
lastY = accel.y;
lastZ = accel.z;
}
- (NSString *)name
{
return adaptive ? #"Adaptive Highpass Filter" : #"Highpass Filter";
}
#end
Importantly this filter is direction agnostic, since only the magnitude of the acceleration is filtered. This is crucial to make the response seem normal. Otherwise users may feel like the have to tap from different angles to find a sweetspot.
On another note, if this task is proving too difficult and fiddly, I strongly suggest capturing your data ( in a WAV file for example) and using one of any common signal anaylsing program to get a better idea of where it is going wrong. See Baudline
Here is how i achieved it.
- (void)accelerometer:(UIAccelerometer *)accelerometer
didAccelerate:(UIAcceleration *)acceleration
{
if (pause)
{
return;
}
if (handModeOn == NO)
{
if(pocketFlag == NO)
return;
}
// float accelZ = 0.0;
// float accelX = 0.0;
// float accelY = 0.0;
rollingX = (acceleration.x * kFilteringFactor) + (rollingX * (1.0 - kFilteringFactor));
rollingY = (acceleration.y * kFilteringFactor) + (rollingY * (1.0 - kFilteringFactor));
rollingZ = (acceleration.z * kFilteringFactor) + (rollingZ * (1.0 - kFilteringFactor));
float accelX = acceleration.x - rollingX;
float accelY = acceleration.y - rollingY;
float accelZ = acceleration.z - rollingZ;
if((-accelZ >= [senstivity floatValue] && timerFlag) || (-accelZ <= -[senstivity floatValue] && timerFlag)|| (-accelX >= [senstivity floatValue] && timerFlag) || (-accelX <= -[senstivity floatValue] && timerFlag) || (-accelY >= [senstivity floatValue] && timerFlag) || (-accelY <= -[senstivity floatValue] && timerFlag))
{
timerFlag = false;
addValueFlag = true;
timer = [NSTimer scheduledTimerWithTimeInterval:1.5 target:self selector:#selector(timerTick:) userInfo:nil repeats:YES];
}
if(addValueFlag)
{
[self.accArray addObject:[NSNumber numberWithFloat:-accelX]];
[self.accArray addObject:[NSNumber numberWithFloat:-accelY]];
[self.accArray addObject:[NSNumber numberWithFloat:-accelZ]];
}
}
Some good answers. Here's some working code. I implemented this as a subclass of UIGestureRecognizer so that you can just drop it in and attach it to a UIView or UIButton. Once triggered, it will have the "pressure" set to a float between 0.0f and 2.0f. You can optionally set the minimum and maximum pressures required to recognize. Enjoy.
#import <UIKit/UIKit.h>
#define CPBPressureNone 0.0f
#define CPBPressureLight 0.1f
#define CPBPressureMedium 0.3f
#define CPBPressureHard 0.8f
#define CPBPressureInfinite 2.0f
#interface CPBPressureTouchGestureRecognizer : UIGestureRecognizer <UIAccelerometerDelegate> {
#public
float pressure;
float minimumPressureRequired;
float maximumPressureRequired;
#private
float pressureValues[30];
uint currentPressureValueIndex;
uint setNextPressureValue;
}
#property (readonly, assign) float pressure;
#property (readwrite, assign) float minimumPressureRequired;
#property (readwrite, assign) float maximumPressureRequired;
#end
//
// CPBPressureTouchGestureRecognizer.h
// PressureSensitiveButton
//
// Created by Anthony Picciano on 3/21/11.
// Copyright 2011 Anthony Picciano. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#import <UIKit/UIGestureRecognizerSubclass.h>
#import "CPBPressureTouchGestureRecognizer.h"
#define kUpdateFrequency 60.0f
#define KNumberOfPressureSamples 3
#interface CPBPressureTouchGestureRecognizer (private)
- (void)setup;
#end
#implementation CPBPressureTouchGestureRecognizer
#synthesize pressure, minimumPressureRequired, maximumPressureRequired;
- (id)initWithTarget:(id)target action:(SEL)action {
self = [super initWithTarget:target action:action];
if (self != nil) {
[self setup];
}
return self;
}
- (id)init {
self = [super init];
if (self != nil) {
[self setup];
}
return self;
}
- (void)setup {
minimumPressureRequired = CPBPressureNone;
maximumPressureRequired = CPBPressureInfinite;
pressure = CPBPressureNone;
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:1.0f / kUpdateFrequency];
[[UIAccelerometer sharedAccelerometer] setDelegate:self];
}
#pragma -
#pragma UIAccelerometerDelegate methods
-(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
int sz = (sizeof pressureValues) / (sizeof pressureValues[0]);
// set current pressure value
pressureValues[currentPressureValueIndex%sz] = acceleration.z;
if (setNextPressureValue > 0) {
// calculate average pressure
float total = 0.0f;
for (int loop=0; loop<sz; loop++) total += pressureValues[loop];
float average = total / sz;
// start with most recent past pressure sample
if (setNextPressureValue == KNumberOfPressureSamples) {
float mostRecent = pressureValues[(currentPressureValueIndex-1)%sz];
pressure = fabsf(average - mostRecent);
}
// caluculate pressure as difference between average and current acceleration
float diff = fabsf(average - acceleration.z);
if (pressure < diff) pressure = diff;
setNextPressureValue--;
if (setNextPressureValue == 0) {
if (pressure >= minimumPressureRequired && pressure <= maximumPressureRequired)
self.state = UIGestureRecognizerStateRecognized;
else
self.state = UIGestureRecognizerStateFailed;
}
}
currentPressureValueIndex++;
}
#pragma -
#pragma UIGestureRecognizer subclass methods
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
setNextPressureValue = KNumberOfPressureSamples;
self.state = UIGestureRecognizerStatePossible;
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
self.state = UIGestureRecognizerStateFailed;
}
- (void)reset {
pressure = CPBPressureNone;
setNextPressureValue = 0;
currentPressureValueIndex = 0;
}
#end
Related
So I did the Ray Wenderlich tutorial for a 5-star review view, only I modified it to do half-star ratings.
It looked like it was working perfectly, but it turns out, the custom UIView is not always updating the UITableView that the rating value changed.
I added a NSLog() to display the rating value in the custom class, and then added a label to output the value in the TableView. It seems like, it takes a very deliberate tap-drag-stop-lift finger action to get it to update the TableView value. Any thoughts?? Following is some code snipets:
Modified 'Handle Touch'
(void)handleTouchAtLocation:(CGPoint)touchLocation {
if (!self.editable) return;
float newRating = 0;
for(int i = self.imageViews.count - 1; i >= 0; i--) {
UIImageView *imageView = [self.imageViews objectAtIndex:i];
CGFloat distance = touchLocation.x - imageView.frame.origin.x;
if (distance <= 0) continue;
if (distance < imageView.frame.size.width / 2) {
newRating = i + 0.5;
break;
} else if (distance > imageView.frame.size.width / 2){
newRating = i + 1;
break;
}
}
self.rating = newRating;
NSLog(#"Self.rating = %f", self.rating);
}
Segments of my tableView .h/.m file
My .h
#property (weak, nonatomic) IBOutlet RateView *queenRateView;
My .m
//Queen Performance Rating
self.queenRateView.rating = 0;
self.queenRateView.editable = YES;
self.queenRateView.maxRating = 5;
self.queenRateView.delegate = self;
- (void)rateView:(RateView *)rateView ratingDidChange:(float)rating {
NSLog(#"Queen Rating from Class: %f", rating);
queenRating = [NSNumber numberWithFloat: rating];
NSLog(#"Queen Rating: %#", queenRating);
self.queenRatingLabel.text = [NSString stringWithFormat:#"%#", queenRating];
}
I think you messing this part :
(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self.delegate rateView:self ratingDidChange:self.rating];
}
I am migrating all my games to cocos2d 3.0. I used SlidingMenuGrid.m with slight modification.
SlidingMenuGrid.h:
//
// SlidingMenuGrid
//
// Created by Brandon Reynolds on 1/9/11.
#import "cocos2d.h"
#interface SlidingMenuGrid : CCLayer
{
tCCMenuState state; // State of our menu grid. (Eg. waiting, tracking touch, cancelled, etc)
CCMenuItem *selectedItem; // Menu item that was selected/active.
CGPoint padding; // Padding in between menu items.
CGPoint menuOrigin; // Origin position of the entire menu grid.
CGPoint touchOrigin; // Where the touch action began.
CGPoint touchStop; // Where the touch action stopped.
int iPageCount; // Number of pages in this grid.
int iCurrentPage; // Current page of menu items being viewed.
bool bMoving; // Is the grid currently moving?
bool bSwipeOnlyOnMenu; // Causes swiping functionality to only work when siping on top of the menu items instead of entire screen.
bool bVerticalPaging; // Disabled by default. Allows for pages to be scrolled vertically instead of horizontal.
float fMoveDelta; // Distance from origin of touch and current frame.
float fMoveDeadZone; // Amount they need to slide the grid in order to move to a new page.
float fAnimSpeed; // 0.0-1.0 value determining how slow/fast to animate the paging.
CGPoint pageIndicatorPosition;
}
//use this 5:
-(int) getTotalPage;
-(int) getCurrentPage;
-(void) moveToPage: (int) page animated: (BOOL) animated;
+(id) packWithArray: (NSMutableArray*) items posY: (int) posY indicatorPosY: (int) indiPosY;
+(id) levelWithArray: (NSMutableArray*) items cols: (int) cols rows: (int) rows leftEdge: (int) leftEdge upperEdge: (int) upperEdge lowerEdge: (int) lowerEdge indicatorPosY: (int) indiPosY;
+(id) menuWithArray:(NSMutableArray*)items cols:(int)cols rows:(int)rows position:(CGPoint)pos padding:(CGPoint)pad pageIndicatorPosition:(CGPoint)pip;
-(id) initWithArray:(NSMutableArray*)items cols:(int)cols rows:(int)rows position:(CGPoint)pos padding:(CGPoint)pad verticalPaging:(bool)vertical pageIndicatorPosition:(CGPoint)pip;
-(void) buildGrid:(int)cols rows:(int)rows;
-(void) buildGridVertical:(int)cols rows:(int)rows;
-(CCMenuItem*) GetItemWithinTouch:(UITouch*)touch;
- (CGPoint) GetPositionOfCurrentPageWithOffset:(float)offset;
- (CGPoint) GetPositionOfCurrentPage;
- (bool) IsSwipingOnMenuOnlyEnabled;
- (void) SetSwipingOnMenuOnly:(bool)bValue;
- (float) GetSwipeDeadZone;
- (void) SetSwipeDeadZone:(float)fValue;
- (bool) IsVerticallyPaged;
- (void) SetVerticalPaging:(bool)bValue;
#property (nonatomic, readwrite) CGPoint padding;
#property (nonatomic, readwrite) CGPoint menuOrigin;
#property (nonatomic, readwrite) CGPoint touchOrigin;
#property (nonatomic, readwrite) CGPoint touchStop;
#property (nonatomic, readwrite) int iPageCount;
#property (nonatomic, readwrite) int iCurrentPage;
#property (nonatomic, readwrite) bool bMoving;
#property (nonatomic, readwrite) bool bSwipeOnlyOnMenu;
#property (nonatomic, readwrite) bool bVerticalPaging;
#property (nonatomic, readwrite) float fMoveDelta;
#property (nonatomic, readwrite) float fMoveDeadZone;
#property (nonatomic, readwrite) float fAnimSpeed;
#end
SlidingMenuGrid.m
#import "SlidingMenuGrid.h"
#import "Constants.h"
#implementation SlidingMenuGrid
#synthesize padding;
#synthesize menuOrigin;
#synthesize touchOrigin;
#synthesize touchStop;
#synthesize bMoving;
#synthesize bSwipeOnlyOnMenu;
#synthesize fMoveDelta;
#synthesize fMoveDeadZone;
#synthesize iPageCount;
#synthesize iCurrentPage;
#synthesize bVerticalPaging;
#synthesize fAnimSpeed;
-(int) getTotalPage {
return iPageCount;
}
-(int) getCurrentPage {
return iCurrentPage;
}
+(id) packWithArray: (NSMutableArray*) items posY: (int) posY indicatorPosY: (int) indiPosY {
CGSize size = [CCDirector sharedDirector].winSize;
BOOL vertical = NO;
//pos, padding, pageindicatorpos
CGPoint pos = ccp(size.width / 2, posY);
CGPoint posIndicator = ccp(size.width / 2, indiPosY);
//padding no matter
return [SlidingMenuGrid menuWithArray:items cols:1 rows:1 position:pos padding:ccp(0, 0) verticalPaging:vertical pageIndicatorPosition:posIndicator];
}
+(id) levelWithArray: (NSMutableArray*) items cols: (int) cols rows: (int) rows leftEdge: (int) leftEdge upperEdge: (int) upperEdge lowerEdge: (int) lowerEdge indicatorPosY: (int) indiPosY {
CGSize screen = [CCDirector sharedDirector].winSize;
BOOL vertical = NO;
CGSize itemSize = [[items objectAtIndex:0] boundingBox].size;
//pos, padding, pageindipos
CGPoint posIndicator = ccp(screen.width / 2, indiPosY);
int posx = leftEdge + itemSize.width / 2;
int posy = screen.height - (upperEdge + itemSize.height / 2);
int padx = (screen.width - itemSize.width - 2 * leftEdge) / (cols - 1);
int pady = (screen.height - upperEdge - lowerEdge - itemSize.height) / (rows - 1);
return [SlidingMenuGrid menuWithArray:items cols:cols rows:rows position:ccp(posx, posy) padding:ccp(padx, pady) verticalPaging:vertical pageIndicatorPosition:posIndicator];
}
+(id) menuWithArray:(NSMutableArray*)items cols:(int)cols rows:(int)rows position:(CGPoint)pos padding:(CGPoint)pad pageIndicatorPosition:(CGPoint)pip
{
return [[[self alloc] initWithArray:items cols:cols rows:rows position:pos padding:pad verticalPaging:false pageIndicatorPosition:pip] autorelease];
}
+(id) menuWithArray:(NSMutableArray*)items cols:(int)cols rows:(int)rows position:(CGPoint)pos padding:(CGPoint)pad verticalPaging:(bool)vertical pageIndicatorPosition: (CGPoint) pip
{
return [[[self alloc] initWithArray:items cols:cols rows:rows position:pos padding:pad verticalPaging:vertical pageIndicatorPosition:pip] autorelease];
}
-(id) initWithArray:(NSMutableArray*)items cols:(int)cols rows:(int)rows position:(CGPoint)pos padding:(CGPoint)pad verticalPaging:(bool)vertical pageIndicatorPosition:(CGPoint)pip
{
if ((self = [super init]))
{
self.isTouchEnabled = YES;
int z = 1;
for (id item in items)
{
[self addChild:item z:z tag:z];
++z;
}
padding = pad;
iCurrentPage = 0;
bMoving = false;
bSwipeOnlyOnMenu = false;
menuOrigin = pos;
fMoveDeadZone = 10;
bVerticalPaging = vertical;
fAnimSpeed = 0.6f;
(bVerticalPaging) ? [self buildGridVertical:cols rows:rows] : [self buildGrid:cols rows:rows];
self.position = menuOrigin;
pageIndicatorPosition = pip;
}
return self;
}
-(void) dealloc
{
[super dealloc];
}
-(void) buildGrid:(int)cols rows:(int)rows
{
CGSize winSize = [[CCDirector sharedDirector] winSize];
int col = 0, row = 0;
for (CCMenuItem* item in self.children)
{
// Calculate the position of our menu item.
item.position = CGPointMake(self.position.x + col * padding.x + (iPageCount * winSize.width), self.position.y - row * padding.y);
// Increment our positions for the next item(s).
++col;
if (col == cols)
{
col = 0;
++row;
if( row == rows )
{
iPageCount++;
col = 0;
row = 0;
}
}
}
if([self children].count > rows * cols * iPageCount) iPageCount++;
}
-(void) buildGridVertical:(int)cols rows:(int)rows
{
CGSize winSize = [[CCDirector sharedDirector] winSize];
int col = 0, row = 0;
for (CCMenuItem* item in self.children)
{
// Calculate the position of our menu item.
item.position = CGPointMake(self.position.x + col * padding.x , self.position.y - row * padding.y + (iPageCount * winSize.height));
// Increment our positions for the next item(s).
++col;
if (col == cols)
{
col = 0;
++row;
if( row == rows )
{
iPageCount++;
col = 0;
row = 0;
}
}
}
if([self children].count > rows * cols * iPageCount) iPageCount++;
}
-(void) addChild:(CCMenuItem*)child z:(int)z tag:(int)aTag
{
return [super addChild:child z:z tag:aTag];
}
-(CCMenuItem*) GetItemWithinTouch:(UITouch*)touch
{
// Get the location of touch.
CGPoint touchLocation = [[CCDirector sharedDirector] convertToGL: [touch locationInView: [touch view]]];
// Parse our menu items and see if our touch exists within one.
for (CCMenuItem* item in [self children])
{
//only deal with the item
if ([item isKindOfClass:[CCMenuItem class]]) {
CGPoint local = [item convertToNodeSpace:touchLocation];
CGRect r = [item rect];
r.origin = CGPointZero;
// If the touch was within this item. Return the item.
if (CGRectContainsPoint(r, local))
{
return item;
}
}
}
// Didn't touch an item.
return nil;
}
-(void) registerWithTouchDispatcher
{
[[CCDirector sharedDirector].touchDispatcher addTargetedDelegate:self priority:kCCMenuHandlerPriority swallowsTouches:NO];
}
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
// Convert and store the location the touch began at.
touchOrigin = [[CCDirector sharedDirector] convertToGL:[touch locationInView:[touch view]]];
// If we weren't in "waiting" state bail out.
if (state != kCCMenuStateWaiting)
{
return NO;
}
// Activate the menu item if we are touching one.
selectedItem = [self GetItemWithinTouch:touch];
[selectedItem selected];
// Only track touch if we are either in our menu system or dont care if they are outside of the menu grid.
if (!bSwipeOnlyOnMenu || (bSwipeOnlyOnMenu && selectedItem) )
{
state = kCCMenuStateTrackingTouch;
return YES;
}
return NO;
}
// Touch has ended. Process sliding of menu or press of menu item.
-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
// User has been sliding the menu.
if( bMoving )
{
bMoving = false;
// Do we have multiple pages?
if( iPageCount > 1 && (fMoveDeadZone < abs(fMoveDelta)))
{
// Are we going forward or backward?
bool bForward = (fMoveDelta < 0) ? true : false;
// Do we have a page available?
if(bForward && (iPageCount>iCurrentPage+1))
{
// Increment currently active page.
iCurrentPage++;
}
else if(!bForward && (iCurrentPage > 0))
{
// Decrement currently active page.
iCurrentPage--;
}
}
// Start sliding towards the current page.
[self moveToCurrentPage];
}
// User wasn't sliding menu and simply tapped the screen. Activate the menu item.
else
{
[selectedItem unselected];
[selectedItem activate];
}
// Back to waiting state.
state = kCCMenuStateWaiting;
}
-(void) moveToPage: (int) page animated:(BOOL)animated {
float interval = 0;
if (animated) {
interval = 0.3f * abs(iCurrentPage - page);
}
iCurrentPage = page;
// Perform the action
CGPoint position = [self GetPositionOfCurrentPage];
CCMoveTo* action = [CCMoveTo actionWithDuration:interval position:position];
[self runAction:action];
}
// Run the action necessary to slide the menu grid to the current page.
- (void) moveToCurrentPage
{
CGSize winSize = [[CCDirector sharedDirector] winSize];
// Perform the action
CGPoint position = [self GetPositionOfCurrentPage];
CCMoveTo* action = [CCMoveTo actionWithDuration:fAnimSpeed * 0.3f position:position];
[self runAction:action];
}
-(void) ccTouchCancelled:(UITouch *)touch withEvent:(UIEvent *)event
{
[selectedItem unselected];
state = kCCMenuStateWaiting;
}
-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
// Calculate the current touch point during the move.
touchStop = [[CCDirector sharedDirector] convertToGL:[touch locationInView:[touch view]]];
// Distance between the origin of the touch and current touch point.
fMoveDelta = (bVerticalPaging) ? (touchStop.y - touchOrigin.y) : (touchStop.x - touchOrigin.x);
// Set our position.
[self setPosition:[self GetPositionOfCurrentPageWithOffset:fMoveDelta]];
bMoving = true;
if (selectedItem) {
[selectedItem unselected];
selectedItem = nil;
}
}
- (CGPoint) GetPositionOfCurrentPage
{
CGSize winSize = [[CCDirector sharedDirector] winSize];
return (bVerticalPaging) ?
CGPointMake(menuOrigin.x,menuOrigin.y-(iCurrentPage*winSize.height))
: CGPointMake((menuOrigin.x-(iCurrentPage*winSize.width)),menuOrigin.y);
}
- (CGPoint) GetPositionOfCurrentPageWithOffset:(float)offset
{
CGSize winSize = [[CCDirector sharedDirector] winSize];
return (bVerticalPaging) ?
CGPointMake(menuOrigin.x,menuOrigin.y-(iCurrentPage*winSize.height)+offset)
: CGPointMake((menuOrigin.x-(iCurrentPage*winSize.width)+offset),menuOrigin.y);
}
// Returns whether or not we should only allow swiping on the actual grid.
- (bool) IsSwipingOnMenuOnlyEnabled
{
return bSwipeOnlyOnMenu;
}
// Sets the ability to swipe only on the menu or utilize entire screen for swiping.
- (void) SetSwipingOnMenuOnly:(bool)bValue
{
bSwipeOnlyOnMenu = bValue;
}
// Returns the swiping dead zone.
- (float) GetSwipeDeadZone
{
return fMoveDeadZone;
}
- (void) SetSwipeDeadZone:(float)fValue
{
fMoveDeadZone = fValue;
}
// Returns wheather or not vertical paging is enabled.
- (bool) IsVerticallyPaged
{
return bVerticalPaging;
}
// Sets the vertical paging flag.
- (void) SetVerticalPaging:(bool)bValue
{
bVerticalPaging = bValue;
[self buildGridVertical];
}
- (void) visit
{
[super visit];//< Will draw after glPopScene.
BOOL showPagesIndicator = YES;
ccColor4B pagesIndicatorNormalColor_ = ccc4(180, 180, 180, 255);
ccColor4B pagesIndicatorSelectedColor_ = ccc4(255, 255, 255, 255);
if (showPagesIndicator)
{
int totalScreens = iPageCount;
// Prepare Points Array
CGFloat n = (CGFloat)totalScreens; //< Total points count in CGFloat.
CGFloat pY = pageIndicatorPosition.y; //< Points y-coord in parent coord sys.
CGFloat d = ph_pad(16.0f, 16.0f * 2); //< Distance between points.
CGPoint points[totalScreens];
for (int i=0; i < totalScreens; ++i)
{
CGFloat pX = pageIndicatorPosition.x + d * ( (CGFloat)i - 0.5f*(n-1.0f) );
points[i] = ccp (pX, pY);
}
// Set GL Values
#if COCOS2D_VERSION >= 0x00020000
// ccGLEnable(CC_GL_BLEND);
ccPointSize( ph_pad(5.0 * CC_CONTENT_SCALE_FACTOR(), 5.0 * CC_CONTENT_SCALE_FACTOR() * 2) );
#define DRAW_4B_FUNC ccDrawColor4B
#else
glEnable(GL_POINT_SMOOTH);
GLboolean blendWasEnabled = glIsEnabled( GL_BLEND );
glEnable(GL_BLEND);
// save the old blending functions
int blend_src = 0;
int blend_dst = 0;
glGetIntegerv( GL_BLEND_SRC, &blend_src );
glGetIntegerv( GL_BLEND_DST, &blend_dst );
glPointSize( ph_pad(5.0 * CC_CONTENT_SCALE_FACTOR(), 5.0 * CC_CONTENT_SCALE_FACTOR() * 2) );
#define DRAW_4B_FUNC glColor4ub
#endif
ccGLBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
// Draw Gray Points
DRAW_4B_FUNC(pagesIndicatorNormalColor_.r,
pagesIndicatorNormalColor_.g,
pagesIndicatorNormalColor_.b,
pagesIndicatorNormalColor_.a);
ccDrawPoints( points, totalScreens );
// Draw White Point for Selected Page
DRAW_4B_FUNC(pagesIndicatorSelectedColor_.r,
pagesIndicatorSelectedColor_.g,
pagesIndicatorSelectedColor_.b,
pagesIndicatorSelectedColor_.a);
ccDrawPoint(points[iCurrentPage]);
// Restore GL Values
#if COCOS2D_VERSION >= 0x00020000
ccPointSize(1.0f);
#else
glPointSize(1.0f);
glDisable(GL_POINT_SMOOTH);
if (! blendWasEnabled)
glDisable(GL_BLEND);
// always restore the blending functions too
ccGLBlendFunc( blend_src, blend_dst);
// glBlendFunc( blend_src, blend_dst );
#endif
}
}
#end
The source code is from Brandon Reynolds and I have modified it a bit to better suit my game. I use this 5 methods to easily build menus for packs (single row, multi columns) and levels (multi rows, multi columns)
-(int) getTotalPage;
-(int) getCurrentPage;
-(void) moveToPage: (int) page animated: (BOOL) animated;
+(id) packWithArray: (NSMutableArray*) items posY: (int) posY indicatorPosY: (int) indiPosY;
+(id) levelWithArray: (NSMutableArray*) items cols: (int) cols rows: (int) rows leftEdge: (int) leftEdge upperEdge: (int) upperEdge lowerEdge: (int) lowerEdge indicatorPosY: (int) indiPosY;
But the open gl code is not working anymore for cocos2d 3.0 (e.g. ccGLBlendFunc etc). How can I update this class for cocos2d 3.x? Or any new implementation for similar menu grid?
Now it is better to use CCScrollView.
It supports pagination and you can easily create your level grid by just pre-creating it as a content node.
I've tried several method but I cannot find a working method to replace my
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / kAccelerometerFrequency)];
[[UIAccelerometer sharedAccelerometer] setDelegate:self];
most recently i've tried to replace that with
motionManager = [[CMMotionManager alloc] init];
motionManager.accelerometerUpdateInterval = (1.0 / kAccelerometerFrequency);
But it does not seem to work with my void
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
CGPoint pt = theCar.center;
CGFloat accel = acceleration.x * kAccelerationSpeed;
float halfCarWidth = theCar.frame.size.width / 2;
if(pt.x - halfCarWidth + accel > 0 && pt.x + halfCarWidth + accel < 320) {
pt.x += accel;
}
[theCar setCenter:pt];
}
So what do I replace the sharedAccelerometer with since it was depreciated in iOS 5
There is no delegate message with motion manager. Use a timer (NSTimer) and poll the motion manager for its values, at intervals.
self.motman = [CMMotionManager new];
if (!self.motman.accelerometerAvailable) {
// report error or whatever
return;
}
self.motman.accelerometerUpdateInterval = 1.0 / 30.0;
[self.motman startAccelerometerUpdates];
self.timer =
[NSTimer
scheduledTimerWithTimeInterval:self.motman.accelerometerUpdateInterval
target:self selector:#selector(pollAccel:) userInfo:nil repeats:YES];
Now pollAccel: is called repeatedly. Pull out the accelerometer info:
CMAccelerometerData* dat = self.motman.accelerometerData;
CMAcceleration acc = dat.acceleration;
So I have this app Im working on where you can roll the ball around the screen by tilting the device around(accelerometer). How can I alter the code below so that I don't have to hold the phone flat and have that as my neutral balance point. What I want is that whatever tilt you have with the device at the moment when the app loads, that will be the neural balance point. From that current angle your holding the device that is the neutral point. Neutral balance point meaning the point where the ball is pretty much still. Hope thats clear as to what I would like. Also the app is landscapeRight only.
note The code below works 100 percent well just like it need it to work for my app.Just I need to hold the phone flat to roll the ball around...
CGRect screenRect;
CGFloat screenHeight;
CGFloat screenWidth;
double currentMaxAccelX;
double currentMaxAccelY;
#property (strong, nonatomic) CMMotionManager *motionManager;
-(id)initWithSize:(CGSize)size {
//init several sizes used in all scene
screenRect = [[UIScreen mainScreen] bounds];
screenHeight = screenRect.size.height;
screenWidth = screenRect.size.width;
if (self = [super initWithSize:size]) {
self.motionManager = [[CMMotionManager alloc] init];
self.motionManager.accelerometerUpdateInterval = .2;
[self.motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue currentQueue]
withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
[self outputAccelertionData:accelerometerData.acceleration];
if(error)
{
NSLog(#"%#", error);
}
}];
}
return self;
}
-(void)outputAccelertionData:(CMAcceleration)acceleration{
currentMaxAccelX = 0;
currentMaxAccelY = 0;
if(fabs(acceleration.x) > fabs(currentMaxAccelX))
{
currentMaxAccelY = acceleration.x;
}
if(fabs(acceleration.y) > fabs(currentMaxAccelY))
{
currentMaxAccelX = acceleration.y;
}
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
//set min and max bounderies
float maxY = screenHeight - (self.ball.size.width/2);
float minY = 0 + (self.ball.size.width/2);
float maxX = screenWidth - (self.ball.size.height/2);
float minX = 0 + (self.ball.size.height/2);
float newY = 0;
float newX = 0;
//left and right tilt
if(currentMaxAccelX > 0.05){
newX = currentMaxAccelX * -10;
}
else if(currentMaxAccelX < -0.05){
newX = currentMaxAccelX*-10;
}
else{
newX = currentMaxAccelX*-10;
}
//up and down tilt
newY = currentMaxAccelY *10;
newX = MIN(MAX(newX+self.ball.position.x,minY),maxY);
newY = MIN(MAX(newY+self.ball.position.y,minX),maxX);
self.ball.position = CGPointMake(newX, newY);
}
First, Larme's comment gives the correct answer for determining the starting point.
However, if you are trying to determine device tilt (attitude), you want to use the gyroscope, not the accelerometer. The accelerometer tells how fast the device is moving in each direction. That's useful for determining if the user is quickly moving or shaking the device but doesn't help you at all determine whether the device is being tilted. The gyroscope provides the device's current attitude and the rate of rotation.
Since it sounds like you are trying to implement a ball that will "roll" around a table as the user tilts the device, you probably want to get the attitude. To get the attitude, use startDeviceMotionUpdatesToQueue:withHandler:. Then you can use the attitude property of the CMDeviceMotion object to find out how the device is oriented on each axis.
As it was mentioned, we need to catch an initial device position (accelerometer value) and use it as zero reference. We catch reference value once when game starts and subtract this value from every next accelerometer update.
static const double kSensivity = 1000;
#interface ViewController ()
{
CMMotionManager *_motionManager;
double _vx, _vy; // ball velocity
CMAcceleration _referenceAcc; // zero reference
NSTimeInterval _lastUpdateTimeInterval; // see update: method
}
Initially, ball is motionless (velocities = 0). Zero reference is invalid. I set significant value in CMAcceleration to mark it as invalid:
_referenceAcc.x = DBL_MAX;
Accelerometer updates. As the app uses landscape right mode only we map y-acceleration to x-velocity, and x-acceleration to y-velocity. accelerometerUpdateInterval factor is required to make velocity values independent of update rate. We use negative sensitivity value for x-acceleration, because direction of accelerometer X axis is opposite to landscape right orientation.
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
_vx = 0;
_vy = 0;
_referenceAcc.x = DBL_MAX;
_motionManager = [CMMotionManager new];
_motionManager.accelerometerUpdateInterval = 0.1;
[_motionManager
startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
CMAcceleration acc = accelerometerData.acceleration;
if (_referenceAcc.x == DBL_MAX) {
_referenceAcc = acc;
_referenceAcc.x *= -1;
_referenceAcc.y *= -1;
}
_vy += kSensivity * (acc.x+_referenceAcc.x) * _motionManager.accelerometerUpdateInterval;
_vx += -kSensivity * (acc.y+_referenceAcc.y) * _motionManager.accelerometerUpdateInterval;
}];
self.ball = [SKSpriteNode spriteNodeWithImageNamed:#"ball"];
self.ball.position = CGPointMake(self.size.width/2, self.size.height/2);
[self addChild:self.ball];
}
return self;
}
Your update: method does not respect currentTime value. Intervals between update calls can be different. It would be better to update distance according to time interval.
- (void)update:(NSTimeInterval)currentTime {
CFTimeInterval timeSinceLast = currentTime - _lastUpdateTimeInterval;
_lastUpdateTimeInterval = currentTime;
CGSize parentSize = self.size;
CGSize size = self.ball.frame.size;
CGPoint pos = self.ball.position;
pos.x += _vx * timeSinceLast;
pos.y += _vy * timeSinceLast;
// check bounds, reset velocity if collided
if (pos.x < size.width/2) {
pos.x = size.width/2;
_vx = 0;
}
else if (pos.x > parentSize.width-size.width/2) {
pos.x = parentSize.width-size.width/2;
_vx = 0;
}
if (pos.y < size.height/2) {
pos.y = size.height/2;
_vy = 0;
}
else if (pos.y > parentSize.height-size.height/2) {
pos.y = parentSize.height-size.height/2;
_vy = 0;
}
self.ball.position = pos;
}
EDIT: alternative way
By the way, I found an alternative way solve it. If you use SpriteKit, it is possible to configure gravity of physics world in response to accelerometer changes. In that case there's no need to move a ball in update: method.
We need to add physics body to a ball sprite and make it dynamic:
self.physicsWorld.gravity = CGVectorMake(0, 0); // initial gravity
self.ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.ball.size.width/2];
self.ball.physicsBody.dynamic = YES;
[self addChild:self.ball];
And set updated gravity in accelerometer handler:
// set zero reference acceleration
...
_vy = kSensivity * (acc.x+_referenceAcc.x) * _motionManager.accelerometerUpdateInterval;
_vx = -kSensivity * (acc.y+_referenceAcc.y) * _motionManager.accelerometerUpdateInterval;
self.physicsWorld.gravity = CGVectorMake(_vx, _vy);
Also we need to set physical bounds of the screen in order to limit ball movement.
Why don't you just take the numbers, at the point of start up, as a baseline and save them as a class property. Any further readings you have you can simply add/subtract the current numbers with your baseline. Unless I am wrong, that should give you the desired results.
I want to make UBersense like app (http://blog.ubersense.com/2013/01/03/how-to-use-the-drawing-tools-in-ubersense/), there i need to draw two line with some angle, after that i can adjust the angle between two line by dragging any line or intersection point.
can you guys please provide me some idea or code snippet.
screenshots url:
https://picasaweb.google.com/yunusm7/AppScreenshots#slideshow/5952787957718627714
Thanks in advance.
You have a construction with three points, one point is an angle point, and two others are just vertices. First of all you should create a new class like this:
#interface MyAngle : NSObject {
}
#property (nonatomic) CGPoint p1;
#property (nonatomic) CGPoint p2;
#property (nonatomic) CGPoint v; // this is an angle point
#end
You can use the default implementation of this without any tricks with such sample init:
- (id)init {
if (self = [super init]) {
p1 = CGPointMake(1,0);
p2 = CGPointMake(0,1);
v = CGPointZero;
}
return self;
}
But also as I understood you need to know the value of the angle. You can do this using the following way:
- (CGFloat)valueOfAngle {
CGPoint v1 = CGPointMake(p1.x-v.x, p1.y-v.y);
CGPoint v2 = CGPointMake(p2.x-v.x, p2.y-v.y);
CGFloat scalarProduct = v1.x*v2.x + v1.y*v2.y;
CGFloat lengthProduct = sqrt(v1.x*v1.x + v1.y*v1.y)*sqrt(v2.x*v2.x + v2.y*v2.y);
CGFloat fraction = scalarProduct / lengthProduct;
if (fraction < -1) fraction = -1;
if (fraction > 1) fraction = 1;
return acos(fraction);
}
If you want to obtain angles more than 180 degrees you should change the code above a little. But there are too much information about how to do this in the Internet, so I will skip this part.
Then you need to create an instance of MyAngle in your viewController. Let it be called "angle". Knowing coordinates of every three points if enough do draw it (!!!). Implement drawRect method in a view that will contain the MyAngle instance (I strongly recommend do to this on your own subclass of UIView):
- (void)drawRect {
[super drawRect];
// set options of drawing
CGContextRef c = UIGraphicsGetCurrentContext();
CGFloat red[4] = {1.0f, 0.0f, 0.0f, 1.0f};
CGContextSetLineWidth(c, 3.0);
CGContextSetStrokeColor(c, red);
// draw an angle directly
CGContextBeginPath(c);
CGContextMoveToPoint(c, angle.p1.x, angle.p1.y);
CGContextAddLineToPoint(c, angle.v.x, angle.v.y);
CGContextAddLineToPoint(c, angle.p2.x, angle.p2.y);
CGContextStrokePath(c);
// draw circles around vertices (like on the screenshot you provided)
CGFloat R = 7.0f;
CGContextBeginPath(c);
CGContextAddEllipseInRect(c, CGRectMake(angle.p1.x - R, angle.p1.y - R, 2*R, 2*R));
CGContextStrokePath(c);
CGContextBeginPath(c);
CGContextAddEllipseInRect(c, CGRectMake(angle.p2.x - R, angle.p2.y - R, 2*R, 2*R));
CGContextStrokePath(c);
CGContextBeginPath(c);
CGContextAddEllipseInRect(c, CGRectMake(angle.v.x - R, angle.v.y - R, 2*R, 2*R));
CGContextStrokePath(c);
}
And that's all you need to know for drawing what you want! You can change the stroke color or radius of three circles if you want.
Then you need to have a possibility to change the locations of your angle's points. For this you can just implement panGestureRecognizer in your viewController's viewDidLoad method like this:
UIPanGestureRecognizer *pan = [[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(moveAngle:)] autorelease];
pan.delegate = self;
[self.view addGestureRecognizer:pan];
Implement UIGestureRecognizerDelegate method:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
CGPoint p = [gestureRecognizer locationInView:self.view];
CGFloat d1 = sqrt((p.x-angle.p1.x)*(p.x-angle.p1.x) + (p.y-angle.p1.y)*(p.y-angle.p1.y);
CGFloat d2 = sqrt((p.x-angle.p2.x)*(p.x-angle.p2.x) + (p.y-angle.p2.y)*(p.y-angle.p2.y);
CGFloat d3 = sqrt((p.x-angle.v.x)*(p.x-angle.v.x) + (p.y-angle.v.y)*(p.y-angle.v.y);
// just check if we touched the screen near some of angle's points
CGFloat tolerance = 15.0f;
return (d1 < tolerance) || (d2 < tolerance) || (d3 < tolerance);
}
return YES;
}
and tagret's selector (also in your viewController):
- (void)moveAngle:(UIPanGestureRecognizer *)gr {
CGPoint p = [gr locationInView:self.view];
if (gr.state == UIGestureRecognizerStateBegan) {
CGFloat d1 = sqrt((p.x-angle.p1.x)*(p.x-angle.p1.x) + (p.y-angle.p1.y)*(p.y-angle.p1.y);
CGFloat d2 = sqrt((p.x-angle.p2.x)*(p.x-angle.p2.x) + (p.y-angle.p2.y)*(p.y-angle.p2.y);
CGFloat d3 = sqrt((p.x-angle.v.x)*(p.x-angle.v.x) + (p.y-angle.v.y)*(p.y-angle.v.y);
// pointToMove is your int variable
CGFloat tolerance = 15.0f;
if (d1 < tolerance) {
pointToMove = 1;
}
else if (d2 < tolerance) {
pointToMove = 2;
}
else {
pointToMove = 3;
}
}
else {
if (pointToMove == 1) {
angle.p1 = loc;
}
else if (pointToMove == 2) {
angle.p2 = loc;
}
else {
angle.v = loc;
}
[yourCustomView setNeedsDisplay];
[yourLabel setText:[NSString stringWithFormat:#"%.3f", [angle valueOfangle]*180/PI]];
}
}
Maybe I skip some evident things, but I think it should be enough for you to begin writing some code.