Is there anyway in ios app to adjust the linespacing between the multiple lines in CCLabelTTF in cocos2d? Thanks
The answer to you question is no. You can't adjust a CCLabelTTF linespacing. But hey! I will share with you my solution for this ;)
This is the .h
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface CCLabelTTFLineSpaced : CCLayer {
}
+ (id) labelWithString:(NSString*)string dimensions:(CGSize)dimensions alignment:(CCTextAlignment)alignment fontName:(NSString*)name fontSize:(CGFloat)size lineSpace:(CGFloat)space;
- (id) initWithString:(NSString*)str dimensions:(CGSize)dimensions alignment:(CCTextAlignment)alignment fontName:(NSString*)name fontSize:(CGFloat)size lineSpace:(CGFloat)space;
#end
And this is the .m
#import "CCLabelTTFLineSpaced.h"
#implementation CCLabelTTFLineSpaced
+ (id) labelWithString:(NSString*)string dimensions:(CGSize)dimensions alignment:(CCTextAlignment)alignment fontName:(NSString*)name fontSize:(CGFloat)size lineSpace:(CGFloat)space{
return [[[self alloc] initWithString: string dimensions:dimensions alignment:alignment fontName:name fontSize:size lineSpace:(CGFloat)space]autorelease];
}
- (id) initWithString:(NSString*)str dimensions:(CGSize)dimensions alignment:(CCTextAlignment)alignment fontName:(NSString*)name fontSize:(CGFloat)size lineSpace:(CGFloat)space{
if( (self=[super init]) ) {
anchorPoint_ = ccp(0.5f, 0.5f);
[self setContentSize:dimensions];
self.isRelativeAnchorPoint = NO;
int pos = 0;
int i = 0;
while (pos<[str length]) {
int end = 0;
int lastCut = -1;
bool finished=NO;
while (finished==NO) {
CGSize actualSize = [[str substringWithRange:NSMakeRange(pos, end)] sizeWithFont:[UIFont fontWithName:name size:size]];
if (actualSize.width > dimensions.width || pos+end == [str length]) {
if (pos+end == [str length] && actualSize.width <= dimensions.width) lastCut = end;
finished=YES;
}
else {
if ([[str substringWithRange:NSMakeRange(pos+end, 1)] isEqualToString:#" "] || [[str substringWithRange:NSMakeRange(pos+end, 1)] isEqualToString:#"."] || [[str substringWithRange:NSMakeRange(pos+end, 1)] isEqualToString:#","]) {
lastCut = end;
}
end++;
}
}
NSString * strLine = [str substringWithRange:NSMakeRange(pos, lastCut)];
CCLabelTTF * line = [CCLabelTTF labelWithString:strLine dimensions:CGSizeMake(dimensions.width, size*2) alignment:alignment fontName:name fontSize:size];
[line setAnchorPoint:ccp(0,1)];
[line setPosition:ccp(0,-i*space)];
[self addChild:line];
pos=pos+lastCut;
i++;
}
}
return self;
}
#end
Easy to use ;) I have to complete the class with getters, setters and all stuff. I know that this is a "homemade" solution, but hey! It works!
For those with Cocos 2d 2.x, I changed #Hardschool code to fix the deprecated methods and it's working awesome!
in the .h
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface CCLabelTTFLineSpaced : CCLayer {
}
+ (id) labelWithString:(NSString*)string dimensions:(CGSize)dimensions hAlignment: (CCTextAlignment)alignment fontName:(NSString*)name fontSize:(CGFloat)size lineSpace:(CGFloat)space;
- (id) initWithString:(NSString*)str dimensions:(CGSize)dimensions hAlignment:(CCTextAlignment)alignment fontName:(NSString*)name fontSize:(CGFloat)size lineSpace:(CGFloat)space;
#end
in the .m file
#import "CCLabelTTFLineSpaced.h"
#implementation CCLabelTTFLineSpaced
+ (id) labelWithString:(NSString*)string dimensions:(CGSize)dimensions hAlignment:(CCTextAlignment)alignment fontName:(NSString*)name fontSize:(CGFloat)size lineSpace:(CGFloat)space{
return [[[self alloc] initWithString: string dimensions:dimensions hAlignment:alignment fontName:name fontSize:size lineSpace:(CGFloat)space]autorelease];
}
- (id) initWithString:(NSString*)str dimensions:(CGSize)dimensions hAlignment:(CCTextAlignment)alignment fontName:(NSString*)name fontSize:(CGFloat)size lineSpace:(CGFloat)space{
if( (self=[super init]) ) {
anchorPoint_ = ccp(0.5f, 0.5f);
[self setContentSize:dimensions];
self.ignoreAnchorPointForPosition = YES;
int pos = 0;
int i = 0;
while (pos<[str length]) {
int end = 0;
int lastCut = -1;
bool finished=NO;
while (finished==NO) {
CGSize actualSize = [[str substringWithRange:NSMakeRange(pos, end)] sizeWithFont:[UIFont fontWithName:name size:size]];
if (actualSize.width > dimensions.width || pos+end == [str length]) {
if (pos+end == [str length] && actualSize.width <= dimensions.width) lastCut = end;
finished=YES;
}
else {
if ([[str substringWithRange:NSMakeRange(pos+end, 1)] isEqualToString:#" "] || [[str substringWithRange:NSMakeRange(pos+end, 1)] isEqualToString:#"."] || [[str substringWithRange:NSMakeRange(pos+end, 1)] isEqualToString:#","]) {
lastCut = end;
}
end++;
}
}
NSString * strLine = [str substringWithRange:NSMakeRange(pos, lastCut)];
CCLabelTTF * line = [CCLabelTTF labelWithString:strLine dimensions:CGSizeMake(dimensions.width, size*2) hAlignment:alignment fontName:name fontSize:size];
[line setAnchorPoint:ccp(0,1)];
[line setPosition:ccp(0,-i*space)];
[self addChild:line];
pos=pos+lastCut;
i++;
}
}
return self;
}
#end
That's it, thanks #Hardschool!
gmogames, here is setColor for example
void CCLabelTTFLineSpaced::setColor(ccColor3B color)
{
for (int i = 0; i < getChildren()->count(); i ++)
{
CCLabelTTF* child = (CCLabelTTF*)getChildren()->objectAtIndex(i);
child->setColor(color);
}
}
I think this is an Issue were many Cocos2d Developers stumbled upon. So to improve readability and collaboration on this class I was creating an repository on Github for it.
Here is the link
I startet at the Version of #gmoagames (and #Hardschool), added the setColor: Method from #Alex and added a Method to fade the opacity.
Feel free to send me any merge requests if you have some more improvements.
And many thanks for all the code that was shared here.
Related
I am developing a document-based application. This document can have multiple pages. so I have an array of NSView objects available with me. Now I want to provide print functionality in this app, but NSPrintOpertion takes only one NSView object so I am not able to generate print preview as well as print off multiple pages of the document.
Is there any way in cocoa to print a multi-page document?
Printing in Cocoa does not come for free, unfortunately. You have to implement your own NSView subclass that handles the drawing. It has to have access to your data and to draw the correct data onto each page, depending on the page number.
Here is a sample source code that can print any NSTableView. I used it here to show how you have to calculate the position of things to draw on the page:
MyPrintView.h:
#import <Cocoa/Cocoa.h>
#interface MyPrintView : NSView {
NSString *printJobTitle;
}
#property (copy, readwrite) NSString *printJobTitle;
- (id)initWithTableView:(NSTableView *)tableToPrint andHeader:(NSString *)header;
#end
MyPrintView.m:
#import "MyPrintView.h"
#interface MyPrintView ()
#property (nonatomic, weak) NSTableView *tableToPrint;
#property (nonatomic, strong) NSString *header;
#property (nonatomic, strong) NSDictionary *attributes;
#property (nonatomic, strong) NSFont *listFont;
#property (nonatomic) float headerHeight;
#property (nonatomic) float footerHeight;
#property (nonatomic) float lineHeight;
#property (nonatomic) float entryHeight;
#property (nonatomic) NSRect pageRect;
#property (nonatomic) int linesPerPage;
#property (nonatomic) int currentPage;
#end
#implementation MyPrintView
#synthesize printJobTitle;
- (id)initWithTableView:(NSTableView *)tableToPrint andHeader:(NSString *)header
{
// Initialize with dummy frame
self = [super initWithFrame:NSMakeRect(0, 0, 700, 700)];
if (self) {
self.tableToPrint = tableToPrint;
self.header = header;
self.listFont = [NSFont fontWithName:#"Helvetica Narrow" size:10.0];
CGFloat x = self.listFont.capHeight;
x = self.listFont.ascender;
x = self.listFont.descender;
self.lineHeight = self.listFont.boundingRectForFont.size.height;
self.entryHeight = [self.listFont capHeight] * 3;
self.headerHeight = 20 + self.entryHeight;
self.footerHeight = 20;
if (self.listFont) {
self.attributes = #{ NSFontAttributeName: self.listFont };
} else {
self.attributes = nil;
}
printJobTitle = #"My Print Job";
}
return self;
}
#pragma mark Pagination
- (BOOL)knowsPageRange:(NSRangePointer)range
{
NSPrintInfo *printInfo = [[NSPrintOperation currentOperation] printInfo];
self.pageRect = [printInfo imageablePageBounds];
NSRect newFrame;
newFrame.origin = NSZeroPoint;
newFrame.size = [printInfo paperSize];
[self setFrame:newFrame];
// Number of lines per page
self.linesPerPage = (self.pageRect.size.height - self.headerHeight - self.footerHeight) / self.entryHeight - 1;
// Number of full pages
NSUInteger noPages = self.tableToPrint.numberOfRows / self.linesPerPage;
// Rest of lines on last page
if (self.tableToPrint.numberOfRows % self.linesPerPage > 0) {
noPages++;
}
range->location = 1;
range->length = noPages;
return YES;
}
- (NSRect)rectForPage:(NSInteger)page
{
self.currentPage = (int)page - 1;
return self.pageRect;
}
- (NSAttributedString *)pageHeader
{
return [[NSAttributedString alloc] initWithString:self.header];
}
#pragma mark Drawing
- (BOOL)isFlipped
{
// Origin top left
return YES;
}
// We need this to find any transformers which are used to display the values of a certain column
static NSValueTransformer *TransformerFromInfoDict( NSDictionary *dict )
{
NSDictionary *options = dict[NSOptionsKey];
if (options == nil) return nil;
NSValueTransformer *transformer = options[NSValueTransformerBindingOption];
if (transformer == nil || (id)transformer == [NSNull null]) {
transformer = nil;
NSString *name = options[NSValueTransformerNameBindingOption];
if (name != nil && (id)name != [NSNull null]) {
transformer = [NSValueTransformer valueTransformerForName: name];
}
}
return transformer;
}
// This is where the drawing takes place
- (void)drawRect:(NSRect)dirtyRect
{
float margin = 20;
float leftMargin = self.pageRect.origin.x + margin;
float topMargin = self.pageRect.origin.y + self.headerHeight;
[NSBezierPath setDefaultLineWidth:0.25];
CGFloat originalWidth = 0;
for (NSTableColumn *col in self.tableToPrint.tableColumns) {
originalWidth += col.width;
}
CGFloat widthQuotient = (self.pageRect.size.width - margin) / originalWidth;
CGFloat inset = (self.entryHeight - self.lineHeight - 1.0)/2.0;
// Column titles
CGFloat horOffset = 0;
for (NSTableColumn *col in self.tableToPrint.tableColumns) {
NSRect rect = NSMakeRect(linkerRand + horOffset, topMargin, widthQuotient * spalte.width, self.entryHeight);
horOffset += widthQuotient * col.width;
[NSBezierPath strokeRect:rect];
NSString *theTitle = #"--";
if ([col respondsToSelector:#selector(title)]) { // OS X 10.10 and higher
theTitle = col.title;
} else {
NSTableHeaderCell *cell = col.headerCell;
theTitle = cell.title;
}
[theTitle drawInRect:NSInsetRect(rect, inset, inset) withAttributes:self.attributes];
}
NSUInteger firstEntryOfPage = self.currentPage * self.linesPerPage;
NSUInteger lastEntryOfPage = ((self.currentPage + 1) * self.linesPerPage) > self.tableToPrint.numberOfRows ? self.tableToPrint.numberOfRows : ((self.currentPage + 1) * self.linesPerPage);
for (NSUInteger i = 0; i < lastEntryOfPage - firstEntryOfPage; i++) {
#autoreleasepool { // to avoid memory hogging
NSUInteger row = firstEntryOfPage + i;
CGFloat horOffset = 0;
for (NSTableColumn *col in self.tableToPrint.tableColumns) {
NSDictionary *bindingInfo = [spalte infoForBinding: #"value"];
NSArray *columnValues = [bindingInfo[NSObservedObjectKey] valueForKeyPath: bindingInfo[NSObservedKeyPathKey]];
NSString *valueAsStr = #"";
id value = [columnValues objectAtIndex:col];
if ((value != nil) && (![value isKindOfClass:[NSNull class]])) {
// Do we have a transformer for that column? Then transform accordingly.
NSValueTransformer *transformer = TransformerFromInfoDict(bindingInfo);
if (transformer != nil)
value = [transformer transformedValue: value];
if ([value isKindOfClass:[NSString class]]) {
valueAsStr = value;
} else if ([value isKindOfClass:[NSNumber class]]) {
NSCell *cell = [col dataCellForRow:zeile];
if (cell.formatter != nil) {
valueAsStr = [cell.formatter stringForObjectValue:value];
} else {
valueAsStr = [value stringValue];
}
} else {
// We don't know what that is
NSLog(#"value class: %#", [value class]);
valueAsStr = #"????!";
}
}
NSRect rect = NSMakeRect(leftMargin + horOffset, topMargin + (i+1) * self.entryHeight, widthQuotient * col.width, self.entryHeight);
horOffset += widthQuotient * col.width;
// Now we can finally draw the entry
[NSBezierPath strokeRect:rect];
NSRect stringRect = NSInsetRect(rect, inset, inset);
[valueAsStr drawInRect:stringRect withAttributes:self.attributes];
}
}
}
}
#end
This is what you have to include in your document class in printDocumentWithSettings which gets called when the user selects "Print...":
[[self.printInfo dictionary] setValue:#YES forKey:NSPrintHeaderAndFooter];
NSString *headerLine = #"My first printed Table View";
MyPrintView *myPrintView = [[MyPrintView alloc] initWithTableView:theTableView andHeader:headerLine];
NSPrintOperation *op = [NSPrintOperation
printOperationWithView:myPrintView
printInfo:[self printInfo]];
[op setShowsPrintPanel:showPrintPanel];
// Run print operation, which shows the print panel if showPanels was YES
[self runModalPrintOperation:op
delegate:self
didRunSelector:nil
contextInfo:NULL];
This is my first time really using cocoa to make an application for mac. The only problem is that drawRect isn't being called at all. The real kicker is that it used to, but now it doesn't, and I have no idea what I did wrong. :/
I am setting 'setNeedsDisplay' but it refuses to work. As you can see, I have two debug prints in there, but only "DEBUG 1" is being called. Any ideas?
//ScrollingTextView.h:
//Adapted from http://stackoverflow.com/a/3233802/3438793
#import <Cocoa/Cocoa.h>
#interface ScrollingTextView : NSView {
NSTimer *scroller;
NSPoint point;
NSString *text;
NSString *tempText;
NSString *nextText;
CGFloat stringWidth;
NSInteger delay;
BOOL draw;
NSDictionary *fontDict;
NSFont *font;
BOOL isBigger;
}
#property (nonatomic, copy) NSString *text;
#property (nonatomic, copy) NSString *tempText;
#property (nonatomic, copy) NSString *nextText;
#property (nonatomic) NSInteger delay;
#property (nonatomic) BOOL draw;
#property (nonatomic) NSDictionary *fontDict;
#property (nonatomic) NSFont *font;
#property (nonatomic) BOOL isBigger;
#end
//ScrollingTextView.m
//Adapted from http://stackoverflow.com/a/3233802/3438793
#import "ScrollingTextView.h"
#implementation ScrollingTextView
#synthesize text;
#synthesize tempText;
#synthesize nextText;
#synthesize delay;
#synthesize draw;
#synthesize fontDict;
#synthesize font;
#synthesize isBigger;
- (void) setNextText:(NSString *)newText {
font = [NSFont fontWithName:#"Lucida Grande" size:15.0];
fontDict = [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, [NSNumber numberWithFloat:1.0], NSBaselineOffsetAttributeName, nil];
if (text == nil) {
text = [newText copy];
} else {
nextText = [newText copy];
}
point = NSZeroPoint;
stringWidth = [newText sizeWithAttributes:fontDict].width;
if (stringWidth <= 163) {
NSString *size = [#"{" stringByAppendingString:[[NSString stringWithFormat:#"%f", stringWidth] stringByAppendingString:#", 22}"]];
[self setFrameSize:NSSizeFromString(size)];
isBigger = false;
} else {
isBigger = true;
}
if (text != nil) {
scroller = nil;
scroller = [NSTimer scheduledTimerWithTimeInterval:0.03 target:self selector:#selector(moveText:) userInfo:nil repeats:YES];
}
draw = false;
delay = 100;
}
- (void) moveText:(NSTimer *)timer {
point.x = point.x - 1.0f;
[self setNeedsDisplay:YES];
NSLog(#"DEBUG 1");
}
- (void)drawRect:(NSRect)dirtyRect {
// Drawing code here.
NSLog(#"DEBUG 2");
//NSLog([#"Next: " stringByAppendingString:nextText]);
//NSLog([#"Temp: " stringByAppendingString:tempText]);
//NSLog([#"Main: " stringByAppendingString:text]);
if (isBigger) {
if (draw) {
CGFloat pointX = dirtyRect.size.width + (-1*(dirtyRect.size.width - stringWidth)) + 30;
if (point.x < -1*pointX) {
point.x += pointX;
draw = false;
delay = 100;
text = tempText;
tempText = nil;
}
[text drawAtPoint:point withAttributes:fontDict];
if (point.x < 0) {
NSPoint otherPoint = point;
otherPoint.x += pointX;
if (tempText != nil) {
[tempText drawAtPoint:otherPoint withAttributes:fontDict];
} else {
[text drawAtPoint:otherPoint withAttributes:fontDict];
}
}
} else {
if (nextText != nil) {
tempText = nextText;
nextText = nil;
}
point.x = 0;
[text drawAtPoint:point withAttributes:fontDict];
if (delay <= 0) {
draw = true;
} else {
delay -= 1;
}
}
} else {
dirtyRect.size.width = stringWidth;
point.x = 0;
text = nextText;
[text drawAtPoint:point withAttributes:fontDict];
}
}
#end
EDIT: I added a line to "moveText" to see what "needsDisplay" was, and it turns out that "[self setNeedsDisplay:YES];" is doing nothing. Here is the line I added:
- (void) moveText:(NSTimer *)timer {
point.x = point.x - 1.0f;
[self setNeedsDisplay:YES];
NSLog([NSString stringWithFormat:#"%hhd", [self needsDisplay]]); //This one
}
It just prints 0
Hi guys I really need some help, I have been stuck in this part of my game for over a week now and I can't seem to get past this issue, So have look at my code below,
#import "HelloWorldLayer.h"
#import "AppDelegate.h"
#implementation HelloWorldLayer
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
HelloWorldLayer *layer = [HelloWorldLayer node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
-(id) init
{
// always call "super" init
// Apple recommends to re-assign "self" with the "super's" return value
if( (self=[super init]) ) {
moles = [[NSMutableArray alloc] init];
winSize = [[CCDirector sharedDirector]winSize];
CCSprite *mole1 = [CCSprite spriteWithFile:#"lightsabericonblue.png"];
[self starCreateCurrentLevel:mole1];
}
return self;
}
-(void)starCreateCurrentLevel:(CCSprite *)mole1{
starCountCurrentLevel = 10;
for (int i = 0; i < starCountCurrentLevel;) {
[moles addObject:mole1];
starCountCurrentLevel--;
}
[self schedule:#selector(tryPopMoles:) interval:1];
}
- (void)tryPopMoles:(ccTime)dt {
if(moles.count != 0){
for (CCSprite *mole in moles) {
if (arc4random() % moles.count == 0) {
if (mole.numberOfRunningActions == 0) {
[self popMole:mole];
}
}
}
}else if(moles.count == 0){
NSLog(#"No More Moles To Spawn");
[self unschedule:#selector(tryPopMoles:)];
}
}
- (void) popMole:(CCSprite *)mole {
mole.position = ccp(150, 150);
[self addChild:mole];
}
- (void) dealloc
{
[moles release];
moles = nil;
[super dealloc];
}
#end
When I run this code i get the following error, * Assertion failure in -[HelloWorldLayer addChild:z:tag:], /Users/....../libs/cocos2d/CCNode.m:335.
I when i add the child in the init method its fine but I don't want to do that, I want to be able to call the try pop mole which will then call the pop mole based on if there is anymore sprites left in the array, I have a feeling I am missing something or doing something wrong.
UPDATE -
#import "HelloWorldLayer.h"
#import "AppDelegate.h"
#pragma mark - HelloWorldLayer
CGSize winSize;
int enemyX;
int enemyY;
int randomAngleY;
int randomAngleX;
int winSizeX;
int winSizeY;
int minDuration = 1;
int maxDuration = 4.0;
int rangeDuration;
int actualDuration;
int starCountCurrentLevel;
int test = 0;
#implementation HelloWorldLayer
+(CCScene *) scene
{
CCScene *scene = [CCScene node];
HelloWorldLayer *layer = [HelloWorldLayer node];
[scene addChild: layer];
return scene;
}
-(id) init
{
if( (self=[super init]) ) {
moles = [[NSMutableArray alloc] init];
winSize = [[CCDirector sharedDirector]winSize];
[self starCreateCurrentLevel];
}
return self;
}
-(void)starCreateCurrentLevel{
starCountCurrentLevel = 10;
for (int i = 0; i < starCountCurrentLevel;) {
[moles addObject:[CCSprite spriteWithFile:#"lightsabericonblue.png"]];
starCountCurrentLevel--;
}
[self schedule:#selector(tryPopMoles:) interval:3];
}
- (void)tryPopMoles:(ccTime)dt {
NSMutableArray *tempArray = [NSMutableArray arrayWithArray:moles];
for (int randomIndex = tempArray.count -1; randomIndex < tempArray.count; randomIndex--) {
CCSprite * sprite = (CCSprite *)[tempArray objectAtIndex:randomIndex];
//CCSprite *sprite = [tempArray objectAtIndex:randomIndex];
[self popMole:sprite];
NSLog(#"Enemy Added");
[tempArray removeObject:sprite];
}
if([tempArray count] == 0){
NSLog(#"No More Moles To Spawn");
[self unschedule:#selector(tryPopMoles:)];
}
}
- (void) popMole:(CCSprite *)sprite {
winSizeX = winSize.width - sprite.contentSize.width + 25;
winSizeY = winSize.height - 25 - sprite.contentSize.height + 17;
randomAngleX = arc4random() % winSizeX;
randomAngleY = arc4random() % winSizeY;
enemyX = arc4random() % winSizeX ;
enemyY = arc4random() % winSizeY;
rangeDuration = maxDuration - minDuration;
actualDuration = (arc4random() % rangeDuration) + minDuration;
sprite.position = ccp(enemyX, enemyY);
[self addChild:sprite];
}
- (void) dealloc
{
[moles release];
moles = nil;
[super dealloc];
}
#end
You're only creating the sprite once, but trying to add it several times. (Remember, these are pointers to objects, so you're always referring to exactly the same sprite.) If you want 10 versions of the same sprite (at different positions, scales, speeds, whatever), you'll need to create the sprite (via the creator method) 10 times. You can do a copy on the original one, or just create it on the fly:
-(void)starCreateCurrentLevel:(CCSprite *)mole1{
starCountCurrentLevel = 10;
for (int i = 0; i < starCountCurrentLevel;) {
//[moles addObject:mole1];
// ^^ You're adding the same sprite (meaning same pointer reference) again and again. Try this instead:
[moles addObject:[CCSprite spriteWithFile:#"lightsabericonblue.png"]];
starCountCurrentLevel--;
}
Of course, now you don't need to pass mole1 into your method. You may (depending on your need) consider passing in a spriteFrameName or something.
I was foolish and didn't test continually as I programmed, so now I'm not sure where the error has crept in. Am working on a programmable calculator. When I run, it crashes before displaying anything and gives me this message:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<CalculatorViewController 0x6a405a0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key description.'
I'd use NSLog to look for the bug, but I don't know where to try it when the crash happens before anything shows up. Thoughts on what I'm doing wrong?
Here's CalculatorViewController.m, with some extra property declarations for unfinished, commented-out code I've omitted:
#import "CalculatorViewController.h"
#import "CalculatorBrain.h"
#interface CalculatorViewController ()
#property (nonatomic) BOOL userIsEnteringNumber;
#property (nonatomic) BOOL numberIsNegative;
#property (nonatomic,strong) CalculatorBrain *brain;
#end
#implementation CalculatorViewController
#synthesize display = _display;
#synthesize descriptionLabel = _descriptionLabel;
#synthesize userIsEnteringNumber = _userIsEnteringNumber;
#synthesize numberIsNegative;
#synthesize brain = _brain;
-(CalculatorBrain *)brain
{
if (!_brain) _brain = [[CalculatorBrain alloc] init];
return _brain;
}
//This adds a pressed digit to the display label.
- (IBAction)digitPressed:(UIButton *)sender
{
NSString *digit = sender.currentTitle;
//Enter digit if it wouldn't create a two-decimal-point case.
NSRange range = [self.display.text rangeOfString:#"."];
if (range.location==NSNotFound || (![digit isEqualToString:#"."]))
if (self.userIsEnteringNumber)
{
self.display.text = [self.display.text stringByAppendingString:digit];
self.descriptionLabel.text = [self.display.text stringByAppendingString:#" "];
}
else
{
self.descriptionLabel.text = [self.descriptionLabel.text stringByAppendingString:digit];
self.descriptionLabel.text = [self.descriptionLabel.text stringByAppendingString:#" "];
if (![sender.currentTitle isEqualToString:#"."])
{
self.display.text = digit;
}
else
{
self.display.text = #"0.";
}
self.userIsEnteringNumber = YES;
}
}
//This sets up an operation.
- (IBAction)operationPressed:(UIButton *)sender
{
if (self.userIsEnteringNumber) [self enterPressed];
NSString *operation = sender.currentTitle;
double result = [self.brain performOperation:operation];
self.display.text = [NSString stringWithFormat:#"%g",result];
{
NSString *descr = [self.brain description];
self.descriptionLabel.text = descr;
}
}
- (IBAction)enterPressed
{
NSCharacterSet *set = [NSCharacterSet decimalDigitCharacterSet];
NSRange range = [self.display.text rangeOfCharacterFromSet:set];
if (range.location==NSNotFound)
{
[self.brain pushOperandAsVariable:self.display.text];
}
else
{
[self.brain pushOperand:[self.display.text doubleValue]];
}
self.userIsEnteringNumber = NO;
}
#end
And here's CalculatorBrain.m:
#import "CalculatorBrain.h"
#interface CalculatorBrain()
#property (nonatomic, strong) NSMutableArray *programStack;
#property (nonatomic,strong)NSDictionary *variableValues;
#end
#implementation CalculatorBrain
#synthesize programStack = _programStack;
#synthesize variableValues = _variableValues;
- (NSMutableArray *)programStack
{
if (!_programStack) _programStack = [[NSMutableArray alloc] init];
return _programStack;
}
- (id)program
{
return [self.programStack copy];
}
//Here are the two types of pushes that the ViewController can implement. First, operand pushes . . .
- (void)pushOperand:(double)operand
{
[self.programStack addObject:[NSNumber numberWithDouble:operand]];
}
//. . . and then variable pushes.
- (void) pushOperandAsVariable:(NSString *)variable
{
//Create dictionary
//Move this later on to ViewController but for now leave where it is....
NSMutableArray *variablesUsed = [[NSMutableArray alloc] init];
NSArray *objects = [[NSArray alloc] initWithObjects:[NSNumber numberWithDouble:3],[NSNumber numberWithDouble:4.1],[NSNumber numberWithDouble:-6],[NSNumber numberWithDouble:4.5298], [NSNumber numberWithDouble:3.14159], nil];
NSArray *keys = [[NSArray alloc] initWithObjects:#"x",#"y",#"z",#"foo", #"π", nil];
NSDictionary *variableValues = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];
//Check program for keys
NSNumber *operand;
for (int i=0; i<keys.count; i++)
{
if ([[keys objectAtIndex:i] isEqual:variable])
[variablesUsed addObject:variable];
operand = [variableValues objectForKey:variable];
}
[self.programStack addObject:operand];
}
- (double)performOperation:(NSString *)operation
{
[self.programStack addObject:operation];
return [[self class] runProgram:self.program];
}
+ (double)popOffStack:(NSMutableArray *)stack
{
double result = 0;
id topOfStack = [stack lastObject];
if (topOfStack) [stack removeLastObject];
if ([topOfStack isKindOfClass:[NSNumber class]])
{
result = [topOfStack doubleValue];
}
//Here are the results for various operations.
else if ([topOfStack isKindOfClass:[NSString class]])
{
NSString *operation = topOfStack;
if ([operation isEqualToString:#"+"])
{
result = [self popOffStack:stack] +
[self popOffStack:stack];
}
else if ([#"*" isEqualToString:operation])
{
result = [self popOffStack:stack] *
[self popOffStack:stack];
}
else if ([operation isEqualToString:#"-"])
{
double subtrahend = [self popOffStack:stack];
result = [self popOffStack:stack] - subtrahend;
}
else if ([operation isEqualToString:#"/"])
{
double divisor = [self popOffStack:stack];
if (divisor) result = [self popOffStack:stack] / divisor;
}
else if ([operation isEqualToString:#"sin"])
{
result = sin([self popOffStack:stack]);
}
else if ([operation isEqualToString:#"cos"])
{
result = cos([self popOffStack:stack]);
}
else if ([operation isEqualToString:#"√"])
{
result = sqrt([self popOffStack:stack]);
}
else if ([operation isEqualToString:#"π"])
{
result = M_PI;
}
}
return result;
}
+ (double)runProgram:(id)program
{
//Run program.
NSMutableArray *mutableCopyOfProgram;
if ([program isKindOfClass:[NSArray class]])
{
mutableCopyOfProgram = [program mutableCopy];
return [self popOffStack:mutableCopyOfProgram];
}
else return 0;
}
#end
As always, thanks for your help.
The most common cause of this is that you have a CalculatorViewController object defined in a storyboard or xib file and something in the graphical interface has a link to an outlet on it called "description". Meanwhile, you no longer have a description outlet in the code for that class.
This will usually be fixed by tracking down the stray reference in interface builder and getting rid of it.
I am trying to create a class where the width and height of a 2 dimensional array can be dynamically created at the point of initialisation with init parameters.
I have been looking through the web for hours and cannot find a way.
Using a standard C syntax [][] does not allow for a variable to be used to declare the array.
The mutable arrays within Objective C, in all examples I have seen, require the objects to be hard coded at the time of creation.
Is there a way of creating a 2 dimensional array within an object with parameters to define the sizes at the point of creation?
Hoping someone can tell me what I am missing...
You can do this quite easily by writing a category on NSMutableArray:
#interface NSMutableArray (MultidimensionalAdditions)
+ (NSMutableArray *) arrayOfWidth:(NSInteger) width andHeight:(NSInteger) height;
- (id) initWithWidth:(NSInteger) width andHeight:(NSInteger) height;
#end
#implementation NSMutableArray (MultidimensionalAdditions)
+ (NSMutableArray *) arrayOfWidth:(NSInteger) width andHeight:(NSInteger) height {
return [[[self alloc] initWithWidth:width andHeight:height] autorelease];
}
- (id) initWithWidth:(NSInteger) width andHeight:(NSInteger) height {
if((self = [self initWithCapacity:height])) {
for(int i = 0; i < height; i++) {
NSMutableArray *inner = [[NSMutableArray alloc] initWithCapacity:width];
for(int j = 0; j < width; j++)
[inner addObject:[NSNull null]];
[self addObject:inner];
[inner release];
}
}
return self;
}
#end
Usage:
NSMutableArray *dynamic_md_array = [NSMutableArray arrayOfWidth:2 andHeight:2];
Or:
NSMutableArray *dynamic_md_array = [[NSMutableArray alloc] initWithWidth:2 andHeight:2];
Here is another pure Objective C Version
#import foundation.h
#interface ZTwoDimensionalArray : NSObject{
#package
NSMutableArray* _array;
int _rows, _columns;
}
-(id) initWIthRows:(int)numberOfRows andColumns:(int) numberOfColumns;
-(id) getObjectAtRow:(int) row andColumn:(int)column;
-(void) setObject:(id) anObject atRow:(int) row andColumn:(int)column;
#end
#import "ZTwoDimensionalArray.h"
#implementation ZTwoDimensionalArray
-(id) initWIthRows:(int)numberOfRows andColumns:(int) numberOfColumns{
if (self = [super init]) {
_array = [NSMutableArray initWithCapacity:numberOfRows*numberOfColumns];
_rows = numberOfRows;
_columns = numberOfColumns;
}
return self;
}
-(id) getObjectAtRow:(int) row andColumn:(int)column{
return [_array objectAtIndex: row*_rows + column];
}
-(void) setObject:(id) anObject atRow:(int) row andColumn:(int)column{
[_array insertObject:anObject atIndex:row*_rows + column];
}
-(void) dealloc{
[_array release];
}
#end
Here's another way. Of course this is just for int but the code could easily be altered for other datatypes.
AOMatrix.h:
#import <Cocoa/Cocoa.h>
#interface AOMatrix : NSObject {
#private
int* matrix_;
uint columnCount_;
uint rowCount_;
}
- (id)initWithRows:(uint)rowCount Columns:(uint)columnCount;
- (uint)rowCount;
- (uint)columnCount;
- (int)valueAtRow:(uint)rowIndex Column:(uint)columnIndex;
- (void)setValue:(int)value atRow:(uint)rowIndex Column:(uint)columnIndex;
#end
AOMatrix.m
#import "AOMatrix.h"
#define INITIAL_MATRIX_VALUE 0
#define DEFAULT_ROW_COUNT 4
#define DEFAULT_COLUMN_COUNT 4
/****************************************************************************
* BIG NOTE:
* Access values in the matrix_ by matrix_[rowIndex*columnCount+columnIndex]
****************************************************************************/
#implementation AOMatrix
- (id)init {
return [self initWithRows:DEFAULT_ROW_COUNT Columns:DEFAULT_COLUMN_COUNT];
}
- (id)initWithRows:(uint)initRowCount Columns:(uint)initColumnCount {
self = [super init];
if(self) {
rowCount_ = initRowCount;
columnCount_ = initColumnCount;
matrix_ = malloc(sizeof(int)*rowCount_*columnCount_);
uint i;
for(i = 0; i < rowCount_*columnCount_; ++i) {
matrix_[i] = INITIAL_MATRIX_VALUE;
}
// NSLog(#"matrix_ is %ux%u", rowCount_, columnCount_);
// NSLog(#"matrix_[0] is at %p", &(matrix_[0]));
// NSLog(#"matrix_[%u] is at %p", i-1, &(matrix_[i-1]));
}
return self;
}
- (void)dealloc {
free(matrix_);
[super dealloc];
}
- (uint)rowCount {
return rowCount_;
}
- (uint)columnCount {
return columnCount_;
}
- (int)valueAtRow:(uint)rowIndex Column:(uint)columnIndex {
// NSLog(#"matrix_[%u](%u,%u) is at %p with value %d", rowIndex*columnCount_+columnIndex, rowIndex, columnIndex, &(matrix_[rowIndex*columnCount_+columnIndex]), matrix_[rowIndex*columnCount+columnIndex]);
return matrix_[rowIndex*columnCount_+columnIndex];
}
- (void)setValue:(int)value atRow:(uint)rowIndex Column:(uint)columnIndex {
matrix_[rowIndex*columnCount_+columnIndex] = value;
}
#end