I had created an puzzle game which was like Scrabble.
Here is the layout:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
What is my problem?
My problem was when I start touch from 1 and direction to 12, If touch and drag in slow then no problem but when drag in fast, I manage to 1, 6, 12 or 1, 7, 12 only. There is missing a number.
How to make sure the path numbers all be selected?
I am using touch began, touch moved and touch ended and check with coordinate to locate which number is being touched.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
UITouch *touch = [touches anyObject];
CGPoint currentTouchLocation = [touch locationInView:self.numberview];
if(!ispause && [time.text intValue] > 0){
if(!isbegan && !isended){
for(int i = 1; i <= 16; i++)
{
UIView *imageview = [self.numberview viewWithTag:i];
if (CGRectContainsPoint(imageview.frame, currentTouchLocation))
{
isbegan = YES;
isreverse = NO;
if([[ischose objectAtIndex:i-1] boolValue] == 0)
{
currentposition = imageview.tag;
positionvalue += pow(i, 3);
currentanswer += [self converter:[NSString stringWithFormat:#"%#", [allimagenumbers substringWithRange:NSMakeRange(i-1, 1)]]];
[ischose replaceObjectAtIndex:i-1 withObject:[NSNumber numberWithBool:YES]];
[self changeimage:#"selected"];
}
break;
}
}
}
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesMoved:touches withEvent:event];
UITouch *touch = [touches anyObject];
CGPoint currentTouchLocation = [touch locationInView:self.numberview];
float gapX = image1.frame.size.width / 8;
float gapY = image1.frame.size.height / 8;
if(isbegan && !isended)
{
if(currentTouchLocation.x >= 0 && currentTouchLocation.x <= self.numberview.frame.size.width && currentTouchLocation.y >= 0 && currentTouchLocation.y <= self.numberview.frame.size.height)
{
for(int i = 1; i <= 16; i++)
{
UIView *imageview = [self.numberview viewWithTag:i];
if (CGRectContainsPoint(imageview.frame, currentTouchLocation))
{
if((currentTouchLocation.x >= imageview.frame.origin.x + gapX && currentTouchLocation.x < imageview.frame.origin.x + imageview.frame.size.width - gapX) && (currentTouchLocation.y >= imageview.frame.origin.y + gapY && currentTouchLocation.y < imageview.frame.origin.y + imageview.frame.size.height - gapY ))
{
if([[ischose objectAtIndex:i-1] boolValue] == 0 && !isreverse)
{
currentposition = imageview.tag;
positionvalue += pow(i, 3);
currentanswer += [self converter:[NSString stringWithFormat:#"%#", [allimagenumbers substringWithRange:NSMakeRange(i-1, 1)]]];
[ischose replaceObjectAtIndex:i-1 withObject:[NSNumber numberWithBool:YES]];
[self changeimage:#"selected"];
}
else
{
if(currentposition != imageview.tag)
{
isreverse = YES;
}
else
{
isreverse = NO;
}
}
break;
}
}
}
}
else
{
isended = YES;
isoutofbound = YES;
if(isbegan && isoutofbound)
[self countinganswer];
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
if(!isoutofbound)
{
isended = YES;
[self countinganswer];
}
else
isoutofbound = NO;
}
-(void)changeimage:(NSString *)status{
if([status isEqualToString:#"default"])
{
for(int i = 1; i <=16;i++)
{
UIImageView *imageview = (UIImageView*)[self.numberview viewWithTag:i];
imageview.image = [UIImage imageNamed:[NSString stringWithFormat:#"stone%#", [allimagenumbers substringWithRange:NSMakeRange(i-1, 1)]]];
[image1 setUserInteractionEnabled:YES];
}
}
else if([status isEqualToString:#"correct"] || [status isEqualToString:#"selected"])
{
for(int i = 1; i<= ischose.count; i++)
{
if([[ischose objectAtIndex:i-1] boolValue] == 1)
{
UIImageView *imageview = (UIImageView*)[self.numberview viewWithTag:i];
imageview.image = [UIImage imageNamed:[NSString stringWithFormat:#"stone%#_correct", [allimagenumbers substringWithRange:NSMakeRange(i-1, 1)]]];
}
}
}
else if([status isEqualToString:#"wrong"] || [status isEqualToString:#"repeat"])
{
for(int i = 1; i<= ischose.count; i++)
{
if([[ischose objectAtIndex:i-1] boolValue] == 1)
{
UIImageView *imageview = (UIImageView*)[self.numberview viewWithTag:i];
imageview.image = [UIImage imageNamed:[NSString stringWithFormat:#"stone%#_wrong_repeat", [allimagenumbers substringWithRange:NSMakeRange(i-1, 1)]]];
}
}
}
}
Update:
Chatting with you, it appears that you have solved your problem (where a swipe over one of your UIImageView objects was not being detected). It looks like the solution was a unique issue (i.e. highly "localized" in Stack Overflow language) associated with your code to create reduced size "hit zones" that you constructed with your gap variables. It doesn't look like the solution was anything really associated with touchesMoved, iOS API, or iOS system performance. Regardless, I'm glad you solved the problem.
My original answer below was predicated on the original source code posted, which had the same logic repeated for each of the 16 UIImageView objects. I was just demonstrating how you can use a UIArray to significantly simplify that logic. I also use UIPanGestureRecognizer, which I think unifies the code, and with <UIKit/UIGestureRecognizerSubclass.h> you can cancel the gesture, in case the user's gesture went "out of bounds."
Original answer:
I'm assuming that you simply want to build an array of image numbers as the user drags their finger over the numbers. So the ARC code might look something like:
// NumberGameViewController.m
#import "NumberGameViewController.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
#interface NumberGameViewController ()
{
NSArray *images;
NSMutableArray *results;
}
#end
#implementation NumberGameViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// if you build an array of your images, the logic to determine which image you're over is much easier
images = #[self.image1, self.image2, self.image3, self.image4, self.image5, self.image6, self.image7, self.image8, self.image9, self.image10, self.image11, self.image12, self.image13, self.image14, self.image15, self.image16];
// I know you used `touchesMoved` and the like, but I think gesture recognizers are a little easier
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[self.numberview addGestureRecognizer:pan];
}
- (NSInteger)determineImageNumber:(CGPoint)point
{
for (NSInteger i = 0; i < [images count]; i++)
{
UIImageView *imageview = images[i];
// I'm just going to see if the user's finger was over the number in question.
// If you wanted more restrictive logic (e.g. 3/4ths of the frame), just adjust
// adjust the frame variable here.
CGRect frame = imageview.frame;
if (CGRectContainsPoint(frame, point))
return i;
}
return -1;
}
- (void)handlePan:(UIGestureRecognizer *)gesture
{
CGPoint location = [gesture locationInView:self.numberview];
NSInteger imageNumber = [self determineImageNumber:location];
static NSInteger lastImageNumber;
if (gesture.state == UIGestureRecognizerStateBegan)
{
results = [[NSMutableArray alloc] init];
if (imageNumber >= 0)
{
[results addObject:#(imageNumber)];
}
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
if (imageNumber >= 0)
{
if (imageNumber != lastImageNumber)
{
// If you want to do some visual adjustment of the image you're over, do it
// here.
// add the image to our array of results
[results addObject:#(imageNumber)];
// if you want to do some additional validation (e.g. do you have 16 points,
// has the user hit the same number twice, etc.), do that here
}
}
// by the way, let's check to see if we're still within the numberview subview, and if
// not, let's cancel the gesture
if (!CGRectContainsPoint(self.numberview.bounds, location))
{
gesture.state = UIGestureRecognizerStateCancelled;
return;
}
}
else if ((gesture.state == UIGestureRecognizerStateEnded) || (gesture.state == UIGestureRecognizerStateCancelled) || (gesture.state == UIGestureRecognizerStateFailed))
{
// At this point you'd do any final validation of the user's response, to see if they
// succeeded or not. I'm just displaying the results in the console
NSLog(#"%#", results);
}
if (imageNumber >= 0)
lastImageNumber = imageNumber;
}
#end
float gapX = image1.frame.size.width / 8;
float gapY = image1.frame.size.height / 8;
Here is the problem, there is a gap within all images so when fast drag it will be outer part of images
Related
Ok, i've read a few posts on this one (ex. UIImageView Gestures (Zoom, Rotate) Question) but I can't seem to fix my problem.
I have the following setup: an SKScene, an SKNode _backgroundLayer and 9 SKSpriteNodes that are tiles that make up the background and are attached to the _backgroundLayer.
Since these 9 tiles make a 3x3 square and they are quite large, I need to be able to zoom in and look at other SKSpriteNodes that will be on top of these 9 background images.
There are two problems:
1) When I pinch to zoom in or zoom out it seems like it is zooming in/out from location (0,0) of the _backgroundLayer and not from the touch location.
2) I have added some bounds so that the user can not scroll out of the 9 background images. In general it works. However, if I zoom in then move towards the top of the 9 background images and then zoom out the bounding conditions go berserk and the user can see the black space outside the background images. I need a way to limit the amount of zooming out that the user can do depending on where he's at.
Any ideas? Thanks!
I include my code below:
#import "LevelSelectScene.h"
#import "TurtleWorldSubScene.h"
#interface LevelSelectScene ()
#property (nonatomic, strong) SKNode *selectedNode;
#end
#implementation LevelSelectScene
{
SKNode *_backgroundLayer;
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
_backgroundLayer = [SKNode node];
_backgroundLayer.name = #"backgroundLayer";
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[_backgroundLayer setScale:0.76];
} else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && IS_WIDESCREEN) {
} else {
[_backgroundLayer setScale:0.36];
}
[self addChild:_backgroundLayer];
SKTexture *backgroundTexture = [SKTexture textureWithImageNamed:#"levelSelect"];
int textureID = 0;
for (int i = 0; i<3; i++) {
for (int j = 0; j<3; j++) {
SKSpriteNode *background = [SKSpriteNode spriteNodeWithTexture:backgroundTexture];
background.anchorPoint = CGPointZero;
background.position = CGPointMake((background.size.width)*i, (background.size.height)*j);
background.zPosition = 0;
background.name = [NSString stringWithFormat:#"background%d", textureID];
textureID++;
[_backgroundLayer addChild:background];
}
}
[TurtleWorldSubScene displayTurtleWorld:self];
}
return self;
}
- (void)didMoveToView:(SKView *)view {
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanFrom:)];
[[self view] addGestureRecognizer:panGestureRecognizer];
//UITapGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
// [self.view addGestureRecognizer:tapRecognizer];
UIPinchGestureRecognizer *pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(handlePinch:)];
[[self view] addGestureRecognizer:pinchGestureRecognizer];
}
- (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
CGPoint touchLocation = [recognizer locationInView:recognizer.view];
touchLocation = [self convertPointFromView:touchLocation];
SKNode *node = [self nodeAtPoint:touchLocation];
_selectedNode = node;
} else if (recognizer.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [recognizer translationInView:recognizer.view];
translation = CGPointMake(translation.x, -translation.y);
CGPoint initialPosition = CGPointAdd(_backgroundLayer.position, translation);
_backgroundLayer.position = [self boundLayerPos:initialPosition];
[recognizer setTranslation:CGPointZero inView:recognizer.view];
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
float scrollDuration = 0.2;
CGPoint velocity = [recognizer velocityInView:recognizer.view];
CGPoint pos = [_backgroundLayer position];
CGPoint p = CGPointMultiplyScalar(velocity, scrollDuration);
CGPoint newPos = CGPointMake(pos.x + p.x, pos.y - p.y);
newPos = [self boundLayerPos:newPos];
[_backgroundLayer removeAllActions];
SKAction *moveTo = [SKAction moveTo:newPos duration:scrollDuration];
[moveTo setTimingMode:SKActionTimingEaseOut];
[_backgroundLayer runAction:moveTo];
}
}
- (void)handlePinch:(UIPinchGestureRecognizer *) recognizer
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
if(_backgroundLayer.xScale*recognizer.scale < 0.76) {
//SKSpriteNode *backgroundTile = (SKSpriteNode *)[_backgroundLayer childNodeWithName:#"background0"];
[_backgroundLayer setScale:0.76];
} else if(_backgroundLayer.xScale*recognizer.scale > 2) {
[_backgroundLayer setScale:2.0];
} else {
[_backgroundLayer runAction:[SKAction scaleBy:recognizer.scale duration:0]];
}
} else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && IS_WIDESCREEN) {
} else {
if(_backgroundLayer.xScale*recognizer.scale < 0.36) {
[_backgroundLayer setScale:0.36];
} else if(_backgroundLayer.xScale*recognizer.scale > 2) {
[_backgroundLayer setScale:2.0];
} else {
[_backgroundLayer runAction:[SKAction scaleBy:recognizer.scale duration:0]];
}
}
recognizer.scale = 1;
}
- (CGPoint)boundLayerPos:(CGPoint)newPos {
SKSpriteNode *backgroundTile = (SKSpriteNode *)[_backgroundLayer childNodeWithName:#"background0"];
CGPoint retval = newPos;
retval.x = MIN(retval.x, 0);
retval.x = MAX(retval.x, -(backgroundTile.size.width*_backgroundLayer.xScale*3)+self.size.width);
retval.y = MIN(retval.y, 0);
retval.y = MAX(retval.y, -(backgroundTile.size.height*_backgroundLayer.xScale*3)+self.size.height);
return retval;
}
I am trying to get multi touch working so that I can move two sprites at the same time. I followed this tutorial http://www.saturngod.net/detecting-touch-events-in-cocos2d-iphone-ganbaru-games and this is the code I have in ccTouchesBegan:
-(void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *touchArray = [touches allObjects];
// We're going to track the first two touches (i.e. first two fingers)
// Create "UITouch" objects representing each touch
UITouch *fingerOne = [touchArray objectAtIndex:0];
UITouch *fingerTwo = [touchArray objectAtIndex:1];
// Convert each UITouch object to a CGPoint, which has x/y coordinates we can actually use
CGPoint pointOne = [fingerOne locationInView:[fingerOne view]];
CGPoint pointTwo = [fingerTwo locationInView:[fingerTwo view]];
// The touch points are always in "portrait" coordinates
// You will need to convert them if in landscape (which we are)
pointOne = [[CCDirector sharedDirector] convertToGL:pointOne];
pointTwo = [[CCDirector sharedDirector] convertToGL:pointTwo];
if (CGRectContainsPoint(ball.boundingBox, pointOne))
{
//ball.position = ccp(location.x , location.y);
areWeTouchingABall = YES;
//printf("*** ccTouchesBegan (x:%f, y:%f)\n", location.x, location.y);
}
if(CGRectContainsPoint(sp.boundingBox, pointOne)){
areWeTouchingASquare = YES;
// printf("*** ccTouchesBegan (x:%f, y:%f)\n", location.x, location.y);
}
// Only run the following code if there is more than one touch
if ([touchArray count] > 1)
{
if ( CGRectContainsPoint(ball.boundingBox, pointTwo))
{
//ball.position = ccp(location.x , location.y);
areWeTouchingABall = YES;
//printf("*** ccTouchesBegan (x:%f, y:%f)\n", location.x, location.y);
}
if(CGRectContainsPoint(sp.boundingBox, pointTwo)){
areWeTouchingASquare = YES;
// printf("*** ccTouchesBegan (x:%f, y:%f)\n", location.x, location.y);
}
}
}
this is in touchesMoved:
-(void) ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *touchArray = [touches allObjects];
// We're going to track the first two touches (i.e. first two fingers)
// Create "UITouch" objects representing each touch
UITouch *fingerOne = [touchArray objectAtIndex:0];
UITouch *fingerTwo = [touchArray objectAtIndex:1];
// Convert each UITouch object to a CGPoint, which has x/y coordinates we can actually use
CGPoint pointOne = [fingerOne locationInView:[fingerOne view]];
CGPoint pointTwo = [fingerTwo locationInView:[fingerTwo view]];
// The touch points are always in "portrait" coordinates
// You will need to convert them if in landscape (which we are)
pointOne = [[CCDirector sharedDirector] convertToGL:pointOne];
pointTwo = [[CCDirector sharedDirector] convertToGL:pointTwo];
if (areWeTouchingABall == YES) //
{
ball.position = ccp(pointOne.x , pointOne.y);
ball.zOrder = 1;
sp.zOrder = 0;
}
if (areWeTouchingASquare == YES) //
{
sp.position = ccp(pointOne.x , pointOne.y);
sp.zOrder = 1;
ball.zOrder = 0;
}
// Only run the following code if there is more than one touch
if ([touchArray count] > 1)
{
/*if (areWeTouchingABall == YES && CGRectContainsPoint(ball.boundingBox, pointOne)) //
{
ball.position = ccp(pointOne.x , pointOne.y);
ball.zOrder = 1;
sp.zOrder = 0;
}*/
if (areWeTouchingABall == YES && CGRectContainsPoint(ball.boundingBox, pointTwo)) //
{
ball.position = ccp(pointTwo.x , pointTwo.y);
ball.zOrder = 1;
sp.zOrder = 0;
}
/*if (areWeTouchingASquare == YES && CGRectContainsPoint(ball.boundingBox, pointOne)) //
{
sp.position = ccp(pointOne.x , pointOne.y);
sp.zOrder = 1;
ball.zOrder = 0;
}*/
if (areWeTouchingASquare == YES && CGRectContainsPoint(ball.boundingBox, pointTwo)) //
{
sp.position = ccp(pointTwo.x , pointTwo.y);
sp.zOrder = 1;
ball.zOrder = 0;
}
}
}
and this is in touchesEnded:
-(void) ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
areWeTouchingABall = NO;
areWeTouchingASquare = NO;
//printf("*** ccTouchesEnded (x:%f, y:%f)\n", location.x, location.y);
}
Every time I touch anywhere with one finger, I get this error:
"Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'",
When I touch with two fingers, the error does not come up but multi touch does not work correctly ( I cannot drag a sprite with a finger each. The second sprite touched jumps straight to the other finger location so that both sprites are under one finger.)
I made sure to add the code:
[glView setMultipleTouchEnabled:YES];
to my AppDelegate.m file and I have touch enabled in my init method.
How can I fix this issue so that multi-touch works properly and the error is removed?
UITouch *fingerOne = [touchArray objectAtIndex:0];
UITouch *fingerTwo = [touchArray objectAtIndex:1];
There's no guarantee that you'll always receive two touches in a touch event. First test how many touches there are in touchArray using
UITouch *fingerOne = [touchArray objectAtIndex:0];
UITouch *fingerTwo = nil;
if (touchArray.count > 1)
{
fingerTwo = [touchArray objectAtIndex:1];
}
Even then this won't work logically because the second touch in the array may not always correspond to finger #2. I suggest to read up on tracking individual fingers here.
In my efforts to upgrade my application to support IOS7 I found out that UIPageControl doesn't support the UIImageView. They have changed it.
I'm subclassing the UIPageControl in order to put custom circles instead the regular ones (attached an example)
My class is:
- (id)initWithFrame:(CGRect)frame
{
// if the super init was successfull the overide begins.
if ((self = [super initWithFrame:frame]))
{
// allocate two bakground images, one as the active page and the other as the inactive
activeImage = [UIImage imageNamed:#"active_page_image.png"];
inactiveImage = [UIImage imageNamed:#"inactive_page_image.png"];
}
return self;
}
// Update the background images to be placed at the right position
-(void) updateDots
{
for (int i = 0; i < [self.subviews count]; i++)
{
UIImageView* dot = [self.subviews objectAtIndex:i];
if (i == self.currentPage) dot.image = activeImage;
else dot.image = inactiveImage;
}
}
// overide the setCurrentPage
-(void) setCurrentPage:(NSInteger)page
{
[super setCurrentPage:page];
[self updateDots];
}
Now in the IOS7 I got the following error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIView setImage:]: unrecognized selector sent to instance 0xe02ef00'
and after investigating I understood that the following code cause the error:
UIImageView* dot = [self.subviews objectAtIndex:i];
if (i == self.currentPage) dot.image = activeImage;
else dot.image = inactiveImage;
I checked the subviews and saw that it is UIView instead of UIImageView. probably Apple changed something.
Any idea how to fix it?
It looks like they changed the subviews to standard UIViews. I managed to work around it by doing this:
for (int i = 0; i < [self.subviews count]; i++)
{
UIView* dotView = [self.subviews objectAtIndex:i];
UIImageView* dot = nil;
for (UIView* subview in dotView.subviews)
{
if ([subview isKindOfClass:[UIImageView class]])
{
dot = (UIImageView*)subview;
break;
}
}
if (dot == nil)
{
dot = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, dotView.frame.size.width, dotView.frame.size.height)];
[dotView addSubview:dot];
}
if (i == self.currentPage)
{
if(self.activeImage)
dot.image = activeImage;
}
else
{
if (self.inactiveImage)
dot.image = inactiveImage;
}
}
maybe dot isn't kind of UIImageView, so try like this
UIImageView* dot = [self.subviews objectAtIndex:i];
if ([dot isKindOfClass:[UIImageView class]]) {
if (i == self.currentPage)
dot.image = activeImage;
else
dot.image = inactiveImage;
}
I've got a bit cleaner solution:
for (int i = 0; i < [self.subviews count]; i++) {
UIView *dotView = [self.subviews objectAtIndex:i];
if ([dotView isKindOfClass:[UIImageView class]]) {
UIImageView* dot = (UIImageView*)dotView;
dot.frame = CGRectMake(dot.frame.origin.x, dot.frame.origin.y, _activeImage.size.width, _activeImage.size.height);
if (i == self.currentPage)
dot.image = _activeImage;
else
dot.image = _inactiveImage;
}
else {
dotView.frame = CGRectMake(dotView.frame.origin.x, dotView.frame.origin.y, _activeImage.size.width, _activeImage.size.height);
if (i == self.currentPage)
[dotView setBackgroundColor:[UIColor colorWithPatternImage:_activeImage]];
else
[dotView setBackgroundColor:[UIColor colorWithPatternImage:_inactiveImage]];
}
}
The idea is instead of adding subview to UIView for iOS7 just set UIView background image.
Just a little refactor for devgeek's solution to make it a bit more compact
for (int i = 0; i < [self.subviews count]; i++) {
UIImage *customDotImage = (i == self.currentPage) ? _activeDot : _inactiveDot;
UIView *dotView = [self.subviews objectAtIndex:i];
dotView.frame = CGRectMake(dotView.frame.origin.x, dotView.frame.origin.y, customDotImage.size.width, customDotImage.size.height);
if ([dotView isKindOfClass:[UIImageView class]]) { // in iOS 6, UIPageControl contains UIImageViews
((UIImageView *)dotView).image = customDotImage;
}
else { // in iOS 7, UIPageControl contains normal UIViews
dotView.backgroundColor = [UIColor colorWithPatternImage:customDotImage];
}
}
Just override layoutSubviews in your subclass of UIPageControl
- (void) layoutSubviews
{
[super layoutSubviews];
for (UIView* dot in self.subviews)
{
CGRect f = dot.frame;
//sets all the dots to be 5x5
f.size = CGSizeMake(5, 5);
//need to reposition vertically as the dots get repositioned when selected
f.origin.y = CGRectGetMidY(self.bounds) - CGRectGetHeight(f)/2;
dot.frame = f;
//update the cornerRadius to be sure that they are perfect circles
dot.layer.cornerRadius = CGRectGetWidth(f)/2;
}
}
I am trying to create an NSMatrix of NSButtonCells where between zero and four buttons can be selected (toggled on). I have tried the following (test) code, but am not sure how I can provide the functionality I require. Perhaps it's not possible with NSMatrix and I need to look at an alternative control, or create my own?
#interface MatrixView : NSView
{
NSScrollView *_scrollView;
NSMatrix *_matrixView;
}
#end
#implementation MatrixView
- (id)initWithFrame:(NSRect)frameRect
{
NSLog(#"initWithFrame. frameRect=%#", NSStringFromRect(frameRect));
self = [super initWithFrame:frameRect];
if (self != nil)
{
_scrollView = [[NSScrollView alloc] initWithFrame:NSMakeRect(0, 0, frameRect.size.width, frameRect.size.height)];
[_scrollView setBorderType:NSNoBorder];
[_scrollView setHasVerticalScroller:YES];
[_scrollView setHasHorizontalScroller:NO];
[_scrollView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
NSSize contentSize = [_scrollView contentSize];
contentSize.height = 300;
// Make it 3 x however-many-buttons-will-fit-the-height
CGFloat gap = 8.0;
CGFloat width = (contentSize.width / 3.0) - (gap * 2.0);
NSUInteger rows = (contentSize.height / (width + gap));
NSLog(#"width=%f, rows=%lu", width, rows);
NSButtonCell *prototype = [[NSButtonCell alloc] init];
[prototype setTitle:#"Hello"];
[prototype setButtonType:NSToggleButton];
[prototype setShowsStateBy:NSChangeGrayCellMask];
_matrixView = [[NSMatrix alloc] initWithFrame:NSMakeRect(0, 0, contentSize.width, contentSize.height)
mode:NSListModeMatrix
prototype:prototype
numberOfRows:rows
numberOfColumns:3];
[_matrixView setCellSize:NSMakeSize(width, width)];
[_matrixView setIntercellSpacing:NSMakeSize(gap, gap)];
[_matrixView setAllowsEmptySelection:YES];
[_matrixView sizeToCells];
[_scrollView setDocumentView:_matrixView];
[self addSubview:_scrollView];
[self setAutoresizesSubviews:YES];
[prototype release];
}
return self;
}
...
I got this to work with the following subclass of NSMatrix. I added one property, onCount, to keep track of how many buttons were in the on state:
#implementation RDMatrix
#synthesize onCount;
-(id) initWithParentView:(NSView *) cv {
NSButtonCell *theCell = [[NSButtonCell alloc ]init];
theCell.bezelStyle = NSSmallSquareBezelStyle;
theCell.buttonType = NSPushOnPushOffButton;
theCell.title = #"";
if (self = [super initWithFrame:NSMakeRect(200,150,1,1) mode:2 prototype:theCell numberOfRows:4 numberOfColumns:4]){
[self setSelectionByRect:FALSE];
[self setCellSize:NSMakeSize(40,40)];
[self sizeToCells];
self.target = self;
self.action = #selector(buttonClick:);
self.drawsBackground = FALSE;
self.autoresizingMask = 8;
self.allowsEmptySelection = TRUE;
self.mode = NSHighlightModeMatrix;
self.onCount = 0;
[cv addSubview:self];
return self;
}
return nil;
}
-(IBAction)buttonClick:(NSMatrix *)sender {
NSUInteger onOrOff =[sender.selectedCells.lastObject state];
if (onOrOff) {
self.onCount += 1;
}else{
self.onCount -= 1;
}
NSLog(#"%ld",self.onCount);
if (self.onCount == 5) {
[sender.selectedCells.lastObject setState:0];
self.onCount -= 1;
}
}
When you try to select the 5th button it will flash on, but then go off. This could be a problem depending on how you are using the state of these buttons. I just logged them with this method:
-(IBAction)checkMatrix:(id)sender {
NSIndexSet *indxs = [self.mat.cells indexesOfObjectsPassingTest:^BOOL(NSButtonCell *cell, NSUInteger idx, BOOL *stop) {
return cell.state == NSOnState;
}];
NSLog(#"%#",indxs);
}
After Edit: I didn't like the way my first method flashed the button on briefly before turning it off again when you try to click the 5th button. I found what I think is a better solution that involves overriding mouseDown in the matrix subclass (if you want to try this, you should delete the setAction and setTarget statements and delete the buttonClick method):
-(void)mouseDown:(NSEvent *) event {
NSPoint matPoint = [self convertPoint:event.locationInWindow fromView:nil];
NSInteger row;
NSInteger column;
[self getRow:&row column:&column forPoint:matPoint];
NSButtonCell *cell = [self cellAtRow:row column:column];
if (self.onCount < 4 && cell.state == NSOffState) {
cell.state = NSOnState;
self.onCount += 1;
}else if (cell.state == NSOnState) {
cell.state = NSOffState;
self.onCount -= 1;
}
}
I am making a day view calendar just like the native iPhone calendar. I am trying to position the tiles the same as in the native calendar, side by side, if they are the same size and same time.
However, I can only figure out how to do it to 2 tiles and not multiple tiles. In the attached image I have 4 tiles. One that expands slightly into the other 3. I then have the first tile on the far left and the second tile just after the first one. Now I need to figure out how to add the additional tiles?
How would I do this for more than 2 tiles?
About the image: If you can't see it the 3rd tile is ontop of the 2nd tile (you can see it is a bit darker since they are on top of each other.
- (void)layoutSubviews
{
// Set the main
for (UIView *view in self.subviews) {
APCalendarDayTile *tile = (APCalendarDayTile *)view;
CGFloat startPos = [APCalendarCurrentDayView yAxisForTime:[APCalendarCurrentDayView minutesToTime:tile.appointment.startDate]];
CGFloat endPos = [APCalendarCurrentDayView yAxisForTime:[APCalendarCurrentDayView minutesToTime:tile.appointment.endDate]];
tile.frame = CGRectMake(kLeftSideBuffer, startPos, (self.bounds.size.width - kLeftSideBuffer) , endPos - startPos);
tile.backgroundColor = [UIColor colorWithHexString:tile.appointment.appointmentColor];
}
for (UIView *view in self.subviews) {
APCalendarDayTile *tile = (APCalendarDayTile *)view;
if ([self viewIntersectsWithAnotherView:tile]) {
}
}
}
- (BOOL)viewIntersectsWithAnotherView:(UIView*)selectedView{
NSArray *subViewsInView=[self subviews];// I assume self is a subclass
// of UIViewController but the view can be
//any UIView that'd act as a container
//for all other views.
for (UIView *theView in subViewsInView){
if (![selectedView isEqual:theView]) {
if(CGRectIntersectsRect(selectedView.frame, theView.frame)) {
if ((selectedView.frame.origin.y == theView.frame.origin.y) && (selectedView.frame.size.height == theView.frame.size.height)) {
if (theView.frame.size.width == self.bounds.size.width - kLeftSideBuffer) {
theView.frame = CGRectMake(theView.frame.origin.x, selectedView.frame.origin.y, theView.frame.size.width / 2, selectedView.frame.size.height);
}
selectedView.frame = CGRectMake(theView.frame.origin.x + theView.frame.size.width, selectedView.frame.origin.y, theView.frame.size.width, selectedView.frame.size.height);
return YES;
}
}
}
}
return NO;
}
It appears that your test
if ((selectedView.frame.origin.y == theView.frame.origin.y) && (selectedView.frame.size.height == theView.frame.size.height))
Is only applied to views of equal y origin and height. I would solve this problem using the following pseudo code:
initialize an empty arranged subviews array
initialize a nil previous subview
for every subview
if the subview intersects with the previous subview
ensure the subview and the previous subview are added to the arranged subviews array
else if the arranged subviews array is not empty
arrange the subviews in the array across the width of their superview
empty the arranged subview array
Ok,
I sorta took SaltyMule's approach however, his pseudo code didn't make sense in the if / else.
- (void)layoutSubviews
{
// Set the main
for (UIView *view in self.subviews) {
APCalendarDayTile *tile = (APCalendarDayTile *)view;
CGFloat startPos = [APCalendarCurrentDayView yAxisForTime:[APCalendarCurrentDayView minutesToTime:tile.appointment.startDate]];
CGFloat endPos = [APCalendarCurrentDayView yAxisForTime:[APCalendarCurrentDayView minutesToTime:tile.appointment.endDate]];
tile.frame = CGRectMake(kLeftSideBuffer, startPos, (self.bounds.size.width - kLeftSideBuffer) , endPos - startPos);
tile.backgroundColor = [UIColor colorWithHexString:tile.appointment.appointmentColor];
}
[sameTimeAppointments removeAllObjects];
for (UIView *view in self.subviews) {
APCalendarDayTile *tile = (APCalendarDayTile *)view;
if ([self viewIntersectsWithAnotherView:tile]) {
if ([sameTimeAppointments objectForKey:[NSString stringWithFormat:#"%f", tile.frame.origin.y]] != nil) {
NSMutableArray *tempArray = [[sameTimeAppointments objectForKey:[NSString stringWithFormat:#"%f", tile.frame.origin.y]] mutableCopy];
[tempArray addObject:tile];
[sameTimeAppointments setValue:tempArray forKey:[NSString stringWithFormat:#"%f", tile.frame.origin.y]];
} else {
[sameTimeAppointments setValue:[NSMutableArray arrayWithObject:tile] forKey:[NSString stringWithFormat:#"%f", tile.frame.origin.y]];
}
}
}
for (NSString *currentDict in sameTimeAppointments) {
NSArray *currentAppointments = [sameTimeAppointments objectForKey:currentDict];
float tileWidth = ((self.frame.size.width - kLeftSideBuffer) / [currentAppointments count]);
for (int i = 0; i < [currentAppointments count]; i++) {
APCalendarDayTile *tile = [currentAppointments objectAtIndex:i];
float xPos = 0.0 + kLeftSideBuffer;
if (i != 0) {
xPos = (((APCalendarDayTile *)[currentAppointments objectAtIndex:i - 1]).frame.origin.x + tileWidth);
}
tile.frame = CGRectMake(xPos, tile.frame.origin.y, tileWidth, tile.frame.size.height);
[self bringSubviewToFront:tile];
}
}
}
- (BOOL)viewIntersectsWithAnotherView:(UIView*)selectedView{
NSArray *subViewsInView=[self subviews];// I assume self is a subclass
// of UIViewController but the view can be
//any UIView that'd act as a container
//for all other views.
for (UIView *theView in subViewsInView){
if (![selectedView isEqual:theView]) {
if(CGRectIntersectsRect(selectedView.frame, theView.frame)) {
if ((selectedView.frame.origin.y == theView.frame.origin.y) && (selectedView.frame.size.height == theView.frame.size.height)) {
return YES;
}
}
}
}
return NO;
}