iPhone - Error setting up block that takes an array - iphone

I am writing as a follow up to this question I asked yesterday, but I have not heard back from the original responder. I would be happy to wait, but I have a small time limit. He helped me tremendously with my NSURLConnection code, and I understand perfectly how this is working, but I can't seem to get the syntax correct.
I can't get handler:^ to be recognized and this line:
[self loadImageArray:urlArray handler:^(NSMutableArray *)imageArray
and it needs to take an array (imageArray from loadImageArray) that is populated with images.
- (void)loadImageArray:(NSArray *)urls handler:(void(^)( handler)
This should populate an array (imageArray) asynchronously from the server.
How do I setup the block calls correctly? I have read about blocks on a few sites, but none of the suggestions have helped.
Again, I have asked the original responder, but have not heard back.
I hope that edit helps. Thank you!
Here is my .h
#interface OBNSURLViewController : UIViewController
{
NSArray *jsonArray;
NSMutableData *theJsonData;
IBOutlet UIView *mainView;
__weak IBOutlet UIImageView *mainImage;
__weak IBOutlet UILabel *mainLabel;
}
#property (nonatomic, strong) NSData *serverData;
#property (strong, nonatomic) IBOutlet UIScrollView *mainScroll;
#property (nonatomic, retain) NSMutableArray *imageArray;
#property (nonatomic, retain) NSMutableArray *urlArray;
#property (nonatomic, retain) UIImage *imageData;
#end
Here is the relavent code I am stuck on:
- (void)parseJSONAndGetImages:(NSData *)data
{
//initialize urlArray
urlArray = [[NSMutableArray alloc]init];
//parse JSON and load into jsonArray
jsonArray = [NSJSONSerialization JSONObjectWithData:theJsonData options:nil error:nil];
//assertion?
assert([jsonArray isKindOfClass:[NSArray class]]);
//Make into one liner with KVC.... Find out what KVC is
//Code to load url's into array goes here....
//load the images into scrollview after fetching from server
[self loadImageArray:urlArray handler:^(NSMutableArray *)imageArray //Here is a big problem area
{
//work goes here....
}];
}
- (void)loadImageArray:(NSArray *)urls handler:(void(^)( handler)//This does not want to work either. I am stuck on handler???
{ dispatch_async(0, ^{
//imageArray = [NSMutableArray array];
for (int y = 0; y < urlArray.count; y++)
{
//stuff goes here.....a
});
dispatch_async(dispath_get_main_queue(),^{
handler(imageArray);
});
}

As I'm reading this, the method syntax should look like the following. If the handler block takes an argument, you need to declare that it does.
- (void)loadImageArray:(NSArray *)urls handler:(void (^)(NSMutableArray *imageArray))handler
{
NSMutableArray *imageArray = [NSMutableArray array];
// Do something with the urls array to fill in entries in imageArray...
handler(imageArray);
}
You would call the method like this:
NSArray *urls = // filled in somewhere else...
[myObject loadImageArray:urls handler:^(NSArray *imageArray) {
NSLog(#"%#", imageArray);
}];

Related

Core Data, can't retrieve/set to my properties. Most common mistake?

SUBJECT: Core Data, can't retrieve/set to my properties from VC's. Most common mistake?
Searched through this site but could not quite get the answered I need (many good tips though), so thought I post this question in hope it will resolve my issue I've had for a couple of weeks now! Yes, very frustrating, you probably know the feeling! So ANY help would be great - thank's! :-)
Oveview:
iOS 5.1 project. Got Core Data working (tested in main.h/NSLog) but I'm having trouble retrieving and setting Entities properties (data) from other view controllers. Xcode recognizes my singelton "AppContent" found in the AppDelegate from other view controllers, but not the entity name and it's properties.
What is the most common mistake regarding this?
I get a feeling of that I've just missed to importing some file in the right place etc...
Some more details;
FYI: I'm trying to use a recommended method by Matt Campell, that creates a singleton in the AppDelegate that can be used all over the app to work with the managedObjectContext from any view controllers, and retrieve and save data to Core Data and it's entities and it's respective properties. This is done by importing the following two files to the app;
appContent.h
#import <Foundation/Foundation.h>
#import "Contest.h" // Root Entity in CD model
#import "Player.h"
#interface AppContent : NSObject
+(AppContent *)sharedContent;
#property(strong, readonly) id rootObject;
-(void)save;
-(void)rollback;
#end
appContent.m
#import "AppContent.h"
#import <CoreData/CoreData.h>
#interface AppContent()
-(NSURL *)dataStoreURL;
#property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;
#property (nonatomic, strong, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
#property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;
#end
#implementation AppContent
NSManagedObjectModel *_managedObjectModel;
NSPersistentStoreCoordinator *_persistentStoreCoordinator;
NSManagedObjectContext *_managedObjectContext;
id _rootObject;
static AppContent *singletonInstance = nil;
+ (AppContent *)sharedContent{
#synchronized(self){
if (singletonInstance == nil)
singletonInstance = [[self alloc] init];
return(singletonInstance);
}
}
- (NSURL *)dataStoreURL {
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
return [NSURL fileURLWithPath:[docDir stringByAppendingPathComponent:#"DataStore.sql"]];
}
- (NSManagedObjectModel *)managedObjectModel {
if (_managedObjectModel) {
return _managedObjectModel;
}
_managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
return _managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator) {
return _persistentStoreCoordinator;
}
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self dataStoreURL]
options:nil
error:&error]) {
NSLog(#"Unresolved Core Data error with persistentStoreCoordinator: %#, %#", error, [error userInfo]);
}
return _persistentStoreCoordinator;
}
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext) {
return _managedObjectContext;
}
if ([self persistentStoreCoordinator]) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
}
return _managedObjectContext;
}
-(id)rootObject{
if(_rootObject)
return _rootObject;
// #warning Replace [CHANGE] with your root object entity name
NSString *entityName = #"Contest";
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName
inManagedObjectContext:context];
request.entity = entity;
NSArray *listOfObjects = [context executeFetchRequest:request
error:nil];
if([listOfObjects count] == 1){
_rootObject = [listOfObjects lastObject];
return _rootObject;
}
_rootObject = [NSEntityDescription insertNewObjectForEntityForName:entityName
inManagedObjectContext:context];
// Adding some testdata (only first time...)
// Contest
Contest *c = _rootObject;
c.name = #"Big Game 1";
// Players
Player *p1 = (Player *) [NSEntityDescription insertNewObjectForEntityForName:#"Player"
inManagedObjectContext:context];
p1.name = #"Player One";
[c addPlayersObject:p1];
Player *p2 = (Player *) [NSEntityDescription insertNewObjectForEntityForName:#"Player"
inManagedObjectContext:context];
p2.name = #"Player Two";
[c addPlayersObject:p2];
[self save];
return _rootObject;
}
-(void)save{
NSError *error = nil;
NSManagedObjectContext *context = [self managedObjectContext];
if([context hasChanges])
[context save:&error];
if(error)
NSLog(#"Warning: Error saving to data store. %#", error);
}
-(void)rollback{
NSManagedObjectContext *context = [self managedObjectContext];
if([context hasChanges])
[context rollback];
}
#end
Here are the model files (created by CD model editor);
Contest.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#class Player;
#interface Contest : NSManagedObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSSet *players;
#end
#interface Contest (CoreDataGeneratedAccessors)
- (void)addPlayersObject:(Player *)value;
- (void)removePlayersObject:(Player *)value;
- (void)addPlayers:(NSSet *)values;
- (void)removePlayers:(NSSet *)values;
#end
Contest.m
#import "Contest.h"
#import "Player.h"
#implementation Contest
#dynamic name;
#dynamic players;
#end
Player.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#class Contest;
#interface Player : NSManagedObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) Contest *contests;
#end
Player.m
#import "Player.h"
#import "Contest.h"
#implementation Player
#dynamic name;
#dynamic contests;
#end
Here is a view controller
Trying to get hold of the Core Data Entities and it properties here, but Xcode see's the appContent but don't recognize it and when building it gives error "Can't find property player"...
contestPlayerVC.h
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#import "AppContent.h"
#interface contestPlayerVC : UIViewController
#property (strong, nonatomic) IBOutlet UITextField *playerNameField;
#property (strong, nonatomic) IBOutlet UITextField *playerMailField;
#property (strong, nonatomic) IBOutlet UIButton *playerSaveButton;
#property (weak, nonatomic) AppContent *content;
// Test
// #property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
- (IBAction)playerChooseImage:(UIButton *)sender;
- (IBAction)dismissModal:(UIButton *)sender;
- (IBAction)hideKeyboard:(id)sender;
- (IBAction)playerSave:(UIButton *)sender;
#end
contestPlayerVC.m
Not showing hole file since problem is in the method viewDidLoad...
#import "contestPlayerVC.h"
#interface contestPlayerVC ()
#end
#implementation contestPlayerVC
#synthesize playerNameField, playerMailField, playerSaveButton, content;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// Call 1 - seems to work ok
self.content = [AppContent sharedContent];
////// Call 2 - gives error, can't find property "player" - WHY?
// Trying to set textfield to CD Entity "Player" and it's property "name"
self.playerNameField.text = self.content.player.name;
NSLog(#"Player is: %#", self.content.player.name);
// Call 3 - works ok
NSLog(#"Content is: %#", self.content.description);
}
...
Any tips or suggestions? Thanks! :-)
Btw: Sorry about the long post, I'm not sure about how else describe my problem well enough for anyone to understand it. If you read it all - I'm impressed and I'll be real happy to if you help me solve this. Probably some basic thing I totally missed. Thank's ;-)
#user1578933 Ok, let's back up a second and think about what each piece is responsible for:
Contest is the root (first) object responsible for everything related to a contest including the name and list of players
Player is responsible for everything about a player including the player's name
AppContent is responsible for managing the object graph (retrieving the object graph so you can use it and remembering what the user adds and removes)
contestPlayerVC is responsible for presenting content (from AppContent)
In your app, everything starts with Contest. In your AppContent implementation you have code that generates that property rootObject and this is where the Contest object is created for the first time.
NOTE You have that defined as id because you are copying this code directly for our program, but you could also have defined rootObject as type Contest like this:
-(Contest *) rootObject;
This your point of entry to your object graph. The idea is that you will access AppContent (using the Singleton pattern) and then retrieve the Contest object. The Contest object will have the NSSet object collection that contains references to each player in the collection.
Here's an example of how you would use this in your view controller:
- (void)viewDidLoad {
[super viewDidLoad];
self.content = [AppContent sharedContent];
//get reference to the root object contest
Contest *theContest = (Contest *)self.content.rootObject;
//get an array based reference to the players in theContest
NSArray *players = [theContest.players allObjects];
//Example of getting a reference to an individual player:
Player *p1 = [players objectAtIndex:0];
}
This is essentially how you would add this content to your view controller; for a table view controller you would use the index path row property to find out what player goes in what table view cell (see cellForRowAtIndexPath).
#user1578933 - after reading this post and also the ones you left on Mobile App Mastery Institute it seems like you are missing some understanding of the object graph and how all these object relationships are organized in general. I've recently added some additional content about how to use and think about the Objective-C object graph here. Also, I would carefully look over the sections on Objective-C, Table Views and Core Data again.
Your error, not being able to find a player property on self.content, is because your AppContent class doesn't declare any property of that name. Let's walk through your code one line at a time:
self.content = [AppContent sharedContent];
Now self.content is an object of type AppContent, and so any future calls to self.content.something will look for visible methods or properties declared on that class (usually in "AppContent.h").
self.playerNameField.text = self.content.player.name;
We're looking here for a property named player in an AppContent object, which has as its header:
#import <Foundation/Foundation.h>
#import "Contest.h" // Root Entity in CD model
#import "Player.h"
#interface AppContent : NSObject
+(AppContent *)sharedContent;
#property(strong, readonly) id rootObject;
-(void)save;
-(void)rollback;
#end
Nowhere in there is a #property named player - you have a rootObject property, and you import the "Player.h" header, but you never declare the property you're trying to access. You'll need to add a line like:
#property(strong) Player * player;
and either #synthesize it in your "AppContent.m" file, or provide the appropriate accessor methods to back that property.

