I've read several posts similar to this one but I found them too specific. What I really want is a more general answer. According to Apple's view controller programming guide, viewDidLoad: should be used to "Allocating or loading data to be displayed in your view". If I have some data that has nothing to do with display, where should I initialize them?
Some posts suggests that initialization could be done in initWithCoder: when the view controller is initialized via a storyboard. I've tried to initialize an array in initWithCoder:, but after that it turned out that the array is still empty. So can we write a designated initializer to initialize this kind of data?
Here is the code:
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
// load notes
NSString * path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
_notes = [[PWCNotes alloc] initNotesWithFilename:self.docTitle path:path numberOfPages:self.numberOfPages];
_index = 0;
}
return self;
}
Here is the designated initializer method for PWCNotes
- (id)initNotesWithFilename:(NSString *)fileName path:(NSString *)path numberOfPages:(int)numberOfPages
{
if (!(self = [super init])) {
return nil;
}
_filePath = [path stringByAppendingString:[NSString stringWithFormat:#"/%#/notes.txt", fileName]];
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:self.filePath];
if (!exists)
{
// if file does not exist, create one and initialize the content
_notes = [[NSMutableArray alloc] init];
[[NSFileManager defaultManager] createFileAtPath:self.filePath contents:nil attributes:nil];
NSString * emptyString = #"Add Notes Here!";
for (int i = 0; i < numberOfPages; ++i)
{
[self.notes addObject:emptyString];
}
// write content of the array to the file
[self.notes writeToFile:self.filePath atomically:YES];
}
else
{
// otherwise, load it from the text file
_notes = [[NSMutableArray alloc] initWithContentsOfFile:self.filePath];
}
return self;
}
PWCNotes class has as a property a mutable array of NSString *s. When I call [self.notes.notes getObjectAtIndex:self.index], an NSRangeException is thrown, saying that I'm trying to access the object at index 0 in an empty array. Am I missing something?
viewDidLoad is the best place if you need to use the data only once the view appears, which is usually the case for View Controllers, as they only exists for managing views.
It's the best place because it is the one that's called in any case, no matter how the class got initialized.
If you really wanted to have the data ready earlier, you could implement an +initialize or +load method (NSObject +load and +initialize - What do they do?) instead, but that's not really what you should do in a View Controller.
Related
I guess its a very basic memory concept. But couldn't figure it out what happens with below case. Any insight would be helpful.
This could be similar to Problems with NSString inside viewWillDisappear
But I wanted to know why there requires a #property. How can we do it without taking #property. Please provide some inside view.
in .h I have NSString *someString
in .mm (this is my non-ARC cocos2d+box2d game scene)
-(id)initWithString:(NSString *)tempString
{
if(self = [super init])
{
someString = [[NSString allo]init];
someString = tempString;
}
return self;
}
-(void)onEnterTransitionDidfinish
{
[super onEnterTransitionDidfinish];
NSLog("The String is %#",someString);//Becomes nil here
}
-(void)printString
{
NSLog(#"The String is %#",someString);//This works fine
}
If you are not using ARC then you need to learn a lot more about memory management.
The following two lines:
someString = [[NSString allo]init];
someString = tempString;
should be:
someString = [tempString copy]; // or [tempString retain];
And be sure you call [someString release] in your dealloc method.
BTW - you are not using a property. someString is declared as an instance variable, not a property.
I cannot figure out what is wrong with my archiving and unarchiving. I am trying to save data from a class. The encoder and decoder are:
//archiving
- (void)encodeWithCoder:(NSCoder *)aCoder
{
//encode only certain instance variables
[aCoder encodeObject:self.name forKey:#"name"];
[aCoder encodeObject:self.location forKey:#"location"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [self init];
if (self) {
//decode data
[self setName:[aDecoder decodeObjectForKey:#"name"]];
[self setLocation:[aDecoder decodeObjectForKey:#"location"]];
}
return self;
}
The instance variables are properties, and they are values of a custom class. Multiple instances of this class populate an NSMutableArray stored in my main view controller.
My view controller contains the methods:
- (NSString *)itemArchivePath
{
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
//only one document in list - get path t o it
NSString *documentDirectory = [documentDirectories objectAtIndex:0];
return [documentDirectory stringByAppendingString:#"file.archive"];
}
- (BOOL)saveChanges
{
//returns success or failure
NSString *path = [self itemArchivePath];
return [NSKeyedArchiver archiveRootObject:customClassArray //This is the array storing multiple instances of the custom class
toFile:path];
}
The comment about the array is not in the actual code. And finally, the app delegate has the following code:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
BOOL success = [mainViewController saveChanges];
if (success) {
NSLog(#"Saved Successfully!");
}
else {
NSLog(#"Unsuccessful");
}
}
Unfortunately, whenever running the code, "unsuccessful" is always logged, and I am not sure why. The mainViewController is an instance variable of the app delegate. I have tried debugging for a very long time, and I cannot find the problem. Any help is appreciated. Thanks!
There's a good chance that the path to which you're trying to save isn't valid. You should try logging it and see whether the value is sane when the save fails. You might also want to look at using stringByAppendingPathComponent: instead of stringByAppendingString:.
When my app loads up it builds a menu using a UITableViewController, this menu is split in two parts. The first part has 4 items from a 'hard-coded' array, the second part is made dynamically from a list of all text files in the documents folder.
When my app runs it builds the menu and then gets files if they're needed (which the menu needs to be complete). So the second part of my menu is blank. If I run the app again the menu is fine as the files now exist.
What I want is, when the file has finished downloading, for the UITableViewController to be reloaded and so the menu rebuilt.
The number of files can and will change and will be updated often with a timestamp check carried out just before the file is downloaded.
Where the file is downloaded:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
MenuViewController *refreshedMenu = [[MenuViewController alloc]init];
[refreshedMenu reloadTableView];
// Have also tried:
// [refreshedMenu performSelectorOnMainThread:#selector(reloadTableView)withObject:nil waitUntilDone:NO ];
[file closeFile];
}
The UITableViewController's code:
-(void)reloadTableView{
NSLog(#"reloadTableView has been run");
[self buildMenuArrays];
[self.tableView reloadData];
}
-(void)buildMenuArrays{
self.mainMenu = [NSMutableArray arrayWithObjects:#"Home", #"Exhibitor", #"Speaker", #"Workshop", nil];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDirectory = [paths objectAtIndex:0];
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSArray *files = [fileManager contentsOfDirectoryAtPath:docDirectory error:nil];
self.infoMenu = [[NSMutableArray alloc] init];
for (int count = 0; count < files.count; count++) {
NSString *currentFile = [files objectAtIndex:count];
if ([currentFile rangeOfString:#".txt"].location == NSNotFound) {
NSLog(#"File %# does not contain .txt", currentFile);
} else {
NSLog(#"File %# DOES contain .txt", currentFile);
[self.infoMenu addObject:[files objectAtIndex:count]];
}
}
NSLog(#"File array = %#", self.infoMenu);
}
At the moment, the array is updated and the correct file names are there but [self.tableView reloadData]; seems to do nothing.
Any ideas?
EDIT
This seems like such a simple process but I see many other people have the same problem. IS the approach I'm taking fundamentally wrong? It seems like calling a UITableViewController's reloadData method should be very easy to do, but isn't?
You seem to be making a new object of MenuViewController in connectionDidFinishLoading method. This will give you a new object allright but where have you added the view of this new TableViewController object into the existing ViewController?
You need to add view of this newly created TableViewController object onto your view hierarchy.
I'm trying to understand polymorphism and inheritance. I don't have too much experience in it. What I'm basically trying to do is have a generic BaseFileLogic class that can open a file from the bundle. I have subclasses of the BaseFileLogic class that have the file name as a constant for those specific subclasses. The idea was basically for someone who dragged a file name I specify from iTunes sharing, I could open the file and parse it for my different object types. So my BaseFileLogic class is pretty simple with a class and designated initializer:
- (id)initWithFileName:(NSString *)fileName fileLocation:(FileLocation)fileLocation {
if (self = [super init]) {
_currentFile = fileName;
NSString *filePath = nil;
if (fileLocation == FileLocationBundle) {
filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:#"csv"];
}
else {
filePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:_currentFile];
}
_parser = [[CHCSVParser alloc] initWithContentsOfCSVFile:filePath];
_parser.delegate = self;
}
return self;
}
+ (id)baseFileLogicWithFileName:(NSString *)fileName fileLocation:(FileLocation)fileLocation {
return [[CRBaseFileLogic alloc] initWithFileName:fileName fileLocation:fileLocation ];
}
Now in my RoastLogic class, I have this so far:
#interface CRRoastFileLogic : CRBaseFileLogic
+ (id)roastFileLogicFromFileLocation:(FileLocation)fileLocation;
#end
#implementation CRRoastFileLogic
+ (id)roastFileLogicFromFileLocation:(FileLocation)fileLocation {
CRBaseFileLogic *cr = [CRBaseFileLogic baseFileLogicWithFileName:(NSString *)kRoastFileName fileLocation:fileLocation];
NSLog(#"cr: %#", [cr description]);
return cr;
}
#end
When I print out the description, the cr object is of type CRBaseFileLogic. I guess at this part I'm confused because I want to create an instance of CRRoastFileLogic, but use methods that I have declared in the superclass. How is inheritance /polymorphism supposed to work?
in this method
+ (id)baseFileLogicWithFileName:(NSString *)fileName fileLocation:(FileLocation)fileLocation {
return [[CRBaseFileLogic alloc] initWithFileName:fileName fileLocation:fileLocation ];
}
you have the problem. Because it always return an instance of CRBaseFileLogic
so change it like this should solve the problem
+ (id)baseFileLogicWithFileName:(NSString *)fileName fileLocation:(FileLocation)fileLocation {
return [[self alloc] initWithFileName:fileName fileLocation:fileLocation ];
}
then the self will be CRRoastFileLogic if you call it like [CRRoastFileLogic baseFileLogicWithFileName...]
Everytime I launch an app, I should to read 5 txt file where are stored some information; then the methods that read and stored data in array from these file should be write in my firstview controller (class of my first view) or in class appdelegate?
In the relevant view controller (probably viewDidLoad).
It would look something like this (untested):
- (void)viewDidLoad {
NSArray *fileNames = [NSArray arrayWithObjects:#"fileName1.txt", #"fileName2.txt", #"etc", nil];
NSMutableArray *fileStrings = [[NSMutableArray alloc] init];
for (int i = 0; i<[fileNames count]; i++) {
NSString *aFileName = [fileNames objectAtIndex:i];
NSString *aFilePath = [[NSBundle mainBundle] pathForResource:aFileName];
NSString *aFileContents = [[NSString alloc] initWithContentsOfFile:aFilePath];
[fileStrings addObject:aFileContents];
[aFileContents release];
}
myStrings = fileStrings; // Some array to store to
}
I am guessing this is configuration info that you are reading. I would suggest using a pList instead of using text files.
Apple has really optimized reading from & to a plist. Hope this helps...