Removing an element from NSDictionary causes it to be deallocated prematurely - objective-c

Full project can be seen here (for context: https://github.com/atlas-engineer/next-cocoa)
The following code returns EXC_BAD_ACCESS:
- (bool)windowClose:(NSString *)key
{
NSWindow *window = [[self windows] objectForKey:key];
[[self windows] removeObjectForKey:key];
[window close];
return YES;
}
The following code, however, works
- (bool)windowClose:(NSString *)key
{
[[self windows] removeObjectForKey:key];
return YES;
}
As does the following:
- (bool)windowClose:(NSString *)key
{
NSWindow *window = [[self windows] objectForKey:key];
[window close];
return YES;
}
It is only somehow when you put them together that everything breaks.
For reference, I've provided the AutokeyDictionary implementation below, which is the value of [self windows] in the examples above
//
// AutokeyDictionary.m
// next-cocoa
//
// Created by John Mercouris on 3/14/18.
// Copyright © 2018 Next. All rights reserved.
//
#import "AutokeyDictionary.h"
#implementation AutokeyDictionary
#synthesize elementCount;
- (instancetype) init
{
self = [super init];
if (self)
{
[self setElementCount:0];
_dict = [[NSMutableDictionary alloc] init];
}
return self;
}
- (NSString *) insertElement:(NSObject *) object
{
NSString *elementKey = [#([self elementCount]) stringValue];
[_dict setValue:object forKey: elementKey];
[self setElementCount:[self elementCount] + 1];
return elementKey;
}
- (NSUInteger)count {
return [_dict count];
}
- (id)objectForKey:(id)aKey {
return [_dict objectForKey:aKey];
}
- (void)removeObjectForKey:(id)aKey {
return [_dict removeObjectForKey:aKey];
}
- (NSEnumerator *)keyEnumerator {
return [_dict keyEnumerator];
}
- (NSArray*)allKeys {
return [_dict allKeys];
}
#end
Lastly, for the record, turning on zombies does make the code work, though that is obviously not a solution.

Your window's releasedWhenClosed property is probably defaulting to YES, which is likely to conflict with ARC's memory management. Set it to NO when you create the window.

The correct answer ended up being the following sequence of code
- (bool)windowClose:(NSString *)key
{
NSWindow *window = [[self windows] objectForKey:key];
[window setReleasedWhenClosed:NO];
[window close];
[[self windows] removeObjectForKey:key];
return YES;
}
any other order of events and the object would be prematurely released.

Related

UISearchController results are not being filtered

So I am using the UISearchController in my project, and it seems to work all fine but whatever I type on the Search Bar no I get no results. So I believe the problem is on my
(void)updateSearchResultsForSearchController:(UISearchController *)searchController {
}. To be honest, I don't really know what code to put in that method. Here is the rest of the code:
#property (nonatomic, strong) UISearchController *searchController;
#property (nonatomic, strong) SearchResultsTableViewController *resultsTableController;
#property (nonatomic, strong) NSMutableArray *searchResults; // Filtered search results
// for state restoration
#property BOOL searchControllerWasActive;
#property BOOL searchControllerSearchFieldWasFirstResponder;
_resultsTableController = [[SearchResultsTableViewController alloc] init];
_searchController = [[UISearchController alloc] initWithSearchResultsController:self.resultsTableController];
self.searchController.searchResultsUpdater = self;
[self.searchController.searchBar sizeToFit];
self.tableView.tableHeaderView = self.searchController.searchBar;
self.searchController.delegate = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.searchController.searchBar.delegate = self;
self.searchController.searchBar.tintColor = [UIColor darkGrayColor];
self.definesPresentationContext = YES;
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// restore the searchController's active state
if (self.searchControllerWasActive) {
self.searchController.active = self.searchControllerWasActive;
_searchControllerWasActive = NO;
if (self.searchControllerSearchFieldWasFirstResponder) {
[self.searchController.searchBar becomeFirstResponder];
_searchControllerSearchFieldWasFirstResponder = NO;
}
}}
#pragma mark - UISearchBarDelegate
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
[searchBar resignFirstResponder];}
#pragma mark - UISearchResultsUpdating
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
}
You'll need to take the NSArray that your table view data comes from, filter it as per your search criteria, and then reload the table view. Your code might look something like this:
- (void) doSearch:(NSString *)searchText {
[self.filteredClients removeAllObjects];
if ( [searchText length] == 0 ) {
[self.filteredClients addObjectsFromArray:self.users];
} else {
for (User *user in self.users) {
NSString *name = [user fullName];
NSRange range = [[name lowercaseString] rangeOfString:[searchText lowercaseString]];
if ( range.location != NSNotFound )
[self.filteredClients addObject:user];
}
}
[self.tableView reloadData];
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if ( [searchText length] > 0 )
{
[self showCancelButton:YES];
}
else {
[self showCancelButton:NO];
[searchBar resignFirstResponder];
}
[self doSearch:searchText];
}
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
[self showCancelButton:YES];
}
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{
if ( [searchBar.text length] > 0 ) [self showCancelButton:YES];
else [self showCancelButton:NO];
[self doSearch:_searchBar.text];
}

