string not populating with array data, even though arrays are not really empty - objective-c

I am trying to create a non-Document-based application for Mac OS X that randomizes cards for the game of Dominion.
From many of the ones I have tried, the only thing I cannot seem to do is limit the number of sets picked from a selection made by the user, and things worked pretty well in my program, but I am having issues.
I am trying to get the results to print in a custom view, but every time I look at the print preview, nothing shows, except header text, as specified in an NSMutableString.
This piece of code is what is being used to print and is found in MasterViewController:
- (IBAction)print:(id)sender
{
NSMutableString *content = [[NSMutableString alloc] initWithString:#"Cards\r\n\r\n"];
for (int i = 0; i < [supply.game count]; i++)
{
[content appendFormat:#"Card: %# Set: %# Cost: %d\r\n", [supply.game[i] name], [supply.game[i] collection], [supply.game[i] cost]];
}
[content appendFormat:#"\r\n\r\nRequired\r\n\r\n"];
for (int i = 0; i < [[setup supply] count]; i++)
{
NSDictionary* current = [setup supply][i];
NSString* key = [current allKeys][0]; // get the key of the current dictionary must be 0, as there is only one key
int value = [[current valueForKey:key] integerValue]; // variable to hold key value
if (value > 0) {
[content appendFormat:#"%#: %#", key, #"Yes"];
}
else
{
[content appendFormat:#"%#: %#", key, #"No"];
}
}
printView.content = [NSMutableString stringWithString:content];
[printView print:sender];
}
the data initially gets filled into some tableviews, which displays the correct content, and the supply.game array is the exact array that contains cards used for games.
setup is a property that refers to a view controller that populates a table with kinds of cards that may be required for games (e.g. shelters, colonies, ruins, spoils, and potions) and the supply method is supposed to return the array that view controller creates, which is itself not empty, as that table populates properly.
printView is a property that is assigned to a custom view found in MainMenu.xib and is the real view being used to print from.
the printView class looks like this:
header:
#import <Cocoa/Cocoa.h>
#interface PrintView : NSView
{
NSMutableString* content;
}
#property NSMutableString* content;
- (void)drawStringInRect:(NSRect)rect; // method to draw string to page
- (void)print:(id)sender; // method to print
#end
implementation:
#import "PrintView.h"
#implementation PrintView
#synthesize content;
- (BOOL)acceptsFirstResponder
{
return YES;
}
- (void)print:(id)sender
{
[[NSPrintOperation printOperationWithView:self] runOperation];
}
- (void)drawRect:(NSRect)dirtyRect {
NSGraphicsContext *context = [NSGraphicsContext currentContext];
if ([context isDrawingToScreen])
{
}
else
{
[[NSColor whiteColor] set];
NSRect bounds = [self bounds];
if (content == nil || [content length] == 0)
{
NSRectFill(bounds);
}
else
{
[self drawStringInRect:bounds];
}
}
}
- (void)drawStringInRect:(NSRect)rect
{
NSSize strSize; // variable to hold string size
NSPoint strOrigin; // variable used to position text
NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init];
[attributes setObject:[NSFont fontWithName:#"Helvetica" size:12] forKey:NSFontAttributeName];
[attributes setObject:[NSColor blackColor] forKey:NSForegroundColorAttributeName];
strSize = [content sizeWithAttributes:attributes];
strOrigin.x = rect.origin.x + (rect.size.width - strSize.width)/2;
strOrigin.y = rect.origin.y + (rect.size.height - strSize.height)/2;
[content drawAtPoint:strOrigin withAttributes:attributes];
}
#end
When I check the array sizes for printing operation, the size of the arrays is reported as zero, thus resulting in my current problem
If you need more code, here is code from Github, but I do not have the experimental branch up there, which is where the above code came from, though it should not be too different.
The MasterViewController will show how the supply.game array is made and SetupViewController houses the code that is used to determine what is needed in the game, as well as show how the supply array from [setup supply] is being produced.
MasterViewController has also been added as an object to MainMenu.xib, so I do not know if that affects anything.
Any idea of what I need to do?
Edit: Added in info that might be relevant

Related

NSMutableDictionary - entries appear to be over-written by each addition

I'm fairly new to Objective-C; but have been coding for years and this one really stumps me.
I'm trying to build an iPhone app and wanted to create a "settings" screen which will use a Table format. (Xcode 5.1.1).
I want to future proof the main Settings screen and make it easy for the application coding by hiding the "hard work" in subroutines/methods.
I may be getting too clever but I've created a class for each 'setting' that contains screen prompts, default values etc and using an Enum to cross-reference it (so the compiler will highlight typos etc)
The problem I'm encountering is that when I add entries to my NSMutableDictionary and use lldb to print the values; every entry seems to have the same "key" and values. I've tried converting the eNum to an NSNumber and also as an NSString -- no difference in the result - so I'm obviously doing something else daft but just can't see it
The following code is from various .m & .h files, I've omitted boring stuff that you always "have to have" to keep it short
// basic x-ref I want to use in my code
typedef NS_OPTIONS(NSInteger, ConfigurationType) {
unDefined = -1,
Server = 0,
Id = 1,
Phone = 2
};
// definition for a "single" Settings value
#interface SettingDefinition : NSObject
#end
#implementation SettingDefinition
ConfigurationType _cfgType;
NSString *_cfgName;
NSString *_screenTitle;
NSString *_value;
- (NSString *)description
{
NSString *className = NSStringFromClass([self class]);
return [NSString stringWithFormat:#"<%#: x%p Type=%d dbKey=%# '%#' -> %#>", className, self, _cfgType, _cfgName, _screenTitle, _value];
}
- (id)initType:(ConfigurationType)cfgOption
withDbKey: (NSString*)dbKey
asOptionTitle:(NSString*)cfgTitle
withValue:(NSString*)itmValue
{
self = [super init];
if (self) {
_screenTitle = cfgTitle;
_cfgName = dbKey;
_cfgType = cfgOption;
_value = itmValue;
}
return self;
}
#end
#interface Configuration : NSObject
#end
#implementation Configuration {
NSMutableDictionary *Settings; // List of Setting structures
};
- (id)init {
self = [super init];
if (self) {
Settings = [[NSMutableDictionary alloc]init];
[self add:Server withDbKey:#"Server" asOptionTitle:#"Server"];
[self add:Id withDbKey:#"Id" asOptionTitle:#"Your ID"];
[self add:Phone withDbKey:#"Phone" asOptionTitle:#"Phone No."];
}
return self;
}
- (void) add:(ConfigurationType)cfgOption
withDbKey:(NSString*)dbKey
asOptionTitle:(NSString*)cfgTitle
{
NSString * itmValue = [self configurationValue: cfgOption cfgName:dbKey];
SettingDefinition *x = [[SettingDefinition alloc]
initType: cfgOption
withDbKey: dbKey
asOptionTitle: cfgTitle
withValue: itmValue];
[Settings setObject:x forKey:[self asKey:cfgOption]];
}
- (NSString *) asKey:(ConfigurationType) settingType {
NSString *rc = [NSString stringWithFormat:#"%d", settingType];
return rc;
}
- (NSString *) configurationValue:(ConfigurationType) settingType {
// returns a suitable value from my system setup
// which is initially a null value until the user sets everything up
}
the debug window shows the following when I break after the final call to [self add: ...]
(lldb) po Settings
{
0 = "<SettingDefinition: x0x8e7c280 Type=2 dbKey=Phone 'Phone No.' -> (null)>";
1 = "<SettingDefinition: x0x8c703a0 Type=2 dbKey=Phone 'Phone No.' -> (null)>";
2 = "<SettingDefinition: x0x8e7c310 Type=2 dbKey=Phone 'Phone No.' -> (null)>";
}
The (null) is obviously due to no data in 'value' yet; but why do they all show as 'Phone'; if I break after the second call to [self add:..] they all show as 'Id'
UPDATE:
DOH! obviously they're globals (I've been using another IDE where everything is local until exposed) .. If I enclose them in braces in the implementation as the documentation states then the exhibited problem vanishes. I have properties to access the variables but as the setter does more than just set the memory, I thought I'd need my "own" variables to hold the data.. said it was something daft .. thank you!

Adding NSImage to IKimagerowserview

I am able to add NSImages to my NSCollectionView without having to first save them on disk. The images are fed into the collection view from an NSMutableArray. This way people can see the images without first having to save them.
Is there something similar that I can achieve with IKImageBrowserView? NSCollectionView is functional when it comes to representing images, but I would like to see if I can do something similar with IKImageBrowserView.
I can easily implement IKImageBrowserView with images saved on disk (Apple docs cover how this works) but can't figure out exactly where to look or how to go about adding images to the browser view directly from NSMutableArray instead of first saving them images to disk.
I'm at a loss here. Apart from the docs, I'm not really sure where else to look for direction. Or what to even call what I'm looking to do.
EDIT: (Here's some of the code)
// The data object -- if it is possible to represent an image object, this is where I am probably going wrong.
#interface ImageObject : NSObject
#property (readwrite, copy) NSImage *image;
#property (readwrite, copy) NSString *imageID;
- (id)initWithImage:(NSImage *)anImage;
- (NSString *)imageUID;
- (NSString *)imageRepresentationType;
- (id)imageRepresentation;
#end
#implementation ImageObject
#synthesize image = _image;
#synthesize imageID = _imageID;
- (id)initWithImage:(NSImage *)anImage
{
if (self = [super init]) {
_image = [anImage copy];
}
return self;
}
- (NSString *)imageUID
{
return _imageID;
}
- (NSString *)imageRepresentationType
{
return IKImageBrowserNSImageRepresentationType;
}
- (id)imageRepresentation
{
return _image;
}
#end
// This is how objects are supposed to be added to the browserView. All of this is straight from Apple.
- (void)updateDatasource
{
[_browserImages addObjectsFromArray:_importedImages];
[_importedImages removeAllObjects];
[imageBrowser reloadData];
}
- (NSUInteger)numberOfItemsInImageBrowser:(IKImageBrowserView *)aBrowser
{
return [_browserImages count];
}
- (id)imageBrowser:(IKImageBrowserView *)aBrowser itemAtIndex:(NSUInteger)index
{
return [_browserImages objectAtIndex:index];
}
This is where I try to add NSImages to the browserView but nothing happens. The array gets populated (which means the images are generated without any errors) but nothing happens on the screen.
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[oPanel URL] options:nil];
NSMutableArray *timesArray = [self generateTimeForSpecifiedNumberOfFramesInVideo:10 UsingAsset:asset];
self.imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];
[[self imageGenerator] generateCGImagesAsynchronouslyForTimes:timesArray completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) {
NSImage *testImage = [[NSImage alloc] initWithCGImage:image size:NSZeroSize];
if (result == AVAssetImageGeneratorSucceeded) {
ImageObject *objects = [[ImageObject alloc] initWithImage:testImage];
[_importedImages addObject:objects];
}
}
As for exploring the rest of the search results...been there done that. If I did miss anything, kindly mark this question as duplicate indicating what post already existed where this issue has been addressed.
EDIT:
I have accepted the answer below. Along with the unique IDs problem. I had overlooked a simple thing which was the requirement to call the updateDatasource method.
The most important point of using IKImageBrowser is create a unique image ID for each element. The following is an example. In fact, it comes from the project that I'm currently working on. I have just implemented IKImageBrowser in it. The code below assumes that you have 36 images (Image01.png, Image02.png..., Image36.png) imported into the project.
// .h
#interface AppDelegate : NSObject {
IBOutlet IKImageBrowserView *browserView;
NSMutableArray *imageArray;
}
// .m
#import "IKBBrowserItem.h"
#import <QuartzCore/QuartzCore.h>
- (void)applicationWillFinishLaunching:(NSNotification *)notification {
imageArray = [[NSMutableArray alloc]init];
}
- (void)populateImage {
for (NSInteger i2 = 1; i2 <= 36 ; i2++) {
NSString *name;
if (i2 < 10) {
name = [NSString stringWithFormat:#"Image0%ld",(long)i2];
} else {
name = [NSString stringWithFormat:#"Image%ld",(long)i2];
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
NSImage *Image0 = [NSImage imageNamed:name];
NSInteger ran = [self genRandom:1000000:9999999];
NSString *imageID = [NSString stringWithFormat:#"%#%li",name,ran];
IKBBrowserItem *item = [[IKBBrowserItem alloc] initWithImage:Image0 imageID:imageID:name];
[imageArray addObject:item];
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
[browserView reloadData];
}
- (NSInteger)genRandom: (NSInteger)min :(NSInteger)max {
int num1;
do {
num1 = arc4random() % max;
} while (num1 < min);
return num1;
}
You don't need to use a random integer generator (genRandom) above, but just make sure that no imageID is the same.
This web site has a sample project, which should get you going. (I have no affiliation.) So make sure you download and run it. Then take a closer look and improve it for your needs.

UILabel Address label is blank and Local Declaration hides instance variable

I am new to Xcode, and I am attempting to have my app access the Address Book, choose a person, and then create NSString values for the person (First Name, Last Name, Organization, Address, Email and Telephone Number) I can pull the first and last name, the organization, the first email entered (it would be nice to display all of the email address, and let the user choose), and the first phone number entered in (again, it would be nice to be able to choose), but the address for the person is always blank. I would really appreciate any help you can provide. In addition, I keep getting local declaration hides instance variable warnings - I have no idea how to resolve these.
#import "TACustomer.h"
#interface TACustomer ()
#end
#implementation TACustomer
#synthesize custfirstName;
#synthesize custlastName;
#synthesize custOrganization;
#synthesize custEmail;
#synthesize custAddress;
#synthesize custphoneNumber;
- (IBAction)showPicker:(id)sender
{
// Creating the Address Book Picker
ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init];
// Place the delegate of the picker to the control.
picker.peoplePickerDelegate = self;
// Showing the picker.
[self presentModalViewController:picker animated:YES];
}
- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker
{
//assigning control back to the main controller.
[self dismissModalViewControllerAnimated:YES];
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person
{
[self displayPerson:person];
[self dismissModalViewControllerAnimated:YES];
return NO;
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property
identifier:(ABMultiValueIdentifier)identifier
{
// Only inspect the value if it's an address.
if (property == kABPersonAddressProperty)
{
//Set up an ABMultiValue to hold the address values; copy from a book record.
ABMutableMultiValueRef multicustValue = ABRecordCopyValue(person, property);
// Set up an NSArray and copy values into it.
NSArray *thecustArray = (__bridge id)ABMultiValueCopyArrayOfAllValues(multicustValue);
// Figure out which values we want and store the index.
const NSUInteger customerIndex = ABMultiValueGetIndexForIdentifier (multicustValue, identifier);
// Set up an NSDictionary to hold the contents of the array.
NSDictionary *custDict = [thecustArray objectAtIndex:customerIndex];
// Set up NSStrings to hold the keys and values. First, how many are there?
const NSUInteger theCount = [custDict count];
NSString * __unsafe_unretained keys[theCount];
NSString *__unsafe_unretained values[theCount];
// Get the keys and values from the CFDictionary.
[custDict getObjects:values andKeys:keys];
// Set the address label's text.
NSString *customeraddress;
customeraddress = [NSString stringWithFormat:#"%#, %#, %#, %#, %#",
[custDict objectForKey:(NSString *)kABPersonAddressStreetKey],
[custDict objectForKey:(NSString *)kABPersonAddressCityKey],
[custDict objectForKey:(NSString *)kABPersonAddressStateKey],
[custDict objectForKey:(NSString *)kABPersonAddressZIPKey],
[custDict objectForKey:(NSString *)kABPersonAddressCountryKey]];
self.custAddress.text = customeraddress;
}
return NO;
}
- (void)displayPerson:(ABRecordRef)person
{
// Get Customer First Name
NSString* custfirstname = (__bridge_transfer NSString*)ABRecordCopyValue(person,kABPersonFirstNameProperty);
self.custfirstName.text = custfirstname;
// Get Customer Last Name
NSString* custlastname = (__bridge_transfer NSString*)ABRecordCopyValue(person,kABPersonLastNameProperty);
self.custlastName.text = custlastname;
// Get Customer Organization
NSString* custorganization = (__bridge_transfer NSString*)ABRecordCopyValue(person,kABPersonOrganizationProperty);
self.custOrganization.text = custorganization;
//Get Customer Email Address
NSString* custemail = nil;
ABMultiValueRef custemailAddresses = ABRecordCopyValue (person,kABPersonEmailProperty);
if (ABMultiValueGetCount(custemailAddresses) > 0)
{
custemail = (__bridge_transfer NSString*)ABMultiValueCopyValueAtIndex(custemailAddresses, 0);
} else
{
custemail = #"[None]";
}
self.custEmail.text = custemail;
CFRelease(custemailAddresses);
// Get Customer Phone Number
NSString* custphone = nil;
ABMultiValueRef phoneNumbers = ABRecordCopyValue (person,kABPersonPhoneProperty);
if (ABMultiValueGetCount(phoneNumbers) > 0)
{
custphone = (__bridge_transfer NSString*)ABMultiValueCopyValueAtIndex(phoneNumbers, 0);
} else
{
custphone = #"[None]";
}
self.custphoneNumber.text = custphone;
CFRelease(phoneNumbers);
bundle:nil;
//[self.navigationController pushViewController:tempExamInfoView animated:YES];
}
#end
I have no answer for the problem that the address data is blank.
To get rid of the "local declaration hides instance variable" warning you need to use different variable names that do not clash with the names of the properties you are synthesizing.
For instance, in displayPerson: you have a local variable custfirstname. Because this uses the same name as a property, the local variable hides the instance variable of the same name that is being synthesized.
If you want to keep the local variable name, I believe it is also possible to tell #synthesize to use a different name for the instance variable that it generates. I am not familiar with the syntax, so if you want to go that way you have to look it up yourself.

After xcode crash some buttons have either empty titles or are partially truncated

This seems to be a partial repost of XCode 4: some button titles not visible in iOS simulation but their question wasn't answered.
Please be gentle, I'm very new to this.
I've implemented a stack-based calculator program as per the stamford lecture series, but some of my buttons show as either empty or half truncated in the simulator. The same buttons will do it every time I run the sim, but if I move any buttons around it changes which ones are affected. It's usually the bottom ones.
First example here: http://i.imgur.com/YpC1f.png - see how the bottom row of buttons don't display correctly? If I make any one of those buttons taller, it will show with no title but all the other four buttons will then show correctly.
I thought it might be too close to the bottom, or those buttons were broken, or similar. So I deleted the whole bottom row, made everything smaller, and then recreated those buttons and now I get four buttons with blank titles and two truncated: http://i.imgur.com/kM1Rb.png
Note that the buttons all still work as expected, it's just the display that isn't right.
Am I doing something wrong? Any advice appreciated.
EDIT: Full code from the controller:
#import "CalculatorViewController.h"
#import "CalculatorBrain.h"
#interface CalculatorViewController()
#property (nonatomic) BOOL userIsInTheMiddleOfEnteringANumber;
#property (nonatomic, strong) CalculatorBrain *brain;
- (void)updateStackDisplay:(NSString *)value;
#end
#implementation CalculatorViewController
#synthesize display = _display;
#synthesize stackDisplay = _stackDisplay;
#synthesize userIsInTheMiddleOfEnteringANumber = _userIsInTheMiddleOfEnteringANumber;
#synthesize brain = _brain;
- (CalculatorBrain *)brain {
if (!_brain) _brain = [[CalculatorBrain alloc] init];
return _brain;
}
- (void)updateStackDisplay:(NSString *)value {
// if there's nothing sent so far, just initialise it
if (self.stackDisplay.text.length == 0) {
self.stackDisplay.text = value;
return;
}
// This part is a little confusing. Extra assignment asked for = to be added to the end of the stack label if an operation was pressed.
// Here I check for = at the end of the label and remove it so it's only displayed once. If "=" is being passed (done by the operation itself), it will be added back on right at the end of this function.
if ([self.stackDisplay.text rangeOfString:#"="].location == (self.stackDisplay.text.length - 1)) {
// .location starts at zero, .length doesn't.
self.stackDisplay.text = [self.stackDisplay.text substringToIndex:[self.stackDisplay.text length]-1];
}
// If we add a space after remove the = we'll end up with double space. Hence the else if, not if.
else if (!self.userIsInTheMiddleOfEnteringANumber) {
self.stackDisplay.text = [self.stackDisplay.text stringByAppendingString:#" "];
}
self.stackDisplay.text = [self.stackDisplay.text stringByAppendingString:value];
}
- (IBAction)digitPressed:(UIButton *)sender {
NSString *digit = sender.currentTitle;
if ([digit isEqualToString:#"."]) {
if ([self.display.text rangeOfString:#"."].location != NSNotFound) {
return;
}
else {
if (!self.userIsInTheMiddleOfEnteringANumber) digit = #"0.";
}
}
[self updateStackDisplay:digit];
if (self.userIsInTheMiddleOfEnteringANumber) {
self.display.text = [self.display.text stringByAppendingString:digit];
}
else {
self.display.text = digit;
self.userIsInTheMiddleOfEnteringANumber = YES;
}
}
- (IBAction)operationPressed:(UIButton *)sender {
if (self.userIsInTheMiddleOfEnteringANumber) [self enterPressed];
[self updateStackDisplay:sender.currentTitle];
[self updateStackDisplay:#"="];
double result = [self.brain performOperation:sender.currentTitle];
NSString *resultString = [NSString stringWithFormat:#"%g", result];
self.display.text = resultString;
}
- (IBAction)enterPressed {
if (self.userIsInTheMiddleOfEnteringANumber) {
self.userIsInTheMiddleOfEnteringANumber = NO;
}
[self.brain pushOperand:[self.display.text doubleValue]];
}
- (IBAction)clearPressed {
self.stackDisplay.text = #"";
[self.brain clearStack];
self.userIsInTheMiddleOfEnteringANumber = NO;
self.display.text = #"0";
}
#end
I got a maybe similar error, where some UIButtons had empty text, although by outputting titleLabel.text got the correct text value. I deleted the Buttons and added them identically to what it has before been in the storyboard. Then it worked.

Updating NSView asynchronously from a thread

First of all, I'm an Objective-C novice. So I'm not very familiar with OS X or iOS development. My experience is mostly in Java.
I'm creating an agent-based modeling-framework. I'd like to display the simulations and to do that I'm writing a little application. First, a little bit about the framework. The framework has a World class, in which there is a start method, which iterates over all agents and has them perform their tasks. At the end of one "step" of the world (i.e., after all the agents have done their thing), the start method calls the intercept method of an object that implements InterceptorProtocol. This object was previously passed in via the constructor. Using the interceptor, anyone can get a hook into the state of the world. This is useful for logging, or in the scenario that I'm trying to accomplish: displaying the information in a graphical manner. The call to intercept is synchronous.
Now as far as the GUI app is concerned, it is pretty simple. I have a controller that initializes a custom view. This custom view also implements InterceptorProtocol so that it can listen in, to what happens in the world. I create a World object and pass in the view as an interceptor. The view maintains a reference to the world through a private property and so once I have initialized the world, I set the view's world property to the world I have just created (I realize that this creates a cycle, but I need a reference to the world in the drawRect method of the view and the only way I can have it is if I maintain a reference to it from the class).
Since the world's start method is synchronous, I don't start the world up immediately. In the drawRect method I check to see if the world is running. If it is not, I start it up in a background thread. If it is, I examine the world and display all the graphics that I need to.
In the intercept method (which gets called from start running on the background thread), I set setNeedsToDisplay to YES. Since the start method of the world is running in a separate thread, I also have a lock object that I use to synchronize so that I'm not working on the World object while it's being mutated (this part is kind of janky and it's probably not working the way I expect it to - there are more than a few rough spots and I'm simply trying to get a little bit working; I plan to clean up later).
My problem is that the view renders some stuff, and then it pretty much locks up. I can see that the NSLog statements are being called and so the code is running, but nothing is getting updated on the view.
Here's some of the pertinent code:
MasterViewController
#import "MasterViewController.h"
#import "World.h"
#import "InfectableBug.h"
#interface MasterViewController ()
#end
#implementation MasterViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
_worldView = [[WorldView alloc] init];
World* world = [[World alloc] initWithName: #"Bhumi"
rows: 100
columns: 100
iterations: 2000
snapshotInterval: 1
interceptor: _worldView];
for(int i = 0; i < 999; i++) {
NSMutableString* name = [NSMutableString stringWithString: #"HealthyBug"];
[name appendString: [[NSNumber numberWithInt: i] stringValue]];
[world addBug: [[InfectableBug alloc] initWithWorld: world
name: name
layer: #"FirstLayer"
infected: NO
infectionRadius: 1
incubationPeriod: 10
infectionStartIteration: 0]];
}
NSLog(#"Added all bugs. Going to add infected");
[world addBug: [[InfectableBug alloc] initWithWorld: world
name: #"InfectedBug"
layer: #"FirstLayer"
infected: YES
infectionRadius: 1
incubationPeriod: 10
infectionStartIteration: 0]];
[_worldView setWorld: world];
//[world start];
}
return self;
}
- (NSView*) view {
return self.worldView;
}
#end
WorldView
#import "WorldView.h"
#import "World.h"
#import "InfectableBug.h"
#implementation WorldView
#synthesize world;
- (id) initWithFrame:(NSRect) frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void) drawRect:(NSRect) dirtyRect {
CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort];
CGContextClearRect(myContext, CGRectMake(0, 0, 1024, 768));
NSUInteger rows = [world rows];
NSUInteger columns = [world columns];
NSUInteger cellWidth = 1024 / columns;
NSUInteger cellHeight = 768 / rows;
if([world running]) {
#synchronized (_lock) {
//Ideally we would need layers, but for now let's just get this to display
NSArray* bugs = [world bugs];
NSEnumerator* enumerator = [bugs objectEnumerator];
InfectableBug* bug;
while ((bug = [enumerator nextObject])) {
if([bug infected] == YES) {
CGContextSetRGBFillColor(myContext, 128, 0, 0, 1);
} else {
CGContextSetRGBFillColor(myContext, 0, 0, 128, 1);
}
NSLog(#"Drawing bug %# at %lu, %lu with width %lu and height %lu", [bug name], [bug x] * cellWidth, [bug y] * cellHeight, cellWidth, cellHeight);
CGContextFillRect(myContext, CGRectMake([bug x] * cellWidth, [bug y] * cellHeight, cellWidth, cellHeight));
}
}
} else {
[world performSelectorInBackground: #selector(start) withObject: nil];
}
}
- (BOOL) isFlipped {
return YES;
}
- (void) intercept: (World *) aWorld {
struct timespec time;
time.tv_sec = 0;
time.tv_nsec = 500000000L;
//nanosleep(&time, NULL);
#synchronized (_lock) {
[self setNeedsDisplay: YES];
}
}
#end
start method in World.m:
- (void) start {
running = YES;
while(currentIteration < iterations) {
#autoreleasepool {
[bugs shuffle];
NSEnumerator* bugEnumerator = [bugs objectEnumerator];
Bug* bug;
while((bug = [bugEnumerator nextObject])) {
NSString* originalLayer = [bug layer];
NSUInteger originalX = [bug x];
NSUInteger originalY = [bug y];
//NSLog(#"Bug %# is going to act and location %i:%i is %#", [bug name], [bug x], [bug y], [self isOccupied: [bug layer] x: [bug x] y: [bug y]] ? #"occupied" : #"not occupied");
[bug act];
//NSLog(#"Bug has acted");
if(![originalLayer isEqualToString: [bug layer]] || originalX != [bug x] || originalY != [bug y]) {
//NSLog(#"Bug has moved");
[self moveBugFrom: originalLayer atX: originalX atY: originalY toLayer: [bug layer] atX: [bug x] atY: [bug y]];
//NSLog(#"Updated bug position");
}
}
if(currentIteration % snapshotInterval == 0) {
[interceptor intercept: self];
}
currentIteration++;
}
}
//NSLog(#"Done.");
}
Please let me know if you'd like to see any other code. I realize that the code is not pretty; I was just trying to get stuff to work and I plan on cleaning it up later. Also, if I'm violating an Objective-C best practices, please let me know!
Stepping out for a bit; sorry if I don't respond immediately!
Whew, quiet a question for probably a simple answer: ;)
UI updates have to be performed on the main thread
If I read your code correctly, you call the start method on a background thread. The start method contains stuff like moveBugFrom:... and also the intercept: method. The intercept method thus calls setNeedsDisplay: on a background thread.
Have all UI related stuff perform on the main thread. Your best bet is to use Grand Central Dispatch, unless you need to support iOS < 4 or OS X < 10.6 (or was it 10.7?), like this:
dispatch_async(dispatch_get_main_queue(), ^{
// perform UI updates
});