I've got a problem with my TableViewController. I'm posting 3 values to the tableViewController. A scoreID, a scoreDate, and a scoreValue. It all seems to work correctly, but my problem is, that the scoreValue is offset by 1 to the scoreID and Date. It's easier to show with a picture:
("A good business idea" Should have the value of "another good idea" and so on and and so on)
Now, I know from looking in the .sqlite database file, that the scores are offset by one. So my question is how to fix it?
My tableViewController.m looks like this:
#import "CEWTableViewCell.h"
#interface CEWScoreTableViewController ()
#property (nonatomic) NSInteger countOfRows;
#property (nonatomic, strong) NSMutableArray *scoreIDsForTableCells;
#property (nonatomic, strong) NSArray *fetchedObjects;
#property (nonatomic, strong) NSDictionary *getScoreDataToDict;
#end
#implementation CEWScoreTableViewController{
UITableView *tableView;
}
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
CGFloat topLayoutGuide = self.topLayoutGuide.length;
tableView.contentInset = UIEdgeInsetsMake(topLayoutGuide, 0, 0, 0);
tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
tableView.delegate = self;
tableView.dataSource = self;
[self.tableView registerClass:[CEWTableViewCell class] forCellReuseIdentifier:#"CellID"];
tableView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:tableView];
//INITIALISE APPDELEGATE AND CREATE INSTANCE. MAKE FETCH REQUEST AND POPULATE DATA TO NSDICTIONARY
CEWAppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
self.managedObjectContext = appDelegate.managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.resultType = NSDictionaryResultType;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ScoreData" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSError *error = nil;
self.fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchRequest == nil) {
NSLog(#"an error occured fetching objects...!");
}
NSLog(#"objectForKey returns: %#", self.scoreIDFromEntity);
NSUInteger countIDsForRows = [self.fetchedObjects count];
self.countOfRows = countIDsForRows;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
//This value is set to the number of #"scoreID"s returned
return self.countOfRows;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"CellID";
CEWTableViewCell *cell = (CEWTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[CEWTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
self.getIndexPath = indexPath.row;
NSDictionary *getScoreIDs = [self.fetchedObjects objectAtIndex:[indexPath row]];
NSDictionary *getDates = [self.fetchedObjects objectAtIndex:[indexPath row]];
//This is just formatting the date to the format I want displayed
NSDate *pullDates = [getDates valueForKey:#"scoreDate"];
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"dd-MMM-yyyy HH:MM"];
NSString *formattedDate = [dateFormat stringFromDate:pullDates];
//Here I fetch the #"scoreValue"s and convert them from a string, so I can add them up
NSDictionary *getScoreValues = [self.fetchedObjects objectAtIndex:[indexPath row]];
NSString *convertDictKeyToString = [getScoreValues objectForKey:#"scoreValue"];
NSArray *items = [convertDictKeyToString componentsSeparatedByString:#","];
double total = 0.0;
for (NSString *string in items)
{
total += [string floatValue];
}
cell.descriptionLabel.text = [getScoreIDs objectForKey:#"scoreID"];
cell.dateLabel.text = formattedDate;
cell.tableCellScoreLabel.text = [NSString stringWithFormat:#"%1.1f", total];
return cell;
}
I hope this is enough code, otherwise let me know, and I will try and provide more
ANSWER
Sooooooo. It turns out, that my labels was offset because of the CGRect x,y,x,y values.... Ehrm...
In my experience cellForRowAtIndexPath gets called really fast so it doesn't have a lot of time to perform calculations, and for what I've read formatters take more resources than what you think.
I usually solve this issue by creating a new method that gets called from viewDidLoad or viewWillAppear, and in this method I create an array or some sort of container for the data I need.
Then cellForRowAtIndexPath only has to call the array by indexes.
Related
I have a UITableView which shows all of the songs in the Music Library, which works fine. However, when I start typing in the search bar to narrow down the search results, the app crashes immediately. Like as soon as I press one letter on the keyboard, it crashes. I've tried reworking my textDidChange: method but it always crashes, no matter what. I'm not sure what I'm doing wrong, could anyone help? Thanks.
Header:
#interface PTTableViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate>
#property (strong,nonatomic)UITableView* tableView;
#property (strong,nonatomic)UISearchBar* searchBar;
#end
ViewController.m:
#import "PTTableViewController.h"
#implementation PTTableViewController
MPMediaQuery *songsQuery;
NSArray *songsArray;
NSMutableArray *filteredArray;
NSMutableArray *songTitlesArray;
-(void)viewDidLoad{
[super viewDidLoad];
self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0,0,[UIScreen mainScreen].bounds.size.width - 75,150) style:UITableViewStylePlain];
self.tableView.dataSource = self;
self.tableView.delegate = self;
[self.view addSubview:_tableView];
self.searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0,0,320,44)];
self.searchBar.delegate = self;
self.searchBar.placeholder = #"Search";
self.tableView.tableHeaderView = self.searchBar;
songsQuery = [MPMediaQuery songsQuery];
songsArray = [songsQuery items];
songTitlesArray = [[NSMutableArray alloc] init];
for (MPMediaItem *item in songsArray) {
[songTitlesArray addObject:[item valueForProperty:MPMediaItemPropertyTitle]];
}
filteredArray = [[NSMutableArray alloc] init];
filteredArray = [songTitlesArray copy];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return filteredArray.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
if (cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
cell.textLabel.text = [filteredArray objectAtIndex:indexPath.row];
return cell;
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
NSLog(#"search changed");
[self performSelectorInBackground:#selector(helper) withObject:nil];
}
-(void)helper{
[filteredArray removeAllObjects];
if ([self.searchBar.text isEqualToString:#""]){
filteredArray = [songTitlesArray copy];
} else {
for (NSString *object in songTitlesArray){
if ([object rangeOfString:self.searchBar.text].location == NSNotFound){
NSLog(#"string not found");
} else {
NSLog(#"string found");
[filteredArray addObject:object];
}
}
} [self.tableView reloadData];
}
#end
First...
You're getting a crash because you do this:
filteredArray = [songTitlesArray copy];
Which assigns a non-mutable copy of songTitlesArray to filteredArray.
When you then try to do this:
[filteredArray removeAllObjects];
you're trying to mutate a non-mutable object.
You can fix this (first) problem in a couple ways...
One way is to change all instances of copy to mutableCopy:
filteredArray = [songTitlesArray mutableCopy];
However, using your exact code, you'll introduce new crashes because you'll be executing UI actions on a background thread.
Next... Are you sure you're getting "keyboard lag"?
You may want to change a few things:
make _filteredArray non-mutable
instead of calling removeAllObjects simply re-create the array
use filteredArrayUsingPredicate instead of looping through and comparing each string
Give this a try - because I don't have your MPMediaQuery I'll generate 500 strings as "song titles":
#interface PTTableViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate>
#property (strong,nonatomic)UITableView* tableView;
#property (strong,nonatomic)UISearchBar* searchBar;
#end
#implementation PTTableViewController
//MPMediaQuery *songsQuery;
NSArray *songsArray;
NSArray *filteredArray;
NSMutableArray *songTitlesArray;
-(void)viewDidLoad{
[super viewDidLoad];
self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0,0,[UIScreen mainScreen].bounds.size.width - 75,150) style:UITableViewStylePlain];
self.tableView.dataSource = self;
self.tableView.delegate = self;
[self.view addSubview:_tableView];
self.searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0,0,320,44)];
self.searchBar.delegate = self;
self.searchBar.placeholder = #"Search";
self.tableView.tableHeaderView = self.searchBar;
// songsQuery = [MPMediaQuery songsQuery];
// songsArray = [songsQuery items];
//
// songTitlesArray = [[NSMutableArray alloc] init];
// for (MPMediaItem *item in songsArray) {
// [songTitlesArray addObject:[item valueForProperty:MPMediaItemPropertyTitle]];
// }
// filteredArray = [[NSMutableArray alloc] init];
// filteredArray = [songTitlesArray copy];
// I don't have your Query, so let's create a 500 element string array,
// using the row number + one of the following words in each entry
NSString *samples = #"These are some random words for the song titles.";
// So, we'll have:
// "Row: 0 These"
// "Row: 1 are"
// "Row: 2 some"
// "Row: 3 random"
// "Row: 4 words"
// "Row: 5 for"
// "Row: 6 the"
// "Row: 7 song"
// "Row: 8 titles."
// "Row: 9 These"
// "Row: 10 are"
// "Row: 11 some"
// "Row: 12 random"
// "Row: 13 words"
// "Row: 14 for"
// ... etc
NSArray *a = [samples componentsSeparatedByString:#" "];
songTitlesArray = [[NSMutableArray alloc] init];
for (int i = 0; i < 500; i++) {
NSString *w = a[i % [a count]];
NSString *s = [NSString stringWithFormat:#"Row: %d %#", i, w];
[songTitlesArray addObject:s];
}
filteredArray = [songTitlesArray copy];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return filteredArray.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
if (cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
cell.textLabel.text = [filteredArray objectAtIndex:indexPath.row];
return cell;
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
[self helper];
}
-(void)helper{
if ([self.searchBar.text isEqualToString:#""]){
filteredArray = [songTitlesArray copy];
} else {
NSString *w = [NSString stringWithFormat:#"SELF contains[c] '%#'", self.searchBar.text];
NSPredicate *sPredicate = [NSPredicate predicateWithFormat:w];
filteredArray = [songTitlesArray filteredArrayUsingPredicate:sPredicate];
}
[self.tableView reloadData];
}
#end
As a side note, you've done a nice job of demonstrating one of the (many) reasons NOT to try and develop on a jailbroken device.
I'm building an app (UITabBar) where I'm storing a NSMutableArray of custom objects. My custom object is called DayModel.
My DayModel.h file:
#import <Foundation/Foundation.h>
#interface DayModel : NSObject
#property (nonatomic, retain) NSDate *mydate;
#property (nonatomic) float myFloat;
#end
My DayModel.m file:
#import "DayModel.h"
#implementation DayModel
#synthesize myDate, myFloat;
-(id)init {
// Init self
self = [super init];
if (self) {
// Setup
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder;
{
[coder encodeObject:self.myDate forKey:#"myDate"];
[coder encodeObject:self.myFloat forKey:#"myFloat"];
}
- (id)initWithCoder:(NSCoder *)coder;
{
self = [[DayModel alloc] init];
if (self != nil)
{
self.myDate = [coder decodeObjectForKey:#"myDate"];
self.myFloat = [coder decodeFloatForKey:#"myFloat"];
}
return self;
}
#end
The "main" ViewController, saving the new objects:
// Add data to the DayModel class
DayModel *currentDay = [[DayModel alloc] init];
currentDay.myDate = myDate;
currentDay.myFloat = myFloat;
// Add currentDay to the _objects NSMutableArray
[_objects insertObject:currentDay atIndex:0];
// Save this array using NSKeyedArchiver
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_objects] forKey:#"objects"];
my UITableViewController displaying this:
viewWillAppear
// Load the _objects array
NSData *objectsData = [defaults objectForKey:#"objects"];
if (objectsData != nil)
{
NSArray *oldArray = [NSKeyedUnarchiver unarchiveObjectWithData:objectsData];
if (oldArray != nil)
{
_objects = [[NSMutableArray alloc] initWithArray:oldArray];
} else
{
_objects = [[NSMutableArray alloc] init];
}
} else
{
_objects = [[NSMutableArray alloc] init];
}
Other methods:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [_objects count];
}
Loading the data:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
// Get the DayModel
DayModel *currentModel = [[DayModel alloc] init];
currentModel = _objects[indexPath.row];
// Get the UILabels
UILabel *dateLabel = (UILabel *)[cell viewWithTag:10];
UILabel *floatLabel = (UILabel *)[cell viewWithTag:20];
// Create the DateFormatter
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"dd-MM-yyyy"];
// Set the text
dateLabel.text = [dateFormatter stringFromDate:currentModel.myDate];
floatLabel.text = [NSString stringWithFormat:#"%.02f", currentModel.myFloat];
return cell;
}
Reproducing the problem:
Add item from tab nr 1
Go to tab nr 2 (table). Data is displayed correctly
Go to tab nr 1 and add new object
Go to tab nr 2 (table). New item is displayed with data from previews item, not new data.
When the app is reloaded, the table displays correctly.
EDIT
What happens is that the new item is added at index 0 to appear at the top of the list, while the tableview class get's the new information from the last row when it should be getting it from the top. How can I "reverse" this?
Thanks!
Erik
I fixed it by reloading the whole list, not only adding the new item. This code is going under the lines loading the list from NSUserDefaults (viewWillAppear).
// Reload the UITableView completely
[self.tableView reloadData];
Please tell me if there's a better solution:)
I am new to iOS programming and I need some advice. I want to create a table view with sections to learn how it works. I have some objects from a model class user. I fill data in the table view using dictionaries. The table view is in a view Controller in my storyboard. The code works, but I don't know if this is a good way to handle the data in my table. Is this a semantic mistake to do this like I did?
Please take a Look at my code and give me some advice.
My user object (User.h):
#interface User : NSObject
#property (nonatomic, strong) NSString *email;
#property (nonatomic, strong) NSString *firstName;
#property (nonatomic, strong) NSString *lastName;
#property (nonatomic, strong) NSString *city;
#end
Dummy function to fill users into an array (in a helper class):
- (NSMutableArray *) createUser
{
NSMutableArray *userArray = [[NSMutableArray alloc] init];
User *user1 = [[User alloc] init];
user1.firstName = #"Donald";
user1.lastName = #"Duck";
user1.email = #"donald_duck#disney.com";
user1.city = #"Entenhausen";
User *user2 = [[User alloc] init];
user2.firstName = #"Micky";
user2.lastName = #"Maus";
user2.email = #"micky#disney.com";
user2.city = #"Entenhausen";
User *user3 = [[User alloc] init];
user3.firstName = #"Daisy";
user3.lastName = #"Duck";
user3.email = #"daisy_duck#disney.com";
user3.city = #"Frankfurt";
User *user4 = [[User alloc] init];
user4.firstName = #"Goofy";
user4.lastName = #"Goof";
user4.email = #"goofy#disney.com";
user4.city = #"Berlin";
User *user5 = [[User alloc] init];
user5.firstName = #"Some";
user5.lastName = #"Body";
user5.email = #"somebody#disney.com";
user5.city = #"Somewhere";
User *user6 = [[User alloc] init];
user6.firstName = #"Dagobert";
user6.lastName = #"Duck";
user6.email = #"dagobert#disney.com";
user6.city = #"Mainz";
[userArray addObject:user1];
[userArray addObject:user2];
[userArray addObject:user3];
[userArray addObject:user4];
[userArray addObject:user5];
[userArray addObject:user6];
return userArray;
}
Function to create a dictionary object (in a helper class):
- (NSMutableDictionary *) createDictionaryFromArray: (NSMutableArray *) allData
{
NSArray *arrayKeys = [[NSArray alloc] initWithObjects:#"Entenhausen", #"Frankfurt", #"Berlin", #"Mainz", nil];
NSMutableDictionary *collection = [[NSMutableDictionary alloc] init];
for (int i=0; i < arrayKeys.count; i++) {
NSString *key = [arrayKeys objectAtIndex:i];
NSMutableArray *allValues = [[NSMutableArray alloc] init];
for (User *usr in allData) {
if([usr.city isEqualToString:key]) {
[allValues addObject:usr];
}
}
[collection setObject:allValues forKey:key];
}
return collection;
}
My table view controller class (TableViewController.h):
#import <UIKit/UIKit.h>
#import "HelperClass.h"
#interface TableViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource> {
HelperClass *helper;
NSMutableArray *users;
NSArray *allKeys;
NSDictionary *dictionaryUsers;
}
TableViewController.m:
#import "TableViewController.h"
#implementation TableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
helper = [[HelperClass alloc] init];
users = [[NSMutableArray alloc] init];
users = [helper createUser];
dictionaryUsers = [[NSMutableDictionary alloc] init];
dictionaryUsers = [helper createDictionaryFromArray:users];
allKeys = [NSArray array];
allKeys = [dictionaryUsers allKeys];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{ // Return the number of sections.
return [allKeys count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
NSMutableArray *arrayValuesUsers = [[NSMutableArray alloc] init];
arrayValuesUsers = [dictionaryUsers objectForKey:[allKeys objectAtIndex:section]];
return [arrayValuesUsers count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [allKeys objectAtIndex:section];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
User *user = [[User alloc] init];
NSMutableArray *usersInSection = [dictionaryUsers objectForKey:[allKeys objectAtIndex:indexPath.section]];
user = [usersInSection objectAtIndex:indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:#"%#, %#", user.lastName, user.firstName];
cell.detailTextLabel.text = user.email;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *usersInSection = [dictionaryUsers objectForKey:[allKeys objectAtIndex:indexPath.section]];
User *usr = [usersInSection objectAtIndex:indexPath.row];
NSLog(#"user: %#, %#", usr.lastName, usr.firstName);
}
#end
First of all thumbs up for using a HelperClass for implementing general functions. Only when you do it, make it at least a singleton, so that you not always have to call:
helper = [[HelperClass alloc] init];
Or if you only have functions like createUser, these methods should be class methods.
Here are two general hints, which came to my mind, when looking at your code:
You have too many allocations in your code. For example in your TableViewController you do:
users = [[NSMutableArray alloc] init];
users = [helper createUser];
So first you allocate a new array and the first thing the createUser function does is to allocate another array in memory, which is then returned. Your first allocated memory space is never used.
When it comes to tableViews don't use dictionaries. Always stick with arrays. A dictionary is not ordered and when iterating through a dictionary you might get different results each time. An array is ordered and objectOfIndex:i will always return the same object.
So I adjusted your code to have a dynamic tableView sorting your users into sections by their city and sorting them alphabetically. (I guess this was what you wanted to achieve). The new code is not completely clean. You could try to use NSArray instead of NSMutableArray everywhere to save some memory for example.
Your User.h is untouched.
Here is the new HelperClass.h
// HelperClass.h
#import <Foundation/Foundation.h>
#interface HelperClass : NSObject
+ (HelperClass *)sharedHelperClass;
+ (NSMutableArray *) createUser;
+ (NSMutableArray *) getAllCitiesFromUserArray: (NSMutableArray *)userArray;
+ (NSMutableArray *)getUsersOfUsersArray: (NSMutableArray *)userArray fromCity: (NSString *)city;
#end
And the HelperClass.m
// HelperClass.m
#import "HelperClass.h"
#import "User.h"
#implementation HelperClass
+ (HelperClass *)sharedHelperClass
{
//This returns always the same object of HelperClass
static dispatch_once_t pred;
static HelperClass *_sharedHelperClass = nil;
dispatch_once(&pred, ^{ _sharedHelperClass = [[self alloc] init]; });
return _sharedHelperClass;
}
- (id)init{
self = [super init];
if (!self) {
return nil;
}
return self;
}
+ (NSMutableArray *) createUser
{
NSMutableArray *userArray = [[NSMutableArray alloc] init];
User *user1 = [[User alloc] init];
user1.firstName = #"Donald";
user1.lastName = #"Duck";
user1.email = #"donald_duck#disney.com";
user1.city = #"Entenhausen";
User *user2 = [[User alloc] init];
user2.firstName = #"Micky";
user2.lastName = #"Maus";
user2.email = #"micky#disney.com";
user2.city = #"Entenhausen";
User *user3 = [[User alloc] init];
user3.firstName = #"Daisy";
user3.lastName = #"Duck";
user3.email = #"daisy_duck#disney.com";
user3.city = #"Frankfurt";
User *user4 = [[User alloc] init];
user4.firstName = #"Goofy";
user4.lastName = #"Goof";
user4.email = #"goofy#disney.com";
user4.city = #"Berlin";
User *user5 = [[User alloc] init];
user5.firstName = #"Some";
user5.lastName = #"Body";
user5.email = #"somebody#disney.com";
user5.city = #"Somewhere";
User *user6 = [[User alloc] init];
user6.firstName = #"Dagobert";
user6.lastName = #"Duck";
user6.email = #"dagobert#disney.com";
user6.city = #"Mainz";
[userArray addObject:user1];
[userArray addObject:user2];
[userArray addObject:user3];
[userArray addObject:user4];
[userArray addObject:user5];
[userArray addObject:user6];
return userArray;
}
+ (NSMutableArray *) getAllCitiesFromUserArray: (NSMutableArray *)userArray{
NSMutableArray *cityArray = [[NSMutableArray alloc] init];
for (User *user in userArray) {
if (![cityArray containsObject: user.city]){
[cityArray addObject:user.city];
}
}
//Sort the city array alphabetically (localizedCaseInsensitiveCompare even regards Umlaute)
[cityArray sortUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
return cityArray;
}
+ (NSMutableArray *)getUsersOfUsersArray: (NSMutableArray *)userArray fromCity: (NSString *)city{
NSMutableArray *usersOfCityArray = [[NSMutableArray alloc] init];
for (User *user in userArray) {
if ([user.city isEqualToString:city]){
[usersOfCityArray addObject:user];
}
}
//Sort the array of custom objects by key lastName
NSSortDescriptor *sortDescriptor =
[NSSortDescriptor sortDescriptorWithKey:#"lastName"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)];
usersOfCityArray = [[usersOfCityArray sortedArrayUsingDescriptors:#[sortDescriptor]]mutableCopy];
return usersOfCityArray;
}
#end
The new TableViewController.h
// TableViewController.h
#import <UIKit/UIKit.h>
#interface TableViewController : UITableViewController
#end
And the new TableViewController.m
// TableViewController.m
#import "TableViewController.h"
#import "HelperClass.h"
#import "User.h"
#interface TableViewController ()
#property (nonatomic,strong) NSMutableArray *users;
#property (nonatomic,strong) NSMutableArray *sectionHeaders;
#end
#implementation TableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//This gets the Array of Users from the HelperClass class method
//You could access the HelperClass Singleton with [HelperClass sharedHelperClass], but this is not needed in this case.
self.users = [HelperClass createUser];
self.sectionHeaders = [HelperClass getAllCitiesFromUserArray:self.users];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return [self.sectionHeaders count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//Get the current section city
NSString *city = [self.sectionHeaders objectAtIndex:section];
NSMutableArray *sectionUsers =[HelperClass getUsersOfUsersArray:self.users fromCity:city];
return sectionUsers.count;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [self.sectionHeaders objectAtIndex:section];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
//Get the current section city
NSString *city = [self.sectionHeaders objectAtIndex:indexPath.section];
//Get users for the current city
NSMutableArray *sectionUsers =[HelperClass getUsersOfUsersArray:self.users fromCity:city];
//Get the user for the current cell
User *user = [sectionUsers objectAtIndex:indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:#"%#, %#", user.lastName, user.firstName];
cell.detailTextLabel.text = user.email;
return cell;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//Get the current section city
NSString *city = [self.sectionHeaders objectAtIndex:indexPath.section];
//Get users for the current city
NSMutableArray *sectionUsers =[HelperClass getUsersOfUsersArray:self.users fromCity:city];
//Get the user for the current cell
User *user = [sectionUsers objectAtIndex:indexPath.row];
NSLog(#"user: %#, %#", user.lastName, user.firstName);
}
#end
I am looking how to make a sum of all the cells in my tableview.
Each cell crated by user have a number on a label. This number is diferent in each cell.
How can i make the sum of all the numbers in all the cells?
I think its important to say im using Core Data.
Thanks, hope anyone can help me.
Be free to ask any other detail.
EDIT:
#synthesize fetchedResultsController = __fetchedResultsController;
#synthesize managedObjectContext = __managedObjectContext;
#synthesize selectedYear;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"fondo.png"]];
self.navigationItem.leftBarButtonItem = self.editButtonItem;
[self.tableView setSeparatorColor:[UIColor brownColor]];
}
- (void)setupFetchedResultsController
{
// 1 - Decide what Entity you want
NSString *entityName = #"Years"; // Put your entity name here
NSLog(#"Setting up a Fetched Results Controller for the Entity named %#", entityName);
// 2 - Request that Entity
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
// 3 - Filter it if you want
//request.predicate = [NSPredicate predicateWithFormat:#"Years.name = Blah"];
// 4 - Sort it if you want
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"subject"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)]];
// 5 - Fetch it
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
[self performFetch];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setupFetchedResultsController];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"My Cell";
MarksCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[MarksCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
Year *years = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.celltitlelabel.text = years.subject;
return cell;
}
Assuming that the Year entity has an attribute value which is an NSNumber:
NSArray *myNumbers = self.fetchedResultsController.fetchedObjects;
NSNumber *sum = [myNumbers valueForKeyPath:#"#sum.value"];
In another question of mine concerning the addition of an insert row in a UITableView backed by Core Data, I mentioned that my NSFetchedResultsController is assigning each object it fetches to a separate section in my UITableView. I assumed this was merely the default behavior, but Marcus S. Zarra said there might be something wrong with my configuration of the controller or my datasource delegate methods. I admit, my code feels a bit like Frankenstein with parts pulled from the Apple docs and numerous tutorials. This is my first program and my first time using Core Data, so please be gentle ;)
My table view controller's header is as follows:
#import <UIKit/UIKit.h>
#import "RubricAppDelegate.h"
#interface ClassList : UITableViewController {
NSMutableArray *classList;
NSFetchedResultsController *fetchedResultsController;
NSManagedObjectContext *managedObjectContext;
}
#property(nonatomic,retain) NSMutableArray *classList;
#property(nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
#property(nonatomic, retain) NSManagedObjectContext *managedObjectContext;
- (IBAction) grade:(id)sender;
#end
My implementation file includes a bunch of dummy test data. I included that in case I am using incorrect methods for instantiating the Core Data objects. Basically, I want to know if my NSFetchedResultsController should not be returning my objects (in this case, instances of myClass) into separate sections. If so, what am I doing to create that problem?
The ultimate goal at the moment is for me to be able to add an insert cell at the top of my table (I know that putting it at the bottom is "standard," but I like how it looks in the apps that do it the other way around). You will notice my -tableView:editingStyleForRowAtIndexPath: sets the cell style of section 0 to insert, but of course I need to figure out how to start the listing of myClass.classTitle at cell 1 instead of cell 0 (which is why I want to determine if having each object assigned to its own section is normal).
Here is my implementation file:
#import "ClassList.h"
#import "ClassRoster.h"
#import "RubricAppDelegate.h"
#import "Student.h"
#import "myClass.h"
#implementation ClassList
#synthesize classList;
#synthesize fetchedResultsController;
#synthesize managedObjectContext;
#pragma mark -
#pragma mark View lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
self.editing = YES;
RubricAppDelegate *appDelegate = (RubricAppDelegate *)[[UIApplication sharedApplication] delegate];
managedObjectContext = [appDelegate managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"myClass" inManagedObjectContext:managedObjectContext];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entity];
//test data
myClass *newClass = (myClass *) [NSEntityDescription insertNewObjectForEntityForName:#"myClass" inManagedObjectContext:managedObjectContext];
newClass.classTitle = #"UFDN 1000";
NSNumber *ID = [NSNumber numberWithInt:1];
newClass.classID = ID;
Student *newStudent = (Student *) [NSEntityDescription insertNewObjectForEntityForName:#"Student" inManagedObjectContext:managedObjectContext];
newStudent.classID = ID;
newStudent.studentName = #"Andy Albert";
newStudent.studentUsername = #"albera";
[newClass addStudentsObject:newStudent];
newStudent.classID = ID;
newStudent.studentName = #"Bob Dole";
newStudent.studentUsername = #"doleb";
[newClass addStudentsObject:newStudent];
newStudent.classID = ID;
newStudent.studentName = #"Chris Hanson";
newStudent.studentUsername = #"hansoc";
[newClass addStudentsObject:newStudent];
myClass *newClass2 = (myClass *) [NSEntityDescription insertNewObjectForEntityForName:#"myClass" inManagedObjectContext:managedObjectContext];
newClass2.classTitle = #"UFDN 3100";
ID = [NSNumber numberWithInt:2];
newClass2.classID = ID;
newStudent.classID = ID;
newStudent.studentName = #"Danny Boy";
newStudent.studentUsername = #"boyd";
[newClass2 addStudentsObject:newStudent];
newStudent.classID = ID;
newStudent.studentName = #"James Matthews";
newStudent.studentUsername = #"matthj";
[newClass2 addStudentsObject:newStudent];
newStudent.classID = ID;
newStudent.studentName = #"Aaron Todds";
newStudent.studentUsername = #"toddsa";
[newClass2 addStudentsObject:newStudent];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"classID" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
NSError *error;
fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"classTitle" cacheName:nil];
[fetchedResultsController performFetch:&error];
UIBarButtonItem *gradeButton = [[UIBarButtonItem alloc]
initWithTitle:#"Grade"
style:UIBarButtonItemStylePlain
target:self
action:#selector(grade:)];
self.navigationItem.rightBarButtonItem = gradeButton;
[gradeButton release];
}
- (IBAction) grade:(id)sender {
}
#pragma mark -
#pragma mark Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSLog(#"Number of sections = %d", [[fetchedResultsController sections] count]);
return ([[fetchedResultsController sections] count]);
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
id <NSFetchedResultsSectionInfo> myClass = [[fetchedResultsController sections] objectAtIndex:section];
NSLog(#"Number of classes = %d", [myClass numberOfObjects]);
return [myClass numberOfObjects];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
myClass *theClass = [fetchedResultsController objectAtIndexPath:indexPath];
NSLog(#"Class name is: %#", theClass.classTitle);
cell.textLabel.text = theClass.classTitle;
}
return cell;
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0) {
return UITableViewCellEditingStyleInsert;
}
else return UITableViewCellEditingStyleDelete;
}
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
myClass *result = (myClass *)[fetchedResultsController objectAtIndexPath:indexPath];
[managedObjectContext deleteObject:result];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
}
}
#pragma mark -
#pragma mark Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic may go here. Create and push another view controller.
/*
<#DetailViewController#> *detailViewController = [[<#DetailViewController#> alloc] initWithNibName:#"<#Nib name#>" bundle:nil];
// ...
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
*/
}
#pragma mark -
#pragma mark Memory management
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Relinquish ownership any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
// For example: self.myOutlet = nil;
}
- (void)dealloc {
[classList release];
[super dealloc];
}
#end
My RubricAppDelegate is essentially identical to the Apple documentation for setting up Core Data NSManagedObjectContext, NSPersistentStoreCoordinator, etc. However, if you think there might be a problem in there, just let me know and I'll post it.
Edit: I forgot to mention two reasons I know each object is being assigned to a different section.
1) NSLog(#"Number of sections = %d", [[fetchedResultsController sections] count]); inside of -numberOfSectionsInTableView: returns the number of myClass objects I have.
2) If I set -numberOfSectionsInTableView: to return 1, my table only displays one object and cuts the rest out.
You have sections because you tell the fetched results controller to create them by passing a non-Nil value for sectionNameKeyPath: when you initialize the FRC.
Change:
fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"classTitle" cacheName:nil];
...to:
fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil cacheName:nil];
... and the sections will go away. Otherwise, the FRC will create one section for each value of the classTitle attribute in the store. If each myClass instance has a different value for classTitle each instance will have its own section in the tableview.