Variables not updating while using CADisplayLink - variables

I cannot get the variables to change when I update them. The method "reCenterYaw" fires when I touch the button and the variable "self.hasYawCenterPointBeenSet" gets updated within the method but when "updateCycle" method comes back around the variable is not showing it was updated. updateCycle is my CADisplaylink driven method. I've tried updating the variables to atomic, referenced them as their instance variables with "_" and with "self." Not sure what is going on.
#import "GameViewController.h"
#import <CoreMotion/CoreMotion.h>
#import <SpriteKit/SpriteKit.h>
#import "HUDOverlay.h"
#interface GameViewController ()
#property (strong, nonatomic) CMMotionManager *motionManager;
#property (strong, nonatomic) CMAttitude *currentAttitude;
#property (strong, nonatomic) SCNNode *cameraNode;
#property (strong, nonatomic) SCNScene *scene;
#property (nonatomic) float roll;
#property (nonatomic) float yaw;
#property (nonatomic) float pitch;
#property (atomic) float yawCenterPoint;
#property (atomic) BOOL hasYawCenterPointBeenSet;
#end
GameViewController* sharedGameViewController = nil;
#implementation GameViewController
+(GameViewController*)sharedController{
if (sharedGameViewController == nil) {
sharedGameViewController = [[GameViewController alloc] init];
}
return sharedGameViewController;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// create a new scene
//SCNScene *scene = [SCNScene sceneNamed:#"art.scnassets/ship.dae"];
_scene = [SCNScene new];
self.hasYawCenterPointBeenSet = NO;
// create and add a camera to the scene
_cameraNode = [SCNNode node];
_cameraNode.camera = [SCNCamera camera];
[_scene.rootNode addChildNode:_cameraNode];
// place the camera
_cameraNode.position = SCNVector3Make(0, 0, 0);
// create and add a light to the scene
SCNNode *lightNode = [SCNNode node];
lightNode.light = [SCNLight light];
lightNode.light.type = SCNLightTypeOmni;
lightNode.position = SCNVector3Make(0, 10, 10);
[_scene.rootNode addChildNode:lightNode];
// create and add an ambient light to the scene
SCNNode *ambientLightNode = [SCNNode node];
ambientLightNode.light = [SCNLight light];
ambientLightNode.light.type = SCNLightTypeAmbient;
ambientLightNode.light.color = [UIColor whiteColor];
[_scene.rootNode addChildNode:ambientLightNode];
// retrieve the SCNView
SCNView *scnView = (SCNView *)self.view;
scnView.delegate = self;
scnView.playing = YES;
// set the scene to the view
scnView.scene = _scene;
scnView.overlaySKScene = [HUDOverlay sceneWithSize:scnView.frame.size];
// allows the user to manipulate the camera
scnView.allowsCameraControl = NO;
// show statistics such as fps and timing information
scnView.showsStatistics = YES;
// configure the view
scnView.backgroundColor = [UIColor blueColor];
// // add a tap gesture recognizer
// UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
// NSMutableArray *gestureRecognizers = [NSMutableArray array];
// [gestureRecognizers addObject:tapGesture];
// [gestureRecognizers addObjectsFromArray:scnView.gestureRecognizers];
// scnView.gestureRecognizers = gestureRecognizers;
SCNNode *sampleNode = [SCNNode new];
SCNPlane *samplePlane = [SCNPlane planeWithWidth:3 height:5];
SCNMaterial *angryFrontMaterial = [SCNMaterial material];
angryFrontMaterial.diffuse.contents = [UIImage imageNamed:#"angryfront110.png"];
sampleNode.geometry = samplePlane;
sampleNode.geometry.firstMaterial = angryFrontMaterial;
sampleNode.position = SCNVector3Make(0, 0, -10);
[_scene.rootNode addChildNode:sampleNode];
// SCNAction *moveToCamera = [SCNAction moveTo:SCNVector3Make(0, 0, -10) duration:10];
// [sampleNode runAction:moveToCamera];
[self setupRotationGrid];
[self setupMotionCapture];
displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(updateCycle)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
//This is the update loop for the sceneView. When the view updates per frame this is where the logic goes
-(void)renderer:(id<SCNSceneRenderer>)aRenderer updateAtTime:(NSTimeInterval)time{
}
-(void)updateCycle{
_currentAttitude = _motionManager.deviceMotion.attitude;
_roll = _currentAttitude.roll + (.5 * M_PI);
_yaw = _currentAttitude.yaw;
_pitch = _currentAttitude.pitch;
_cameraNode.eulerAngles = SCNVector3Make(-_roll, _yaw, _pitch);
//NSLog(#"yawCenterPoint = %d", _hasYawCenterPointBeenSet);
if (self.hasYawCenterPointBeenSet == NO) {
_yawCenterPoint = _yaw;
self.hasYawCenterPointBeenSet = YES;
NSLog(#"Yawcenter point set to %f", _yawCenterPoint);
}
//NSLog(#"Roll: %f Yaw: %f Pitch: %f", _roll, _yaw, _pitch);
}
-(void)setupRotationGrid {
double radius = 30;
double numberOfItems = 8;
SCNGeometry *geometry = [SCNBox boxWithWidth:4.5 height:8 length:3.25 chamferRadius:0.5];
double x = 0.0;
double z = radius;
double theta = (M_PI)/(numberOfItems/2);
double incrementalY = (M_PI)/(numberOfItems*2);
SCNNode *nodeCollection = [SCNNode node];
nodeCollection.position = SCNVector3Make(0, 4, 0);
for (int index = 1; index <= numberOfItems; index++) {
x = radius * sin(index*theta);
z = radius * cos(index*theta);
SCNNode *node = [SCNNode node];
node.geometry = geometry;
node.position = SCNVector3Make(x, 0, z);
float rotation = incrementalY * index;
node.rotation = SCNVector4Make(0, 1, 0, rotation);
[nodeCollection addChildNode:node];
}
[_scene.rootNode addChildNode:nodeCollection];
}
- (void) handleTap:(UIGestureRecognizer*)gestureRecognize
{
// retrieve the SCNView
SCNView *scnView = (SCNView *)self.view;
// check what nodes are tapped
CGPoint p = [gestureRecognize locationInView:scnView];
NSArray *hitResults = [scnView hitTest:p options:nil];
// check that we clicked on at least one object
if([hitResults count] > 0){
// retrieved the first clicked object
SCNHitTestResult *result = [hitResults objectAtIndex:0];
// get its material
SCNMaterial *material = result.node.geometry.firstMaterial;
// highlight it
[SCNTransaction begin];
[SCNTransaction setAnimationDuration:0.5];
// on completion - unhighlight
[SCNTransaction setCompletionBlock:^{
[SCNTransaction begin];
[SCNTransaction setAnimationDuration:0.5];
material.emission.contents = [UIColor blackColor];
[SCNTransaction commit];
}];
material.emission.contents = [UIColor redColor];
[SCNTransaction commit];
}
}
-(void)reCenterYaw{
self.hasYawCenterPointBeenSet = NO;
NSLog(#"Tapped recenter");
}
-(void)setupMotionCapture{
_motionManager = [[CMMotionManager alloc] init];
_motionManager.deviceMotionUpdateInterval = 1.0/60.0;
if (_motionManager.isDeviceMotionAvailable) {
[_motionManager startDeviceMotionUpdates];
_currentAttitude = _motionManager.deviceMotion.attitude;
}
// if (!_hasYawCenterPointBeenSet) {
// _yawCenterPoint = _currentAttitude.yaw;
// _hasYawCenterPointBeenSet = YES;
// }
}
- (BOOL)shouldAutorotate
{
return YES;
}
- (BOOL)prefersStatusBarHidden {
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
return UIInterfaceOrientationMaskLandscape;
} else {
return UIInterfaceOrientationMaskAll;
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#end

I figured it out. I had created a singleton so that I could reference the view controller from the sprite kit hud class. The problem is that I load the view controller from the storyboard and then later, from the hud class init, I instantiate a new view controller because the "sharedViewController" never got initiated by the storyboard load. So, I just created a view controller property on the hud class and set it to self from the view controller did load code.

Related

SceneKit – Playing DAE and USDZ animation using Objective-C

I want to create walking man animation in SceneKit.
I'm exporting animated .dae files from 3DSMax + OpenCollada , I also use ConvertToXcodeCollada to combine all animations in one.
How i get animation:
SCNScene *humanScene = [SCNScene sceneNamed:#"art.scnassets/myScene.DAE"];
CAAnimation *Animation = [[humanScene rootNode] animationForKey:#"myScene-1"];
I also try to get animation from "SCNSceneSource"
How i add animation:
SCNNode *humanNode = [humanScene.rootNode childNodeWithName:#"myScene-1" recursively:YES];
[humanNode addAnimation:walkingAnimation forKey:#"myScene-1"];
or:
SCNNode* humanNode = [SCNNode new];
for(SCNNode* node in humanScene.rootNode.childNodes){
[humanNode addChildNode:node];
}
[humanNode addAnimation:walkingAnimation forKey:#"myScene-1"];
My object "walkingAnimation" is "CAAnimationGroup".
But it doesn't animate in application.
I can see animation only in Xcode sceneKit editor.
example of my .DAE file
Try the following code with your model. It works. I used Xcode 13.2.1 in macOS 12.1.
At first fill viewDidLoad method:
#import "GameViewController.h"
#implementation GameViewController
SCNView *sceneView;
bool notRunningSwitch;
NSMutableDictionary<NSString*, CAAnimation*> *animations;
SCNNode *node;
- (void)viewDidLoad
{
[super viewDidLoad];
sceneView = (SCNView *)self.view;
notRunningSwitch = YES;
animations = #{}.mutableCopy;
node = [SCNNode node];
SCNScene *scene = [SCNScene scene];
sceneView.scene = scene;
sceneView.autoenablesDefaultLighting = YES;
sceneView.allowsCameraControl = YES;
[self anime];
}
Then anime and loadAnime methods:
- (void)anime
{
SCNScene *standStillScene = [SCNScene sceneNamed:#"art.scnassets/Idle"];
for (SCNNode *childNode in standStillScene.rootNode.childNodes)
{
[node addChildNode:childNode];
}
node.scale = SCNVector3Make(0.1, 0.1, 0.1);
node.position = SCNVector3Make(0, 0,-2.5);
[sceneView.scene.rootNode addChildNode: node];
[self loadAnime:#"running" inScene:#"art.scnassets/Running" withID:#"Running-1"];
}
- (void)loadAnime:(NSString*)withKey inScene:(NSString*)scene withID:(NSString*)id
{
NSURL *url = [NSBundle.mainBundle URLForResource:scene withExtension:#"usdz"];
SCNSceneSource *source = [SCNSceneSource sceneSourceWithURL:url options:Nil];
CAAnimation *charAnimation = [source entryWithIdentifier:id
withClass:CAAnimation.self];
charAnimation.repeatCount = 1;
charAnimation.fadeInDuration = 1;
charAnimation.fadeOutDuration = 1;
[animations setValue:charAnimation forKey:withKey];
}
And at last touchesBegan:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CGPoint point = [touches.allObjects.firstObject locationInView: sceneView];
NSDictionary<SCNHitTestOption, id> *options = #{ SCNHitTestBoundingBoxOnlyKey: #(YES) };
NSArray<SCNHitTestResult *> *hitTestResults = [sceneView hitTest: point options: options];
if (notRunningSwitch == YES) {
[sceneView.scene.rootNode addAnimation:animations[#"running"] forKey:#"running"];
} else {
[sceneView.scene.rootNode removeAnimationForKey:#"running" blendOutDuration:1.0];
}
notRunningSwitch = !notRunningSwitch;
NSLog(#"%#", hitTestResults.firstObject.node.name);
NSLog(#"%f", hitTestResults.firstObject.modelTransform.m43);
}
#end

adding CCPhysicsSprite to CCLayer cocos2d

I'm trying to reorganize HelloWorld project in cocos2d to our needs.
The thing I did - made a class, which is inherent from CCPhysicsSprite and wanted to add it to CCLayer (HelloWorldLayer). But something goes wrong. According to debugger my instance is created, but I can't see it in the iOS emulator. Need your help and explanations.
HelloWorldLayer.h
#interface HelloWorldLayer : CCLayer <GKAchievementViewControllerDelegate, GKLeaderboardViewControllerDelegate>
{
CCTexture2D *spriteTexture_; // weak ref
b2World* world_; // strong ref
GLESDebugDraw *m_debugDraw; // strong ref
}
HelloWorldLayer.mm (only changed by me functions:)
-(id) init
{
if( (self=[super init])) {
// enable events
self.touchEnabled = YES;
self.accelerometerEnabled = YES;
CGSize s = [CCDirector sharedDirector].winSize;
// init physics
[self initPhysics];
// create reset button
//[self createMenu];
//Set up sprite
//#if 1
// // Use batch node. Faster
// CCSpriteBatchNode *parent = [CCSpriteBatchNode batchNodeWithFile:#"blocks.png" capacity:100];
// spriteTexture_ = [parent texture];
//#else
// // doesn't use batch node. Slower
// spriteTexture_ = [[CCTextureCache sharedTextureCache] addImage:#"blocks.png"];
// CCNode *parent = [CCNode node];
//#endif
// [self addChild:parent z:0 tag:kTagParentNode];
//
//
// [self addNewSpriteAtPosition:ccp(s.width/2, s.height/2)];
CCLabelTTF *label = [CCLabelTTF labelWithString:#"Tap screen" fontName:#"Marker Felt" fontSize:32];
[self addChild:label z:0];
[label setColor:ccc3(0,0,255)];
label.position = ccp( s.width/2, s.height-50);
[self scheduleUpdate];
}
return self;
}
-(void) addNewSpriteAtPosition:(CGPoint)p
{
CCLOG(#"Add sprite %0.2f x %02.f",p.x,p.y);
if([self getChildByTag:kTagParentNode] == nil)
{
BloodRobotUnit *unit = [[BloodRobotUnit alloc] initWithOwner:world_ at:p];
[self addChild:unit z:0 tag:kTagParentNode];
}
}
And Creating unit: (header and mm file:)
#interface BloodRobotUnit : CCPhysicsSprite
{
b2Body *body_;
b2World *owner_;
}
-(id) initWithOwner:(b2World*)owner at:(CGPoint)pt;
mm:
-(id) initWithOwner:(b2World*)owner at:(CGPoint)pt
{
if(self = [super initWithFile:#"blocks.png" rect:CGRectMake(0, 0, 32, 32)])
{
owner_ = owner;
//create body at position
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(pt.x/PTM_RATIO, pt.y/PTM_RATIO);
body_ = owner->CreateBody(&bodyDef);
// Define another box shape for our dynamic body.
b2PolygonShape dynamicBox;
dynamicBox.SetAsBox(.5f, .5f);//These are mid points for our 1m box
// Define the dynamic body fixture.
b2FixtureDef fixtureDef;
fixtureDef.shape = &dynamicBox;
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.3f;
body_->CreateFixture(&fixtureDef);
[self setB2Body: body_];
[self setPosition:pt];
return (self);
}
return nil;
}
Where is my mistake? Any help will be very appreciated
The trick was in setting position to self
Debugger showed that my texture was at position inf:inf
Code change to make everything work is the following:
in mm file of creating a CCPhysicsSprite iheriter do the following:
[self setB2Body: body_];
self.PTMRatio = PTM_RATIO;
//[self setPosition:CGPointMake(pt.x/PTM_RATIO, pt.y/PTM_RATIO)];
That is - you need to set only body position and set PTMRatio (thanx to #giorashc). Setting sprite texture is not necessary.

iOS animations using CADisplayLink

I'm using animation and timing with CADisplayLink. When the ball touches a big stone it is removed immediately. How can I animate the removal of the stone (block) like exploding to small stones? Here's the code I currently have:
- (void)checkCollisionWithBlocks
{
for (BlockView *bv in blocks) {
if (CGRectIntersectsRect(bv.frame, ballRect)) {
ballVelocity.y = -ballVelocity.y;
[blocks removeObject:bv];
[bv removeFromSuperview];
break;
}
}
}
CADisplayLink is really a bit... much for what you're planning (it's for synchronizing screen drawing updates to a custom animation). Here's my solution: set some things on fire! While it's not necessarily an explosion, this will cause your block to belch some blue fire (you can change the color by modifying self.fireColor).
- (void)checkCollisionWithBlocks
{
for (BlockView *bv in blocks) {
if (CGRectIntersectsRect(bv.frame, ballRect)) {
ballVelocity.y = -ballVelocity.y;
[blocks removeObject:bv];
[bv burpFire]
[bv removeFromSuperview];
break;
}
}
}
#interface BlockView : UIView
#property (nonatomic, strong) CALayer *rootLayer;
#property (nonatomic, strong) CAEmitterLayer *fireEmitter;
#property (nonatomic, strong) CAEmitterLayer *smokeEmitter;
#property (nonatomic, strong) UIColor *fireColor;
#property (nonatomic, strong) UIColor *smokeColor;
-(void)burpFire;
#end
#implementation BlockView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.rootLayer = [CALayer layer];
//Set the root layer's background color to black
self.rootLayer.backgroundColor = [UIColor blackColor].CGColor;
//Create the fire emitter layer
self.fireEmitter = [CAEmitterLayer layer];
self.fireEmitter.emitterPosition = CGPointMake(0, CGRectGetMaxY(frame) + 50);
self.fireEmitter.emitterMode = kCAEmitterLayerOutline;
self.fireEmitter.emitterShape = kCAEmitterLayerLine;
self.fireEmitter.renderMode = kCAEmitterLayerAdditive;
self.fireEmitter.emitterSize = CGSizeMake(0, 0);
//Create the smoke emitter layer
self.smokeEmitter = [CAEmitterLayer layer];
self.smokeEmitter.emitterPosition = CGPointMake(0, CGRectGetMaxY(frame) + 50);
self.smokeEmitter.emitterMode = kCAEmitterLayerPoints;
//Create the fire emitter cell
CAEmitterCell* fire = [CAEmitterCell emitterCell];
fire.emissionLongitude = M_PI;
fire.birthRate = 0;
fire.velocity = -80;
fire.velocityRange = -30;
fire.emissionRange = -1.1;
fire.yAcceleration = -200;
fire.scaleSpeed = 0.3;
self.fireColor = [UIColor colorWithRed:0.425 green:0.779 blue:0.800 alpha:0.100];
fire.color = self.fireColor.CGColor;
fire.contents = (id)[self CGImageNamed:#"fire.png"];
//Name the cell so that it can be animated later using keypaths
[fire setName:#"fire"];
//Add the fire emitter cell to the fire emitter layer
self.fireEmitter.emitterCells = [NSArray arrayWithObject:fire] ;
//Create the smoke emitter cell
CAEmitterCell* smoke = [CAEmitterCell emitterCell];
smoke.birthRate = 11;
smoke.emissionLongitude = M_PI / 2;
smoke.lifetime = 0;
smoke.velocity = -40;
smoke.velocityRange = 20;
smoke.emissionRange = M_PI / 4;
smoke.spin = 1;
smoke.spinRange = 6;
smoke.yAcceleration = 160;
smoke.contents = (id) [self CGImageNamed:#"smoke.png"];
smoke.scale = 0.1;
smoke.alphaSpeed = -0.12;
smoke.scaleSpeed = 0.7;
//Name the cell so that it can be animated later using keypaths
[smoke setName:#"smoke"];
//Add the smoke emitter cell to the smoke emitter layer
self.smokeEmitter.emitterCells = [NSArray arrayWithObject:smoke];
//Add the two emitter layers to the root layer
[self.layer addSublayer:self.smokeEmitter];
[self.layer addSublayer:self.fireEmitter];
[self burpFire];
}
return self;
}
//Return a CGImageRef from the specified image file in the app's bundle
-(CGImageRef)CGImageNamed:(NSString*)name {
NSString *url = [[NSBundle mainBundle] pathForResource:name ofType:nil];
return [UIImage imageWithContentsOfFile:url].CGImage;
}
-(void)burpFire {
float gas = 80.0 / 100.0;
//Update the fire properties
[self.fireEmitter setValue:[NSNumber numberWithInt:(gas * 1000)] forKeyPath:#"emitterCells.fire.birthRate"];
[self.fireEmitter setValue:[NSNumber numberWithFloat:gas] forKeyPath:#"emitterCells.fire.lifetime"];
[self.fireEmitter setValue:[NSNumber numberWithFloat:(gas * 0.35)] forKeyPath:#"emitterCells.fire.lifetimeRange"];
self.fireEmitter.emitterSize = CGSizeMake(100 * gas, 0);
//Update the smoke properites
[self.smokeEmitter setValue:[NSNumber numberWithInt:gas * 4] forKeyPath:#"emitterCells.smoke.lifetime"];
self.smokeColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:gas * 0.3];
[self.smokeEmitter setValue:(__bridge id)self.smokeColor.CGColor forKeyPath:#"emitterCells.smoke.color"];
[self setNeedsDisplay];
[self performSelector:#selector(burpFinished) withObject:nil afterDelay:1.5];
}
-(void)burpFinished {
float gas = 0.0 / 100.0;
//Update the fire properties
[self.fireEmitter setValue:[NSNumber numberWithInt:(gas * 1000)] forKeyPath:#"emitterCells.fire.birthRate"];
[self.fireEmitter setValue:[NSNumber numberWithFloat:gas] forKeyPath:#"emitterCells.fire.lifetime"];
[self.fireEmitter setValue:[NSNumber numberWithFloat:(gas * 0.35)] forKeyPath:#"emitterCells.fire.lifetimeRange"];
self.fireEmitter.emitterSize = CGSizeMake(100 * gas, 0);
//Update the smoke properites
[self.smokeEmitter setValue:[NSNumber numberWithInt:gas * 4] forKeyPath:#"emitterCells.smoke.lifetime"];
self.smokeColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:gas * 0.3];
[self.smokeEmitter setValue:(__bridge id)self.smokeColor.CGColor forKeyPath:#"emitterCells.smoke.color"];
}
The CAEmitterLayers last for quite a while, so if you want the fire to shut off a little faster, play around with the timing of the stop selector. If you want smaller or larger fire, or fireballs that stick around longer, play around with the properties in the init method, either way you're gonna get a cool effect.
All colors must be strong references to UIColor, else Core Animation will freak out and throw a C++ temper tantrum in your debugger... not pleasant.

NSMatrix with multiple toggle buttons?

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;
}
}

MKMapView overlay does not display on map

My app gets the points for a route from google. The response also contains the bounding rect. My app creates the rect and shows the map correctly and I add the overlay. The
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)
overlay is called and I construct a MKOverlayPathView from the points. I have verified that the points are within the bounding rect. However, the route overlay does not draw on the displayed map.
I have checked everything I can think of with no joy, any suggestions would be greatly appreciated.
I don't know yet why the overlay isn't showing but try the following code in a fresh, new project.
In my test, I added the map view control to the xib and then connected the IBOutlet and the map view's delegate outlet to File's Owner. Creating the map view in code will also work (just don't also add it to the xib and be sure to set the delegate property).
This is my test TRTrip class:
//TRTrip.h...
#interface TRTrip : NSObject<MKOverlay>
#property (nonatomic, readonly) MKMapRect boundingMapRect;
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
#end
//TRTrip.m...
#implementation TRTrip
#synthesize boundingMapRect;
#synthesize coordinate;
-(MKMapRect)boundingMapRect {
return MKMapRectWorld;
}
-(CLLocationCoordinate2D)coordinate {
return CLLocationCoordinate2DMake(42.3507,-71.0608);
}
#end
In the view controller's viewDidLoad, I add an MKPointAnnotation, MKCircle, TRTrip, and an MKPolyline to the map:
- (void)viewDidLoad
{
[super viewDidLoad];
CLLocationCoordinate2D bostonCoord = CLLocationCoordinate2DMake(42.3507,-71.0608);
//center map on Boston...
mMapView.region = MKCoordinateRegionMakeWithDistance(bostonCoord, 30000, 30000);
//add boston annotation...
MKPointAnnotation *pa = [[MKPointAnnotation alloc] init];
pa.coordinate = bostonCoord;
pa.title = #"Boston";
[mMapView addAnnotation:pa];
[pa release];
//add MKCircle overlay...
MKCircle *circle = [MKCircle circleWithCenterCoordinate:bostonCoord radius:10000];
[mMapView addOverlay:circle];
//add TRTrip overlay...
TRTrip *trt = [[TRTrip alloc] init];
[mMapView addOverlay:trt];
[trt release];
//NOTE:
//Using an MKPolyline and MKPolylineView is probably easier than
//manually drawing lines using MKOverlayPathView.
//add MKPolyline overlay...
int numberOfRouteCoords = 3;
CLLocationCoordinate2D *routeCoords = malloc(numberOfRouteCoords * sizeof(CLLocationCoordinate2D));
routeCoords[0] = CLLocationCoordinate2DMake(42.34, -71.1);
routeCoords[1] = CLLocationCoordinate2DMake(42.25, -71.05);
routeCoords[2] = CLLocationCoordinate2DMake(42.3, -71.02);
MKPolyline *pl = [MKPolyline polylineWithCoordinates:routeCoords count:numberOfRouteCoords];
[mMapView addOverlay:pl];
free(routeCoords);
}
and this is the viewForOverlay method:
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay
{
if ([overlay isKindOfClass:[MKCircle class]])
{
MKCircleView *cv = [[[MKCircleView alloc] initWithCircle:overlay] autorelease];
cv.fillColor = [UIColor greenColor];
cv.strokeColor = [UIColor blueColor];
cv.alpha = 0.5;
return cv;
}
if ([overlay isKindOfClass:[TRTrip class]])
{
MKOverlayPathView *opv = [[[MKOverlayPathView alloc] initWithOverlay:overlay] autorelease];
opv.strokeColor = [UIColor redColor];
opv.lineWidth = 3;
CGMutablePathRef myPath = CGPathCreateMutable();
MKMapPoint mp1 = MKMapPointForCoordinate(CLLocationCoordinate2DMake(42.3507,-71.1));
CGPoint cgp1 = [opv pointForMapPoint:mp1];
CGPathMoveToPoint(myPath, nil, cgp1.x, cgp1.y);
MKMapPoint mp2 = MKMapPointForCoordinate(CLLocationCoordinate2DMake(42.45,-71.05));
CGPoint cgp2 = [opv pointForMapPoint:mp2];
CGPathAddLineToPoint(myPath, nil, cgp2.x, cgp2.y);
MKMapPoint mp3 = MKMapPointForCoordinate(CLLocationCoordinate2DMake(42.3,-71.0));
CGPoint cgp3 = [opv pointForMapPoint:mp3];
CGPathAddLineToPoint(myPath, nil, cgp3.x, cgp3.y);
opv.path = myPath;
CGPathRelease(myPath);
return opv;
}
if ([overlay isKindOfClass:[MKPolyline class]])
{
MKPolylineView *plv = [[[MKPolylineView alloc] initWithPolyline:overlay] autorelease];
plv.strokeColor = [UIColor purpleColor];
plv.lineWidth = 5;
return plv;
}
return nil;
}
Here is the result:
The red line on top is the TRTrip and the purple one on the bottom is the MKPolyline.
If your example is a bit more complex then this, check if you have any calls like this:
[mMapView removeOverlay:overlay]
in the code.