Saving Arrays Between Classes

Currently attempting to save an array that is populated according to which cells in a UITableView are chosen and saving this array in an instance of a seperate object. I am getting the array to populate just fine, however, my save method, which is an IBAction that is invoked by clicking on a Bar Button doesn't seem to be working. Here is some code:
-(IBAction)saveWorkout:(id)sender {
Workouts *new = [[Workouts alloc] init];
[new addNewWorkout:customWorkout];
[customWorkout removeAllObjects];
}
This code is from the first class.
And here is the code for my addNewWorkouts method in the Workouts class:
-(void)addNewWorkout:(NSMutableArray*)array {
NSMutableArray *temp = [[NSMutableArray alloc] init];
temp = array;
self.workoutList = temp;
[temp release];
}
Here is my "Workout.h"
#import <Foundation/Foundation.h>
#interface Workouts : NSObject {
NSString *workoutName;
NSMutableArray *workoutList;
NSString *description;
int *reps;
int *weights;
int *sets;
}
#property (nonatomic, retain) NSString *workoutName;
#property (nonatomic, retain ) NSString *description;
#property (nonatomic, retain) NSMutableArray *workoutList;
-(void)addNewWorkout:(NSMutableArray*)array;
#end
Before running this code, I get a Warning from Xcode saying that 'Workouts may not respond to 'addNewWorkouts.'
Anyone know what is causing this error? Once I build & run, I click on the Save button and the app crashes with a unrecognized selector sent to instance 0x3b04410 error.
You call [new addNewWorkouts:customWorkout]
when the method's selector is addNewWorkout: (note that there is no plural in the method name)
This will make a bad method call and result in a crash.
Also, there is a problem with the memory management of the addNewWorkout method.
1- NSMutableArray *temp = [[NSMutableArray alloc] init];
2- temp = array;
3- self.workoutList = temp;
4- [temp release];
You allocate a new NSMutableArray on line 1, then lose its reference on line 2 when you replace its pointer by 'array'. The allocation you just made is lost and the program will leak.
Then, on line 4, you send a release message to 'temp' which actually points to 'array', resulting in the release of the parameter that you received and not the temporary object.
Is there a reason whny you create a temporary array? You can just assign the property and make the property copy or retain it, depending on your needs.

