My data is stored in a member variable (NSArray) of a view controller. The problem I'm running into is that my data is loaded from a database on application launch, but the NSArray isn't initialized until later, so the addObject calls silently fail.
I've tried putting breakpoints on the init, initWithNibName, viewWillAppear, and viewDidLoad methods of my view controller (SafeTableViewController), but none of them catch before the addObject call. I assume that the actual view controller is initialized, because when I watch it in the debugger it has a nonzero address, but the NSArray has the address 0x0 when addObject is called.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
databaseName = #"DubbleDatabase.sql";
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
databasePath = [documentsDir stringByAppendingPathComponent:databaseName];
[self checkAndCreateDatabase];
[self readSafeItemsFromDatabase ];
// Add the tab bar controller's current view as a subview of the window
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
return YES;
}
- (void) readSafeItemsFromDatabase {
// some code skipped here, but basically: open sqlite3 database, iterate through rows
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
// read database, get data fields out
SafeItem *safeItem = [[SafeItem alloc] initWithName:aName price:aPrice category:aCategory];
[safeTableViewController addItemToSafe: safeItem]; // PROBLEM HERE
[safeItem release];
}
}
sqlite3_close(database);
}
In SafeTableViewController.m:
- (void) addItemToSafe : (SafeItem*) newSafeItem {
[self.safeItems addObject: newSafeItem];
}
// I put a breakpoint on this, but it does not hit. i.e. safeItems is not initialized when addObject is called on it.
-(id) init {
if(self = [super initWithNibName:#"SafeTableViewController" bundle:nil]){
self.safeItems = [[NSMutableArray alloc] init];
}
return self;
}
EDIT: Thought of a way to fix this problem. Still curious though: when is init and/or initWithNibName called? Here's the proposed solution:
- (void) addItemToSafe : (SafeItem*) newSafeItem {
if(self.safeItems == nil){
self.safeItems = [[NSMutableArray alloc] init];
}
[self.safeItems addObject: newSafeItem];
}
How do you setup your instance of SafeTableViewController? By code? By nib?
-(id) init is not the designated initializer.
You probably want to use
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle {
if(self = [super initWithNibName:nibName bundle:nibBundle]){
self.safeItems = [[NSMutableArray alloc] init];
}
return self;
}
or initialize elsewhere, i.e. in viewDidLoad.
The problem is that you shouldn't be storing your data in a view controller. Create a model object (SafeItemManager for instance) to hold your data and point the view controller at that.
Related
I am creating a split-view iPad application. When the user presses the bar button item in the master view, a modal is presented. This modal has a textfield and has an IBAction to pick up keyboard returns.
On keyboard returns, a new instance of my Farm class is created (code below). This instance is then added to an array that is stored in my delegate. I then try to reload the MasterViewController's table. Upon this reload the application crashes on cell.textLabel.text with a EXC_BAD_ACCESS error.
Farm *current = [delegate.arrayOfFarms objectAtIndex:indexPath.row];
cell.textLabel.text = [current getFarmTitle];
If I ask the array within the delegate how many elements it has, it will indeed show the current amount, even. This is what is bizarre to me about this whole thing: the Farm instances appear to be in existence.
I have instances of AppDelegate in both my MasterViewController and my NewFarmNamingView classes. The instance in the Master is to populate the table. The instance in NewFarm is to add the newly created Farm to the delegate. Code below.
Segments from class NewFarmNamingView:
- (IBAction) keyboardDonePushed:(id)sender
{
// create a Farm and add it to the delegate
NSString *text = newFarmTextField.text;
Farm *newFarm = [[Farm alloc] init];
[newFarm setFarmTitle:text];
[[delegate arrayOfFarms] addObject:newFarm];
[newFarm release];
NSLog(#"Added farm: %#" , text);
// dismiss the view
[self closeView:nil];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// initialize the delegate
delegate = [[UIApplication sharedApplication] delegate];
}
Segments from the class Farm
- (void) setFarmTitle : (NSString *) _farmTitle
{
farmTitle = _farmTitle;
}
- (NSString *) getFarmTitle
{
return farmTitle;
}
// NSCoding Methods
- (void) encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:farmTitle forKey:#"kFarmTitle"];
}
- (id) initWithCoder:(NSCoder *)aDecoder
{
farmTitle = [aDecoder decodeObjectForKey:#"kFarmTitle"];
return self;
}
// Initialization method
- (id)init
{
self = [super init];
if (self) {
// Initialization code here.
}
return self;
}
From the runtime reference: "objc_msgsend sends a message to the receiver and expects a simple return value."
I'll bet you anything that what you're returning (if you're returning anything at all) in that class method getTitleFarm is returning an incorrect value. It should be an NSString. Be absolutely sure it is returning an NSString, and not anything else.
If you need to use the respondsToSelector method to see if the class is being released, try:
if([current respondsToSelector:#selector(getFarmTitles)]) {. [current getFarmTitle];
}
else {
NSLog:(#"FAILURE!!");
}
EDIT: maybe you are not retaining or even creating this string at all. In it's initialization, wrap it in a retain]; message
I have been trying for days to get this code to work, but I have no idea what I am doing wrong. Everytime the app wakes up from sleep, or the user closes the app and opens it again (without closing the app from multitasking), I want a label value to change.
In my applicationDidBecomeActive, I am running a counter, which I want to display on whatever viewcontroller is open at that moment.
Code:
- (void)applicationDidBecomeActive:(UIApplication *)application {
counter = counter + 1;
W1G1 *view1 = [[[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil] retain];
[view1 setlabel];
}
In my viewcontroller W1G1, I have the following code:
Code:
- (void) setlabel {
NSString *string = [NSString stringWithFormat:#"%d", counter];
vocabword.text = string;
}
I have imported W1G1 in my appdelegate, but the code does not run :( Please help!
Thanks
In the AppDelegate.m file, where you have
- (void)applicationDidBecomeActive:(UIApplication *)application {
counter = counter + 1;
W1G1 *view1 = [[[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil] retain];
[view1 setlabel];
}
the variable counter being incremented is confined to the AppDelegate. In other words, your view controller doesn't know that it has been incremented.
I would suggest that you use NSUserDefaults to store the value of counter so that you can easily pass it between these view controllers. Either that, or you could allow for an input into the method setLabel, e.g.
- (void) setlabel:(int)counter {
NSString *string = [NSString stringWithFormat:#"%d", counter];
vocabword.text = string;
}
and then in the AppDelegate you'll want to do:
- (void)applicationDidBecomeActive:(UIApplication *)application {
counter = counter + 1;
W1G1 *view1 = [[[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil] retain];
[view1 setlabel:counter]; // <-- now you're using counter
[self.window addSubview:view1];
}
1) When you say 'the code does not run' do you mean that? That is, if you put NSLogs in applicationDidBecomeActive: and in setLabel does it show the code is run?
2) I would suspect the code is running. But your code won't "show the counter on whatever view controller is open at that moment". Your code creates a new view (view1), but that view won't be displayed. It is not added as a subview to anything. Your code will also leak. You create a W1G1 object, but it is never released and you throw away any reference you have to it.
To achieve what you want, you could add a subview to the application's window. Depending how your app delegate is set up, something like the following should do the trick:
counter++;
W1G1 *viewController1 = [[W1G1 alloc] initWithNibName:#"W1G1" bundle:nil];
[viewController1 setlabel: counter];
[[self window] addSubview: [viewController1 view]]
// you'll want to save a reference to the viewController somehow so you can release it at a later date
Then in W1G1
- (void) setlabel: (int) counter;
{
NSString *string = [NSString stringWithFormat:#"%d", counter];
vocabword.text = string;
}
There are, of course, lots of other approaches you could take towards this problem. And you'll need some strategy for removing the W1G1 view that you are adding at some stage, otherwise you'll just get more and more views added.
Update: You ask (in comments) how to keep track of your viewController throughout lifetime of the app... One approach is to keep track of it in your appDelegate. In the header have something like:
#class W1G1;
#interface MyAppDelegate : : NSObject <UIApplicationDelegate>
{
// other decelerations
int counter;
W1G1 * _myW1G1
}
#property (nonatomic, retain) W1G1* theW1G1
In the .m file include
#synthesize theW1G1 = _myW1G1;
Probably in application:didFinishLaunchingWithOptions: create the viewController, set the property to refer to it, and add its view to the view hierarchy.
W1G1* theViewController = [[W1G! alloc] initWithNibName: #"W1G1" bundle: nil];
[[self window] addSubview: [theViewController view]];
[self setTheW1G1: theViewController];
[theViewController release];
Then when you want to access the viewController again from with the app delegate use [self theW1G1], e.g.
[[self W1G1] setlabel: counter];
EDIT: I seem to have found something that's helped. I retained the ivar "stack" and now it seems to be working
I have been serializing several custom NSObject classes without issue. Now I'd like to serialize my NavigationController stack. Each viewController only needs a couple of properties saved in order to rebuild the navigation tree. I've implemented the NSCoding protocol in the viewControllers, and successfully coded them to NSData and saved to disk.
When I attempt to load the stack, the resulting array has the correct number of objects, but I keep getting EXC_BAD_ACCESS errors when I try to set the viewController array. Am I just going about this the wrong way?
//AppDelegate.m
-(void) loadDataFromDisk {
NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *programDataPath = [libraryPath stringByAppendingPathComponent:#"programData.dat"];
NSData *programData = [[NSData alloc] initWithContentsOfFile:programDataPath];
NSKeyedUnarchiver *decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:programData];
//stack is a mutable array declared in header
//stack = [decoder decodeObjectForKey:#"stack"];
stack = [[decoder decodeObjectForKey:#"stack"]retain]; //retain fixes? Seems to work
[decoder release];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after app launch
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
NSLog(#"%#",self.navigationController.viewControllers);
if ([stack count] > 1) {
self.navigationController.viewControllers = stack;
[stack release]; //retained earlier
}
return YES;
}
I had to -retain the viewController stack after loading it from disk. Evidently if you don't immediately assign the data to a retained property it vanishes.
I have very strange problem, when I don't understand what's going on at all, so I'm looking for explanation of it. Situation is as following:
I have a view controller with scrollview with three subviews in it. Those three subviews have method
-(void)loadContent
which loads content from database using CoreData in the background thread, creates subviews which represent loaded items and add them as own subviews calling [self addSubview: itemView]; That method is invoked as
[self performSelectorInBackground: #selector(loadContent) withObject: nil];
To load data from DB I'm using a singleton service class. Everything worked fine, but when those three views are loading their portions of data, it sometimes crashes the app.
I guessed it's because it shares one NSManagedObjectContext instance for all read operations, so I rewrote the class so it shares only NSManagedObjectModel and NSPersistentStoreCoordinator instances and creates it's own NSManagedObjectContext instance.
Suddenly, very strange thing happened. Data are loaded ok, subviews are created and added to the view hierarchy, but it get never displayed on the screen. When I switch back to the old singleton service class (sharing one managedObjectContext), it works again like a charm! (but with risk of crashing the app, though).
I absolutely don't get the point how loading data from DB is related to displaying items on the screen. More on that - when subviews are created and added to the view hierarchy, why the hell it don't get displayed?
The source looks like this:
- (void) loadContent {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *results = [(WLDataService *)[WLDataService service] loadItemsForGDView];
NSUInteger channelPosition = 0;
CGFloat position = 0.0;
CGFloat minuteWidth = ((self.superview.frame.size.width / 2.0) / 60.0);
for(Item *it in results) {
/// On following lines size and position of the view is computed according to item setup - skipping here...
/// Create item; it's simple subclass of UIView class
WLGDItemView *item = [[WLGDItemView alloc] init];
/// Variables used here are declared above when size and position is computed
item.frame = CGRectMake(itemX, itemY, itemWidth, itemHeight);
[self performSelectorOnMainThread: #selector(addSubview:) withObject: item waitUntilDone: NO];
/// This is just helper macro to release things
WL_RELEASE_SAFELY(item);
}
[pool drain];
}
The basic service class (non-singleton one) implementation is as follows (just interesting parts):
#import "WLLocalService.h"
static NSPersistentStoreCoordinator *sharedPSC = nil;
static NSManagedObjectModel *sharedMOM = nil;
#implementation WLLocalService
#synthesize managedObjectContext;
/// This is here for backward compatibility reasons
+ (WLLocalService *) service {
return [[[self alloc] init] autorelease];
}
#pragma mark -
#pragma mark Core Data stack
- (NSManagedObjectContext *) managedObjectContext {
if (managedObjectContext == nil) {
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
[managedObjectContext setUndoManager: nil];
[managedObjectContext setMergePolicy: NSMergeByPropertyStoreTrumpMergePolicy];
}
return managedObjectContext;
}
- (NSManagedObjectModel *) managedObjectModel {
if(sharedMOM == nil) {
sharedMOM = [[NSManagedObjectModel mergedModelFromBundles: nil] retain];
}
return sharedMOM;
}
- (NSPersistentStoreCoordinator *) persistentStoreCoordinator {
if(sharedPSC == nil) {
NSURL *storeUrl = [self dataStorePath];
NSError *error = nil;
sharedPSC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
if (![sharedPSC addPersistentStoreWithType: NSSQLiteStoreType configuration: nil URL: storeUrl options: nil error: &error]) {
WLLOG(#"%#: %#", error, [error userInfo]);
}
}
return sharedPSC;
}
#pragma mark -
#pragma mark Path to data store file
- (NSURL *) dataStorePath {
return [NSURL fileURLWithPath: [WL_DOCUMENTS_DIR() stringByAppendingPathComponent: #"/DB.sqlite"]];
}
- (void)dealloc {
WL_RELEASE_SAFELY(managedObjectModel);
[super dealloc];
}
#end
I'd really love to know what's going on here and why it behaves so strange (and - of course - why it does not work, in particular). Can anybody explain that?
thanks to all
Have you read Multi Threading with Core-Data twice?
First, do not load or construct UI elements on a background thread. The UI (whether on the desktop or on the iPhone) is single threaded and manipulating it on multiple threads is a very bad idea.
Second, data that you load into one context will not be immediately visible in another context. This is what is causing part of your problem.
The solution is to move all your UI code to the main thread and warm up the Core Data cache on a background thread. This means to load the data on a background thread (into a separate cache) to load it into the NSPersistentStoreCoordinator cache. Once that is complete your main thread can access that data very quickly because it is now in memory.
You realize that [WLDataService service] does not actually return a singleton? It creates a new instance every time. So you are effectively working with multiple instances of the Core Data components.
What about:
static WLDataService* gSharedService = NULL;
#implementation WLDataService
+ (id) service
{
#synchronized (self) {
if (gSharedService == NULL) {
gSharedService = [[self alloc] init];
}
}
return gSharedService;
}
#end
That will create the same instance every time. You will also want to make your managedObjectContext, managedObjectModel and persistentStoreCoordinator methods thread safe by using a #synchronized block. Otherwise there is a change that multiple threads will initialize those at the same time, leading to unexpected behaviour.
I'm new to Objective-C and need help!
I'm following the example "PageControl" which can be found on the net. I added a button to the view in the NIB and hooked up an action which the implementation is found below.
Here is my definition for the view controller being displayed in the page control:
//ContactCardViewController.h
#interface ContactCardViewController : UIViewController
{
int pageNumber;
NSMutableString *s;
}
//ContactCardViewController.m implementation
- (id)initWithPageNumber:(int)page {
if (self = [super initWithNibName:#"ContactCardViewController" bundle:nil]) {
s = [[NSString alloc] initWithFormat:#"page: %d", page];
}
return self;
}
- (id)initWithString:(NSDictionary *)_s {
if (self = [super initWithNibName:#"ContactCardViewController" bundle:nil]) {
NSMutableString *t = [[NSMutableString alloc] initWithString:_s];
s = t;
}
return self;
}
-(IBAction)callOrAddToContacts:(id)sender
{
jobtitleLabel.text = s;
}
//AppDelegate.m
//in delegate that loads the scroll view with the view for the current page:
- (void)loadScrollViewWithPage:(int)page {
//init the view this way the page: and the correct page number is displayed
controller = [[ContactCardViewController alloc] initWithPageNumber:page ];
//init the view this way, the value of the first person is always displayed
controller = [[ContactCardViewController alloc] initWithString:[[self.allNames objectAtIndex:page] objectForKey:#"firstname"]];
}
Please help me to understand why when the view is created with initWithString and then accessed via the button action only value for the first person in the list is always returned. When i init the view using initWithPageNumber s has the correct value Page: X.
In the InitWithString code, you're passing in a Dictionary, not a string.
- (id)initWithString:(NSDictionary *)_s {
if (self = [super initWithNibName:#"ContactCardViewController" bundle:nil]) {
NSMutableString *t = [[NSMutableString alloc] initWithString:_s];
s = t;
}
return self;
}
You may want to change that to NSString, or change the NSMutableString... to an instance of NSDictionary. One or the other.
Found out the problem was in the plist file. The code is fine.