Custom NSPopupButtonCell in NSTableView

I am trying to create a custom popup menu in a tableview. As I understand it I can should be able to do so by calling the [NSPopupButtonCell setView:myView] method passing in the custom view (which is just a NSView with an NSOutlineView in it).
So I have created a NSPopupButtonCell subclass and during initialisation I call setView and pass in the custom outline view..
EDIT
In IB I have set the table columns cell to a Popup Button Cell and set
the class to my custom LookupPopupButtonCell.
I still don't get my custom view displaying but my custom classes
initialisation methods appear to be getting called.
I have since replaced this approach with using a NSTableViewDelegate method dataCellForTableColumn. Now the popup shows my custom tableView.
Still no joy in getting the NSOutlineViewDelegate methods called though.
EDIT
OK I have managed to get things working using a NSPopupButton on a view. delegate works find and table view displays things fine. Seems that using NSPopupButtonCell the delegate methods never get called.
#implementation LookupPopupButtonCell
- (id)init
{
LOG(#"init called");
self = [super init];
if (self) {
[self initialise];
}
return self;
}
- (void)initialise
{
LOG(#"initialise called");
[self setFont:[NSFont fontWithName:#"System Regular" size:11]];
[self setBordered:NO];
[self setBezeled:NO];
// Set the Task Lookup Popup Menu
NSMenu *newLookupMenu = [[NSMenu allocWithZone:[NSMenu menuZone]] initWithTitle:#"Custom"];
NSMenuItem *newItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:#"Lookup" action:nil keyEquivalent:#""];
[newItem setEnabled:YES];
TaskLookupViewController *viewController = [[TaskLookupViewController alloc] initWithNibName:#"TaskLookupViewController" bundle:nil];
[newItem setView:[viewController view]];
[newLookupMenu addItem:newItem];
[newItem release];
[self setMenu:newLookupMenu];
}
#end
#implementation TaskLookupViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialization code here.
[self initialise];
}
return self;
}
- (void)awakeFromNib{
LOG(#"awakeFromNib called...");
[self viewDidLoad];
}
- (void)viewDidLoad {
FLOG(#"viewDidLoad called for %#", self);
/*
FLOG(#" _outlineView is %#", _outlineView);
FLOG(#" _outlineView is %#", [_outlineView identifier]);
FLOG(#" _outlineView delegate is %#", [_outlineView delegate]);
FLOG(#" _outlineView dataSource is %#", [_outlineView dataSource]);
*/
[_outlineView setDataSource:self];
[_outlineView setDelegate:self];
[_outlineView reloadData];
[_outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO];
[_outlineView setNeedsDisplay];
[_outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:2] byExtendingSelection:NO];
/*
FLOG(#" _outlineView delegate is %#", [_outlineView delegate]);
FLOG(#" _outlineView dataSource is %#", [_outlineView dataSource]);
*/
//NSTableColumn *tableColumn = [[_outlineView tableColumns] objectAtIndex:0];
//LOG(#" setting bindings");
//[tableColumn bind: #"value" toObject: _treeController withKeyPath: #"arrangedObjects.displayName" options: nil];
}
- (void)initialise {
LOG(#"initialise called");
_topLevelItems = [[NSArray arrayWithObjects:#"Project", #"Tasks and Deliverables", #"Finance", nil] retain];
_childrenDictionary = [NSMutableDictionary new];
[_childrenDictionary setObject:[NSArray arrayWithObjects:#"Scope", nil] forKey:#"Project"];
//[_childrenDictionary setObject:[NSArray arrayWithObjects:#"Issues", #"Risks", nil] forKey:#"Quality"];
[_childrenDictionary setObject:[NSArray arrayWithObjects:#"WBS", #"Functions", nil] forKey:#"Tasks and Deliverables"];
[_childrenDictionary setObject:[NSArray arrayWithObjects:#"Expenses", #"Ongoing Costs", #"Timesheets", nil] forKey:#"Finance"];
//[_childrenDictionary setObject:[NSArray arrayWithObjects:#"Applications", #"Interfaces", nil] forKey:#"IT Systems"];
//[_childrenDictionary setObject:[NSArray arrayWithObjects:#"People", #"Setup", nil] forKey:#"Administration"];
}
- (NSArray *)_childrenForItem:(id)item {
LOG(#"_childrenForItem called");
NSArray *children;
if (item == nil) {
children = _topLevelItems;
} else {
children = [_childrenDictionary objectForKey:item];
}
//FLOG(#" children are %#", children);
return children;
}
#end
#implementation TaskLookupViewController (NSOutlineViewDataSource)
- (void)outlineViewSelectionDidChange:(NSNotification *)notification {
LOG(#"outlineViewSelectionDidChange: called");
if ([_outlineView selectedRow] != -1) {
NSObject *item = [_outlineView itemAtRow:[_outlineView selectedRow]];
if ([_outlineView parentForItem:item] != nil) {
// Only change things for non-root items (root items can be selected, but are ignored)
FLOG(#" selected item is %#", item);
}
}
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
FLOG(#"outlineView:child:ofItem: called for item %#", item);
return [[self _childrenForItem:item] objectAtIndex:index];
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
LOG(#"outlineView:isItemExpandable: called");
if ([outlineView parentForItem:item] == nil) {
return YES;
} else {
return YES;
}
}
- (NSInteger) outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
LOG(#"outlineView:numberOfChildrenOfItem: called");
FLOG(#" children count is %d", [[self _childrenForItem:item] count]);
return [[self _childrenForItem:item] count];
}
#end
#implementation TaskLookupViewController (NSOutlineViewDelegate)
- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
LOG(#"outlineView:isGroupItem: called");
return [_topLevelItems containsObject:item];
}
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
LOG(#"willDisplayCell called");
[cell setTitle:#"Cell Title"];
}
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
LOG(#"outlineView:viewForTableColumn called");
// We just return a regular text view.
if ([_topLevelItems containsObject:item]) {
NSTextField *result = [outlineView makeViewWithIdentifier:#"HeaderTextField" owner:self];
// Uppercase the string value, but don't set anything else. NSOutlineView automatically applies attributes as necessary
NSString *value = [item uppercaseString];
[result setStringValue:value];
return result;
} else {
NSTextField *result = [outlineView makeViewWithIdentifier:#"ItemTextField" owner:self];
[result setStringValue:value];
return result;
}
}
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item;
{
LOG(#"outlineView:shouldExpandItem: called");
return YES;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;
{
LOG(#"outlineView:shouldSelectItem: called");
return YES;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldCollapseItem:(id)item;
{
LOG(#"outlineView:shouldCollapseItem: called");
return NO;
}
#end
#implementation TaskLookupViewController (NSTableViewDelegate)
- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
LOG(#"tableView:dataCellForTableColumn:row: called");
NSString *identifier = [tableColumn identifier];
if ([identifier isEqualToString:#"task"]) {
//LOG(#" task column setting the cell");
LookupPopupButtonCell *cellView = [[LookupPopupButtonCell alloc] init];
return cellView;
}
NSTextFieldCell *cellView = [tableView makeViewWithIdentifier:#"hoursCell" owner:self];
return cellView;
}
#end
It seems you must use a VIEW based tableView to get the delegate messages. Blah, now to figure out how to bind one of them to Core Data, hopefully its not too hard !
Is there any way to reuse the same menu for each row ? I guess as long as the dataSource is not recreated each time its probably not too bad, still there could be lots of rows in this hierarchy!

Objective-C calling instance method within Singleton

I created a Singleton class named DataManager. I've set up some caching. However, when attempting to call the cache instance methods from within another instance method of DataManager, I'm getting EXC_BAD_ACCESS error on the line:
NSMutableArray *stops = [[DataManager sharedInstance] getCacheForKey:#"stops"];
DataManager.h
#interface DataManager : NSObject {
FMDatabase *db;
NSMutableDictionary *cache;
}
+ (DataManager *)sharedInstance;
// ... more methods
- (id)getCacheForKey:(NSString *)key;
- (void)setCacheForKey:(NSString *)key withValue:(id)value;
- (void)clearCache;
DataManager.m
- (NSArray *)getStops:(NSInteger)archived {
NSMutableArray *stops = [[DataManager sharedInstance] getCacheForKey:#"stops"];
if (stops != nil) {
NSLog(#"Stops: %#", stops);
return stops;
}
stops = [NSMutableArray array];
// set stops...
[[DataManager sharedInstance] setCacheForKey:#"stops" withValue:stops];
return stops;
}
It seems to occur when called from another view controller. That is the first view controller no error, second view controller, error.
This is my first attempt at a Singleton, so I'm sure I am making a simple mistake. But I am failing to see it myself.
Note: I've tried [self getCache...] with the same result.
UPDATE
Here is my Singleton implementation. Adapted from http://www.galloway.me.uk/tutorials/singleton-classes/
+ (DataManager *)sharedInstance {
#synchronized(self) {
if (!instance) {
instance = [[super allocWithZone:NULL] init];
}
}
return instance;
}
+ (id)allocWithZone:(NSZone *)zone {
return [[self sharedInstance] retain];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX;
}
- (oneway void)release {
// never release
}
- (id)autorelease {
return self;
}
- (id)init {
if (self = [super init]) {
if (db == nil){
BourbonAppDelegate *appDelegate = (BourbonAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate createEditableCopyOfDatabaseIfNeeded];
db = [[FMDatabase alloc] initWithPath:[appDelegate getDBPath]];
}
if (![db open]) {
NSAssert(0, #"Failed to open database.");
[db release];
return nil;
}
[db setTraceExecution:YES];
[db setLogsErrors:TRUE];
cache = [NSMutableDictionary dictionary];
NSLog(#"cache: %#", cache);
}
return self;
}
Your cache object is autoreleased, so it's no longer in memory when you try to access it.
Use [NSMutableDictionary alloc] init] instead of [NSMutableDictionary dictionary] to get an instance that is retained.
Not a direct answer to your question, which was already answered.
However I'd just like to point out that your singleton implementation is sub-optimal. #synchronized is very expensive, and you can avoid using it every time you access the singleton:
if (!instance) {
#synchronized(self) {
if (!instance) {
instance = [[super allocWithZone:NULL] init];
}
}
}
An even better way to initialize a singleton would be:
+ (DataManager *)sharedInstance {
static DataManager *instance;
static dispatch_once_t donce;
dispatch_once(&donce, ^{
instance = [[self alloc] init];
});
return instance;
}

No known class method for selector 'sharedStore'

Have a singleton class for BNRItemStore, but when I tried to call it, I get the above error which causes an ARC issue. Have commented out the error.
DetailViewController.m
#import "DetailViewController.h"
#import "BNRItem.h"
#import "BNRImageStore.h"
#import "BNRItemStore.h"
#implementation DetailViewController
#synthesize item;
-(id)initForNewItem:(BOOL)isNew
{
self = [super initWithNibName:#"DetailViewController" bundle:nil];
if(self){
if (isNew) {
UIBarButtonItem *doneItem = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:#selector(save:)];
[[self navigationItem] setRightBarButtonItem:doneItem];
UIBarButtonItem *cancelItem = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self
action:#selector(cancel:)];
[[self navigationItem] setLeftBarButtonItem:cancelItem];
}
}
return self;
}
-(id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle
{
#throw [NSException exceptionWithName:#"Wrong initializer"
reason:#"Use initForNewItem:"
userInfo:nil];
return nil;
}
-(void)viewDidLoad
{
[super viewDidLoad];
UIColor *clr = nil;
if ([[UIDevice currentDevice]userInterfaceIdiom]== UIUserInterfaceIdiomPad) {
clr = [UIColor colorWithRed:0.875 green:0.88 blue:0.91 alpha:1];
} else {
clr = [UIColor groupTableViewBackgroundColor];
}
[[self view]setBackgroundColor:clr];
}
- (void)viewDidUnload {
nameField = nil;
serialNumberField = nil;
valueField = nil;
dateLabel = nil;
imageView = nil;
[super viewDidUnload];
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[nameField setText:[item itemName]];
[serialNumberField setText:[item serialNumber]];
[valueField setText:[NSString stringWithFormat:#"%d", [item valueInDollars]]];
// Create a NSDateFormatter that will turn a date into a simple date string
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterNoStyle];
// Use filtered NSDate object to set dateLabel contents
[dateLabel setText:[dateFormatter stringFromDate:[item dateCreated]]];
NSString *imageKey = [item imageKey];
if (imageKey) {
// Get image for image key from image store
UIImage *imageToDisplay = [[BNRImageStore sharedStore]imageForKey:imageKey];
// Use that image to put on the screen in imageview
[imageView setImage:imageToDisplay];
} else {
// Clear the imageview
[imageView setImage:nil];
}
}
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// Clear first responder
[[self view]endEditing:YES];
// "Save" changes to item
[item setItemName:[nameField text]];
[item setSerialNumber:[serialNumberField text]];
[item setValueInDollars:[[valueField text] intValue]];
}
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)io
{
if ([[UIDevice currentDevice]userInterfaceIdiom]==UIUserInterfaceIdiomPad) {
return YES;
} else {
return (io==UIInterfaceOrientationPortrait);
}
}
-(void)setItem:(BNRItem *)i
{
item = i;
[[self navigationItem] setTitle:[item itemName]];
}
- (IBAction)takePicture:(id)sender {
if ([imagePickerPopover isPopoverVisible]) {
// If the popover is already up, get rid of it
[imagePickerPopover dismissPopoverAnimated:YES];
imagePickerPopover = nil;
return;
}
UIImagePickerController *imagePicker =
[[UIImagePickerController alloc]init];
// If our device has a camera, we want to take a picture, otherwise, we
// just pick from the photo library
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
[imagePicker setSourceType:UIImagePickerControllerSourceTypeCamera];
} else {
[imagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
// This line of code will generate a warning right now, ignore it
[imagePicker setDelegate:self];
//Place image picker on the screen
// Check for iPad device before instantiating the popover controller
if ([[UIDevice currentDevice]userInterfaceIdiom]==UIUserInterfaceIdiomPad) {
// Create a new popover controller that will display the imagepicker
imagePickerPopover = [[UIPopoverController alloc]initWithContentViewController:imagePicker];
[imagePickerPopover setDelegate:self];
// Display the popover controller; sender
// is the camera bar button item
[imagePickerPopover presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
} else {
[self presentViewController:imagePicker animated:YES completion:nil];
}
}
}
-(void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
{
NSLog(#"User dismissed popover");
imagePickerPopover = nil;
}
- (IBAction)backgroundTapped:(id)sender {
[[self view]endEditing:YES];
}
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
NSString *oldKey = [item imageKey];
// Did the item already have an image?
if (oldKey) {
// Delete the old image
[[BNRImageStore sharedStore]deleteImageForKey:oldKey];
}
UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
// Create a CFUUID object - it knows how to create unique identifier strings
CFUUIDRef newUniqueID = CFUUIDCreate(kCFAllocatorDefault);
// Create a string from unique identifier
CFStringRef newUniqueIDString = CFUUIDCreateString(kCFAllocatorDefault, newUniqueID); // Incompatible integer to pointer conversion initializing
// Use that unique ID to set our item's imageKey
NSString *key = (__bridge NSString *)newUniqueIDString;
[item setImageKey:key];
// Store image in the BNRImageStore with this key
[[BNRImageStore sharedStore] setImage:image forKey:[item imageKey]];
CFRelease(newUniqueIDString);
CFRelease(newUniqueID);
[imageView setImage:image];
if ([[UIDevice currentDevice]userInterfaceIdiom]==UIUserInterfaceIdiomPad) {
// If on the phone, the image picker is presented modally. Dismiss it.
[self dismissViewControllerAnimated:YES completion:nil];
} else {
// If on the pad, the image picker is in the popover. Dismiss the popover.
[imagePickerPopover dismissPopoverAnimated:YES];
imagePickerPopover = nil;
}
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
-(void)save:(id)sender
{
[[self presentingViewController]dismissViewControllerAnimated:YES
completion:nil];
}
-(void)cancel:(id)sender
{
// If the user cancelled, then remove the BNRItem from the store
[[BNRItemStore sharedStore]removeItem:item]; // No known class method for selector 'sharedStore'
[[self presentingViewController]dismissViewControllerAnimated:YES completion:nil];
}
DetailViewController.h
#import <UIKit/UIKit.h>
#class BNRItem;
#interface DetailViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate,UITextFieldDelegate, UIPopoverControllerDelegate>
{
__weak IBOutlet UITextField *nameField;
__weak IBOutlet UITextField *serialNumberField;
__weak IBOutlet UITextField *valueField;
__weak IBOutlet UILabel *dateLabel;
__weak IBOutlet UIImageView *imageView;
UIPopoverController *imagePickerPopover;
}
#property(nonatomic,strong)BNRItem *item;
-(id)initForNewItem:(BOOL)isNew;
- (IBAction)takePicture:(id)sender;
- (IBAction)backgroundTapped:(id)sender;
#end
BNRItemStore.m
#import "BNRItemStore.h"
#import "BNRItem.h"
#implementation BNRItemStore
+ (BNRItemStore *)defaultStore
{
static BNRItemStore *defaultStore = nil;
if(!defaultStore)
defaultStore = [[super allocWithZone:nil] init];
return defaultStore;
}
+ (id)allocWithZone:(NSZone *)zone
{
return [self defaultStore];
}
- (id)init
{
self = [super init];
if(self) {
allItems = [[NSMutableArray alloc] init];
}
return self;
}
- (void)removeItem:(BNRItem *)p
{
[allItems removeObjectIdenticalTo:p];
}
- (NSArray *)allItems
{
return allItems;
}
- (void)moveItemAtIndex:(int)from
toIndex:(int)to
{
if (from == to) {
return;
}
// Get pointer to object being moved so we can re-insert it
BNRItem *p = [allItems objectAtIndex:from];
// Remove p from array
[allItems removeObjectAtIndex:from];
// Insert p in array at new location
[allItems insertObject:p atIndex:to];
}
- (BNRItem *)createItem
{
BNRItem *p = [BNRItem randomItem];
[allItems addObject:p];
return p;
}
#end
BNRItemStore.h
#import <Foundation/Foundation.h>
#class BNRItem;
#interface BNRItemStore : NSObject
{
NSMutableArray *allItems;
}
+ (BNRItemStore *)defaultStore;
- (void)removeItem:(BNRItem *)p;
- (NSArray *)allItems;
- (BNRItem *)createItem;
- (void)moveItemAtIndex:(int)from
toIndex:(int)to;
#end
You are calling +sharedStore on BNRItemStore where your error occurs. This is because it really does not exist according to the code you posted.
All of the others calling +sharedStore are using the one presumably made available by BNRImageStore which you didn't provide. I assume it exists in that class? :)
In short, change:
[[BNRItemStore sharedStore]removeItem:item];
to
[[BNRImageStore sharedStore]removeItem:item];

The document “Untitled” could not be saved as "Untitled"

I am trying to develop a document based mac app using this Apple walkthrough and I am having issues saving the file (the final step).  The error that I am getting after I try to save a file is: The document "Untitled" could not be saved as "- the new filename is I'm trying to use -"
I've googled around and not found any results for this error.  I've rechecked the code and everything seems pretty solid to the tutorial.  I wondered if anybody has any intuition as to what might be going wrong here.
The code of my main class is:
#import "MyDocument.h"
#implementation MyDocument
- (id)init
{
self = [super init];
if (self) {
if (mString == nil) {
mString = [[NSAttributedString alloc] initWithString:#""];
}
}
return self;
}
- (NSAttributedString *) string { return [[mString retain] autorelease]; }
- (void) setString: (NSAttributedString *) newValue {
if (mString != newValue) {
if (mString) [mString release];
mString = [newValue copy];
}
}
- (void) textDidChange: (NSNotification *)notification {
[self setString: [textView textStorage]];
}
- (NSString *)windowNibName
{
// Override returning the nib file name of the document
// If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.
return #"MyDocument";
}
- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
[super windowControllerDidLoadNib:aController];
if ([self string] != nil) {
[[textView textStorage] setAttributedString: [self string]];
}
}
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
{
BOOL readSuccess = NO;
NSAttributedString *fileContents = [[NSAttributedString alloc]
initWithData:data options:NULL documentAttributes:NULL
error:outError];
if (fileContents) {
readSuccess = YES;
[self setString:fileContents];
[fileContents release];
}
return readSuccess;
}
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
NSData *data;
[self setString:[textView textStorage]];
NSMutableDictionary *dict = [NSDictionary dictionaryWithObject:NSRTFTextDocumentType
forKey:NSDocumentTypeDocumentAttribute];
[textView breakUndoCoalescing];
data = [[self string] dataFromRange:NSMakeRange(0, [[self string] length])
documentAttributes:dict error:outError];
return data;
}
Header file:
#import <Cocoa/Cocoa.h>
#interface MyDocument : NSDocument
{
IBOutlet NSTextView *textView;
NSAttributedString *mString;
}
- (NSAttributedString *)string;
- (void) setString: (NSAttributedString *)value;
#end
In your -dataOfType:error: method, when you assign something to data, are you sure it's not nil? Returning nil will cause this error.
I rebuilt the project from scratch with one exception: I didn't follow the step of dragging my MyDocument class to the File's Owner. The tutorial was written for a previous version of XCode even though it says it's for 3.2 (or maybe that much has happened in that version and now), but that step is unnecessary.