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.
Related
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.
I have a IKImageBrowserView which has its own controller - BrowserController.h + .m
#interface BrowserController : NSWindowController{
NSMutableArray *_images;
}
#property (strong,nonatomic) IBOutlet IKImageBrowserView *imageBrowser;
-(void)addMultipleImages;
When I run the app for the first time, the staring image loads, but when I click a button to add other images and call a method from another class I don't get any results. I have noticed that my _imageBrowser loses the reference and becomes nil.
How could I solve this issue?
AppDelegate.m
#import "AppDelegate.h"
#import "BrowserController.h"
#implementation AppDelegate{
BrowserController *imageBrowserController;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
imageBrowserController = [BrowserController sharedManager];
}
- (IBAction)doSomething:(id)sender {
[imageBrowserController addMultipleImages];
}
#end
BrowserController.m
#import "BrowserController.h"
#interface myImageObject : NSObject
{
NSString *_path;
}
#end
#implementation myImageObject
/* our datasource object is just a filepath representation */
- (void)setPath:(NSString *)path
{
if(_path != path)
{
_path = path;
}
}
/* required methods of the IKImageBrowserItem protocol */
#pragma mark -
#pragma mark item data source protocol
/* let the image browser knows we use a path representation */
- (NSString *)imageRepresentationType
{
return IKImageBrowserPathRepresentationType;
}
/* give our representation to the image browser */
- (id)imageRepresentation
{
return _path;
}
/* use the absolute filepath as identifier */
- (NSString *)imageUID
{
return _path;
}
#end
#interface BrowserController ()
#end
#implementation BrowserController
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
// Drawing code here.
}
- (void)awakeFromNib{
myImageObject *p;
p = [[myImageObject alloc]init];
[p setPath:[[NSBundle mainBundle]pathForResource:#"Unknown" ofType:#"jpg"]];
_images = [[NSMutableArray alloc]init];
[_images addObject:p];
[self updateDataSource];
}
- (void)updateDataSource{
[_imageBrowser reloadData];
}
-(NSUInteger) numberOfItemsInImageBrowser:(IKImageBrowserView *)aBrowser{
return [_images count];
};
-(id)imageBrowser:(IKImageBrowserView *)aBrowser itemAtIndex:(NSUInteger)index{
return [_images objectAtIndex:index];
};
- (void)updateDatasource
{
[_imageBrowser reloadData];
}
- (void)addMultipleImages{
myImageObject *p;
p = [[myImageObject alloc]init];
[p setPath:[[NSBundle mainBundle]pathForResource:#"Unknown" ofType:#"jpg"]];
_images = [[NSMutableArray alloc]init];
[_images addObject:p];
[_images addObject:p];
[_images addObject:p];
[_imageBrowser reloadData];
}
+ (id)sharedManager {
static BrowserController *sharedMyManager = nil;
#synchronized(self) {
if (sharedMyManager == nil)
sharedMyManager = [[self alloc] init];
}
return sharedMyManager;
}
#end
There is some confusion as to the name of the class where you mention it's called ImageBrowserController at the start of your question and it looks like it's called BrowserController at the end of your question.
The issue is that you allocate _images in awakeFromNib which is never called given the class is created via the singleton pattern (sharedInstance) and not loaded from a .nib file.
Therefore move the code from awakeFromNib into init and dump awakeFromNib:
BrowserController.m:
- (id)init
{
self = [super init];
if (self) {
myImageObject *p = [[myImageObject alloc]init];
[p setPath:[[NSBundle mainBundle]pathForResource:#"Unknown" ofType:#"jpg"]];
_images = [[NSMutableArray alloc]init];
[_images addObject:p];
[self updateDataSource];
}
return self;
}
Further confusion: you have implemented an initWithFrame method. Why?
This application is rewritten code from the Cococa and Objective C Up and Running book.
As I try to understand everything in the beginning, I would like to know, where I made a mistake, in the code below. To me, everything looks fine.
Could you, therefore, help me identify the source of the warning:
Incomplete Implementation
I got this in the #implementation Photo line in Photo.m source code file?
Photo.h
#import <Foundation/Foundation.h>
#interface Photo : NSObject{
NSString* caption;
NSString* photographer;
}
+ (Photo*) photo;
- (NSString*) caption;
- (NSString*) photographer;
- (void) setCaption: (NSString*)input;
- (void) setPhotographer: (NSString*)input;
#end
Photo.m
#import "Photo.h"
#implementation Photo // <- Incomplete Implementation?
- (id)init
{
self = [super init];
if (self) {
[self setCaption:#"Default Caption"];
[self setPhotographer:#"Default Photographer"];
}
return self;
}
+ (Photo*) caption {
Photo* newPhoto = [[Photo alloc] init];
return [newPhoto autorelease];
}
- (NSString*) caption {
return caption;
}
- (NSString*) photographer {
return photographer;
}
- (void) setCaption:(NSString *)input {
[caption autorelease];
caption = [input retain];
}
- (void) setPhotographer: (NSString *)input {
[photographer autorelease];
photographer = [input retain];
}
- (void)dealloc
{
[self setCaption:nil];
[self setPhotographer:nil];
[super dealloc];
}
#end
I use Snow Leopard 10.6.7 and Xcode 4.0.0.
Unless its a typo, your Class method defined as + (Photo*) Photo; is not implemented (there is a + (Photo*) Caption {} method which looks its just an accident.
Edit: A simpler way to do have this functionality is to use properties, which are a shortcut that create the getter and setter for a variable for us, (see this link for a good beginner's tutorial: iPhone 101) for your instance variables like so:
in your .h file:
#interface Photo : NSObject{
NSString* caption;
NSString* photographer;
}
#property (nonatomic, retain) NSString *caption;
#property (nonatomic, retain) NSString *photographer;
#end
in your .m file:
#implementation Photo
#synthesize caption, photographer;
//Other stuff (init and any custom methods for class etc.. NOT getters and setters for variables)
- (void)dealloc
{
[caption release];
[photographer release];
[super dealloc];
}
You are receiving this error because in your header file you declared that there would be a method:
+ (Photo*) photo;
but you didn't implement it in the m file.
EDIT:
It looks like this:
+ (Photo*) caption {
Photo* newPhoto = [[Photo alloc] init];
return [newPhoto autorelease];
}
should be:
+ (Photo*) photo {
Photo* newPhoto = [[Photo alloc] init];
return [newPhoto autorelease];
}
In general, when you mouse over the warning, it will not tell you which method it is missing, but there are at least two other ways to get this information:
Type Cmd-4 or select the Issue Navigator view (the ! in a triangle icon), then expand the "Semantic Issue" warning for this issue. You will then see a message to the effect of "Method definition for "" not found.
Type Cmd-7 or select the Log View (the rightmost icon that looks like a caption bubble), then select the appropriate issue from the list. You will see the same message.
You are missing +photo because you accidentally typed caption:
+ (Photo*) caption {
Photo* newPhoto = [[Photo alloc] init];
return [newPhoto autorelease];
}
should be
+ (Photo*) photo {
Photo* newPhoto = [[Photo alloc] init];
return [newPhoto autorelease];
}
Your .m file does not have the implementation for:
+ (Photo*) photo;
That's the missing method.
try changing
+ (Photo*) caption {
Photo* newPhoto = [[Photo alloc] init];
return [newPhoto autorelease];
}
to
+ (Photo*) photo {
Photo* newPhoto = [[Photo alloc] init];
return [newPhoto autorelease];
}
I've got a leak in my application and I do not know why. Maybe I've got all memory managment thing wrong. In my code I've got UIViewController object which have ivar TelephoneValidator *validator
TelephoneValidator is TelephoneValidator : NSObject
So in my initialization function of UIViewController object (initWithFieldData) I've got:
-(id) initWithFieldData: (NSMutableDictionary*) fieldData
{
...
validatorOptions = [fieldData objectForKey:#"fieldValidator"];
...
}
Now in my viewDidLoad I've got:
- (void)viewDidLoad {
...
if (![validatorOptions respondsToSelector:#selector(isEqualToString:)]) {
validator = [[TelephoneValidator alloc] initWithOptions: validatorOptions];
}
else {
validator = nil;
}
...
}
Basicly if my validatorOptions isn't NSString the validator ivar became TelephoneValidator instance.
In my dealloc:
- (void)dealloc {
if(validator != nil)
{
[validator release];
validator = nil;
}
...
[super dealloc];
}
I've checked a couple of times if dealloc works, and it is. After calling dealloc the validator is released (calling any method on validator after [validator release] gets me exception).
And yet in Instruments it is telling me that TelephoneValidator is leaked. And after double clicking in Instruments the line of code that is highlited is:
validator = [[TelephoneValidator alloc] initWithOptions: validatorOptions];
What am I doing wrong?
UPDATE:
Here is my header information of UIViewController:
#interface GenericViewController : UIViewController <UITextFieldDelegate>{
UIImage *backgroundImage;
NSString *step; // na ktorym kroku jestesmy
id <GenericControllerDelegate> delegate; //delegata z ktorej bedziemy pobierali dane
UITextField *textField;
NSString *fieldName; //nazwa pola (potrzebujemy zeby zapisac do modelu odpowiedni tekst
UILabel *textLabel;
UILabel *stepsLabel;
UILabel *prefixTextLabel;
NSString *fieldPlaceholder;
NSString *textLabelText;
NSString *textLabelTextPl; //w jezyku polskim
NSString *prefixTextLabelText; //w jezyku eng
NSString *prefixTextLabelTextPl; //w jezyku polskim prefix
NSString *fieldRequired;
NSString *keyboardType;
NSString *capitalizeType;
UIButton *button; //forward button
UIButton *button2; //backward button
//to bedzie do przerobienia bo bedziemy mieli tablicje walidatorow a nie jeden walidator
NSString *validatorType;
//maksymalna dlugosc pola
int maxLengthOfTextField;
NSArray* validatorOptions;
TelephoneValidator *validator;
//patientModel
PatientData *patientModel;
}
TelephoneValidator header:
#import <Foundation/Foundation.h>
#import "MAOTranslate.h"
#interface TelephoneValidator : NSObject {
//opcje walidacyjne
NSString *phonePrefix;
NSString *phonePostfix;
int phoneLength;
NSString *message;
NSString *messagePl;
UIAlertView *alertView;
}
-(id) initWithOptions:(NSArray *) optionsArray;
-(void) displayMessage;
-(BOOL) validate: (NSString *) phoneNumber;
#end
TelephoneValidator class:
#import "TelephoneValidator.h"
#implementation TelephoneValidator
//#synthesize phoneNumber;
-(id) initWithOptions:(NSArray *) optionsArray;
{
if(self = [[TelephoneValidator alloc] init])
{
phonePrefix = [optionsArray objectAtIndex:0];
phonePostfix = [optionsArray objectAtIndex:1];
phoneLength = [[optionsArray objectAtIndex:2] intValue];
message = [optionsArray objectAtIndex:3];
messagePl = [optionsArray objectAtIndex:4];
}
else {
self = nil;
}
return self;
}
//wyswietlamy wiadomosc
-(void) displayMessage
{
NSString *displayMsg;
if ([[MAOTranslate getLanguage] isEqualToString:#"pl"]) {
displayMsg = messagePl;
}
else {
displayMsg = message;
}
alertView = [[UIAlertView alloc] initWithTitle:#"Alert" message:displayMsg delegate:self cancelButtonTitle:#"ok" otherButtonTitles:nil];
[alertView show];
}
-(BOOL) validate: (NSString *) phoneNumber
{
//dlugosc
if ([phoneNumber length] != phoneLength) {
NSLog(#"zla dlugosc");
return NO;
}
NSLog(#"tutaj");
//sprawdzamy prefix
if ([phonePrefix length]!= 0) {
NSLog(#"w srodku ifa");
if ([phoneNumber compare:phonePrefix options:NSLiteralSearch range:NSMakeRange(0, [phonePrefix length])] != 0) {
NSLog(#"zly prefix");
[self displayMessage];
return NO;
}
}
//sprawdzamy postfix
if([phonePostfix length] != 0)
{
if ([phoneNumber compare:phonePostfix options:NSLiteralSearch range:NSMakeRange([phoneNumber length]-[phonePostfix length], [phonePostfix length])] != 0) {
NSLog(#"zly postfix");
[self displayMessage];
return NO;
}
}
//sprawdzamy czy string jest numeryczny
NSCharacterSet *alphaNums = [NSCharacterSet decimalDigitCharacterSet];
NSCharacterSet *inStringSet = [NSCharacterSet characterSetWithCharactersInString:phoneNumber];
if (![alphaNums isSupersetOfSet:inStringSet])
{
NSLog(#"zly format ");
[self displayMessage];
return NO;
}
return YES; //zwalidowany poprawnie
}
-(void) dealloc
{
[alertView release];
alertView = nil;
[super dealloc];
}
You need to call [super dealloc] at the end of the dealloc method.
See These both lines
validator = [[TelephoneValidator alloc] initWithOptions: validatorOptions];
and inside initWithOptions
if(self = [[TelephoneValidator alloc] init])
You are allocing twice the validator, so there is a leak.
Could it be that instruments is pointing to validatorOptions as the source of the leak? Is it a retained property being released at dealloc or not? I can't say for sure, the code you posted is not enough to arrive to a conclusion.
Also, as willcodejavaforfood says, you must always call [super dealloc]; at the end of your dealloc method. No code must come after it.
Edit:
I'm back. But Bruno Domingues got it right already, you are allocating twice, in which case, the first one leaks. You should change your -initWithOptions: code to:
-(id) initWithOptions:(NSArray *) optionsArray;
{
if((self = [super init])){
// ... rest of code is fine
}
return self;
}
I've been working through the exercises in a book recommended here on stackoverflow, however I've run into a problem and after three days of banging my head on the wall, I think I need some help.
I'm working through the "Speakline" exercise where we add a TableView to the interface and the table will display the "voices" that you can choose for the text to speech aspect of the program.
I am having two problems that I can't seem to get to the bottom of:
I get the following error: *** Illegal NSTableView data source (). Must implement numberOfRowsInTableView: and tableView:objectValueForTableColumn:row:
The tableView that is supposed to display the voices comes up blank
I have a feeling that both of these problems are related.
I'm including my interface code here:
#import <Cocoa/Cocoa.h>
#interface AppController : NSObject <NSSpeechSynthesizerDelegate, NSTableViewDelegate>
{
IBOutlet NSTextField *textField;
NSSpeechSynthesizer *speechSynth;
IBOutlet NSButton *stopButton;
IBOutlet NSButton *startButton;
IBOutlet NSTableView *tableView;
NSArray *voiceList;
}
- (IBAction)sayIt:(id)sender;
- (IBAction)stopIt:(id)sender;
#end
And my implementation code here:
#import "AppController.h"
#implementation AppController
- (id)init
{
[super init];
//Log to help me understand what is happening
NSLog(#"init");
speechSynth = [[NSSpeechSynthesizer alloc] initWithVoice:nil];
[speechSynth setDelegate:self];
voiceList = [[NSSpeechSynthesizer availableVoices] retain];
return self;
}
- (IBAction)sayIt:(id)sender
{
NSString *string = [[textField stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
//Is the string zero-length?
if([string length] == 0) {
NSLog(#"String from %# is a string with a length of %d.", textField, [string length]);
[speechSynth startSpeakingString:#"Please enter a phrase first."];
}
[speechSynth startSpeakingString:string];
NSLog(#"Started to say: %#", string);
[stopButton setEnabled:YES];
[startButton setEnabled:NO];
}
- (IBAction)stopIt:(id)sender
{
NSLog(#"Stopping...");
[speechSynth stopSpeaking];
}
- (void) speechSynthesizer:(NSSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)complete
{
NSLog(#"Complete = %d", complete);
[stopButton setEnabled:NO];
[startButton setEnabled:YES];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
return [voiceList count];
}
- (id)tableView: (NSTableView *)tv objecValueForTableColumn: (NSTableColumn *)tableColumn
row:(NSInteger)row
{
NSString *v = [voiceList objectAtIndex:row];
NSLog(#"v = %#",v);
NSDictionary *dict = [NSSpeechSynthesizer attributesForVoice:v];
return [dict objectForKey:NSVoiceName];
}
/*
- (BOOL)respondsToSelector:(SEL)aSelector
{
NSString *methodName = NSStringFromSelector(aSelector);
NSLog(#"respondsToSelector: %#", methodName);
return [super respondsToSelector:aSelector];
}
*/
#end
Hopefully, you guys can see something obvious that I've missed.
Thank you!
objecValueForTableColumn is not the same as objectValueForTableColumn. When it comes to delegates and data sources, I recommend never typing the method names if you can avoid it - it causes exactly this kind of problem. If you copy & paste the method signature out of the documentation you can be safer. Good luck with your learning!