How to change the iVar of another class

This is the code. It is pretty straight forward. I made two classes one is returning the error and hydrate the iVar of another class (TheView) and show it to the User. however I cant figure it out why the View return Null at all time. Thanks is advance guys.
#interface AccountControllerModel : NSObject {
NSString *anError;
}
#property (nonatomic, retain) NSString *anError;
AccountControllerModel.m
#synthesize anError;
- (void)uploadFailed:(ASIHTTPRequest *)theRequest{
RegistrationViewController *regoVC = [[RegistrationViewController alloc] init];
[regoVC manageTheError:#"THIS IS AN ERROR"];
[regoVC release]; regoVC = nil;
}
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#interface RegistrationViewController : UIViewController {
NSMutableString *test;
}
#property (nonatomic, assign) NSMutableString *test;
#synthesize test;
-(void)viewDidLoad {
test = [[NSMutableString alloc] init];
}
-(void)manageTheError:(NSString *)theError{
self.test = [NSMutableString stringWithFormat:#"%#",theError];
resultOfRegistration.text = [NSString stringWithFormat:#"%#",self.test];
NSLog(#"test is %#",self.resultOfRegistration.text); //It comes back Null
}
Alex is right, some clarification on what's not working would help but by looking through I may have found your error. [[NSNotificationCenter defaultCenter] postNotificationName:#"Blah" object:self], you have object set to nil which could be your issue with the notification.
There are a number of problems with your code.
#property (nonatomic, assign) NSMutableString *test;
Two things here, one, exposing a NSMutable* object in a property is never a good idea, two you should 'copy' value objects, especially because this is how you're treating it in your code. Make this #property (nonatomic, copy) NSString *test;
regoVC.test = [NSMutableString stringWithString:self.anError];
You're assigning an autoreleased object to an assign property, this is a leak, the change above will fix that.
NSLog(#"test is %#",test); // It is perfect as you expect
test isn't in scope here, but I'd assume that was supposed to be regoVC.test, these other changes should remedy the situation.

Need to store array of an array of class objects in View Controller

An iPhone question for you guys! I have an NSURLConnection that downloads XML from a server and processes it into an array which is a part of another array. I have no way of knowing how many objects I will need, so I cannot allocate an NSArray beforehand. My question is:
Would it be better to create the parent array as an NSArray at the class level and allocate it after I store the data in a temporary NSMutableArray or just make the NSMutableArray at class level? It is worth noting that I do not need to modify the array other than to release it at the end of the program run.
I don't think it really matters.
I'm reading the Beginning iPhone 3 Development book at the moment, and usually loading the data is done like this:
You'd have an NSArray property :
#interface
{
...
NSArray *listOfObjects;
...
}
#property (nonatomic, retain) NSArray *listObObjects;
...
Then you create an NSMutableArray, load your data and set the property:
NSMutableArray *array = [[NSMutableArray alloc] init]; // ?
// load the XML into array here
...
self.listOfObjects = array;
[array release];
listOfObjects would then be treated as an NSArray (immutable) although it actually would be an NSMutableArray.
I think what you probably want to do is create some Classes that match what you are representing in your xml. For example if you xml looks something like this:
<peopleList>
<person>
<name>Joe</name>
<possession>Shovel</possession>
</person>
<person>
<name>Sam</name>
<possession>Shovel</possession>
<possession>Fork</possession>
<possession>Backpack</possession>
</person>
</peopleList>
You should have a PeopleList Class and a Person Class. An object instantiated from the PeopleList Class has your first array that contains one or more Person objects. The Person objects, in turn, also have arrays to hold the possesions (which in this case are strings - although if needed they could be Possesion objects) In this case, to help the example the Person Class also has another property: 'name' which is also a String.
For example:
#interface PeopleList {
NSMutableArray *persons; // An array to store the Person objects
}
#property (nonatomic, retain) NSMutableArray *persons;
#end
#interface Person {
NSString *name;
NSMutableArray *possesions; //An array to store this Person's possesion strings
}
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSMutableArray *possesions;
#end
In the init method's of these objects you will have to alloc/init the arrays so they will be ready to receive objects. And because I alloc'd them, my class is responsible for the release.
#implementation PeopleList
-(id) init {
if (self = [super init]) {
persons = [[NSMutableArray alloc] init];
}
}
-(void) dealloc {
[persons release];
[super dealloc];
}
#end
#implementation PeopleList
-(id) init {
if (self = [super init]) {
possesions = [[NSMutableArray alloc] init];
}
}
-(void) dealloc {
[possesions release];
[super dealloc];
}
#end
Now that this is done, you can set up your data structure of cascading array's.
As you are parsing the XML when you come across a PeopleList Tag do a:
currentPeopleList = [[PeopleList alloc] init];
and when you come across a Person tage do a:
currentPerson = [[Person alloc] init];
[peopleList.persons addObject: person];
a possesion:
[currentPerson.possesion addObject: contentsOfCurrentElement];
or name:
currentPerson.name = contentsOfCurrentElement;
But to answer your more specific question, I not store the data in a temporary NSArray and then copy that into a NSMutableArray. There is almost no performance gain by doing so, and you will burn cpu cycles and memory doing the copy.

Unable to setObject:forKey: in NSMutableDictionary

I'm sure I'm missing something obvious here in a simple iPhone program I'm trying to write, but the code seems so basic that I can't see what's wrong.
I'm trying to use an NSMutableDictionary to store a list of classes and their associated save file names. In the header file I declare the dictionary
#interface ClassList : UITableViewController {
NSString *homedirectory;
NSString *masterindexpath;
NSMutableDictionary *classFilenameGlossary;
NSMutableArray *listofclasses;
}
#property (retain, nonatomic) NSString *homedirectory;
#property (retain, nonatomic) NSString *masterindexpath;
#property (retain, nonatomic) NSMutableDictionary *classFilenameGlossary;
#property (retain, nonatomic) NSMutableArray *listofclasses;
And, of course, in the implementation file:
#implementation ClassList
#synthesize homedirectory;
#synthesize masterindexpath;
#synthesize classFilenameGlossary;
#synthesize listofclasses;
I initialize this dictionary at ViewDidLoad from an existing file that saves the classlist:
- (void)viewDidLoad {
[super viewDidLoad];
// Get home directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
homedirectory = [paths objectAtIndex:0];
masterindexpath = [homedirectory stringByAppendingPathComponent:#"MasterIndex"];
// Get master course list or create it if necessary
NSFileManager *fm = [NSFileManager defaultManager];
if ([fm fileExistsAtPath:masterindexpath] == NO)
{
NSMutableDictionary *temporarydictionary = [NSMutableDictionary dictionary];
[temporarydictionary writeToFile:masterindexpath atomically:NO];
[temporarydictionary release];
}
[classFilenameGlossary initWithContentsOfFile:masterindexpath];
[homedirectory retain];
[masterindexpath retain];
}
Later, I add an item using setObject:forKey: but the dictionary never changes. Originally I was doing this in another view controller using a pointer back to this ClassList file, but once I discovered that didn't work, I simplified and tried to just set any sample object and key within the ClassList file:
[classFilenameGlossary setObject:#"sample filename" forKey:#"sample classname"];
I've looked at the debugger variable list and it shows classFilenameGlossary properly identified as an NSMutableDictionary with 0 key/value pairs. I'm not getting any sort of error; the dictionary simply never changes. At the moment, the dictionary loaded from the file masterindexpath is empty (since I haven't been able to add any items yet), but that shouldn't keep me from being able to add items to the dictionary.
I'm a total beginner at iPhone programming, so I'm sure there's something basic that I'm missing. I'd appreciate any help.
This line:
[classFilenameGlossary initWithContentsOfFile:masterindexpath];
should look like this:
classFilenameGlossary = [[NSMutableDictionary alloc] initWithContentsOfFile:masterindexpath];
You forgot to allocate memory for the NSMutableDictionary, so that's why it never initializes.