I have a UITableView which reads items from an NSMutableArray called friendsList.
I initialise this array here:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
self.title = TA(#"Find Friends", #"");
self.navigationItem.rightBarButtonItem = [self createRightNavBarButton];
self.navigationItem.leftBarButtonItem = [self createLeftNavBarButton];
friendsList = [[NSMutableArray alloc]init];
}
return self;
}
I have set it's datasource method for numberOfRows to return friendslist.count if the count is greater than zero.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (friendsList.count) {
return friendsList.count;
}
else{
return 0;
}
}
After I fill the array in another method I'm calling [tableView reloadData] so that the datasource method is called again to read the count of the objects in the array.
- (void)loadContactsFromSource:(ListSource)source
{
if (friendsList) {
[friendsList removeAllObjects];
}
switch (source) {
case LS_Facebook:
break;
case LS_Twitter:
break;
case LS_Contacts:{
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(nil, nil);
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
if (granted) {
CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople( addressBook );
CFMutableArrayRef peopleMutable = CFArrayCreateMutableCopy(
kCFAllocatorDefault,
CFArrayGetCount(allPeople),
allPeople
);
CFArraySortValues(
peopleMutable,
CFRangeMake(0, CFArrayGetCount(peopleMutable)),
(CFComparatorFunction) ABPersonComparePeopleByName,
(void*) ABPersonGetSortOrdering()
);
CFIndex nPeople = ABAddressBookGetPersonCount( addressBook );
for ( int i = 0; i < nPeople; i++ )
{
ABRecordRef ref = CFArrayGetValueAtIndex(peopleMutable, i);
[self copyContactToArray:ref];
}
[tableFriends reloadData];
}
else{
[PopupHandler popupDialogWithTitle:T(#"Error", #"")
message:T(#"Access denied", #"")
delegate:nil];
}
});
break;
}
default:
break;
}
}
Here is the cellForRow method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
NSDictionary *currentPerson = [friendsList objectAtIndex:indexPath.row];
cell = [self fillCell:cell withUserInfo:currentPerson];
return cell;
}
The problem is that after this table remains blank, the items from the array are not show. If I touch the table with my finger the items show up instantly, as if the touch somehow refreshes the table. Any ideas what might be causing this?
I'm testing this on an iPhone 5 with iOS 7, built from XCode 5.0.1 .
It looks like ABAddressBookRequestAccessWithCompletion is being called on a separate thread and the block that it runs when completed is not in the main thread. So updating the UI inside the block will not work properly. Try either creating a function and calling that function on the main thread from inside your addressbook return or doing a dispatch_async inside the addressbook return block.
Inside your return block from ABAddressBookRequestAccessWithCompletion dispatch into the main thread by either using dispatch_async or performSelectorOnMainThread.
Related
I am trying to load Contacts that contain addresses to my TableView. I attempted to debug it and the NSLogs are showing that the data is there. However when I try to load it to my UITableView, it hangs on a black screen and nothing happens. The debugger shows the data is printed to console.
I am using an iOS 7 simulator for testing my project.
#implementation ViewController
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
_contactsArray = [[NSMutableArray alloc] init];
[self getArrayOfPeople];
NSLog(#"array: %#", self.contactsArray);
}
return self;
}
- (void)getArrayOfPeople {
//ABAddressBookRef addressBook = ABAddressBookCreate();
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFIndex nPeople = ABAddressBookGetPersonCount(addressBook);
__block BOOL accessGranted = NO;
if (ABAddressBookRequestAccessWithCompletion != NULL) { // we're on iOS 6
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
accessGranted = granted;
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
else { // we're on iOS 5 or older
accessGranted = YES;
}
if (accessGranted) {
for( CFIndex emailIndex = 0; emailIndex < nPeople; emailIndex++ ) {
ABRecordRef person = CFArrayGetValueAtIndex( allPeople, emailIndex );
//ABMutableMultiValueRef emailRef= ABRecordCopyValue(person, kABPersonEmailProperty);
ABMutableMultiValueRef addressRef= ABRecordCopyValue(person, kABPersonAddressProperty);
int addressCount = (int)ABMultiValueGetCount(addressRef);
if(!addressCount) {
CFErrorRef error = nil;
ABAddressBookRemoveRecord(addressBook, person, &error);
if (error) NSLog(#"Error: %#", error);
} else {
ABMultiValueRef address = ABRecordCopyValue(person, kABPersonAddressProperty);
NSString *contactAddress = (__bridge NSString *)ABMultiValueCopyValueAtIndex(address, 0);
NSString *name = (__bridge NSString *)(ABRecordCopyValue(person, kABPersonFirstNameProperty));
if (name) {
NSMutableDictionary *contactDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
name, #"name",
contactAddress, #"address",
nil];
[self.contactsArray addObject:contactDict];
NSLog(#"%#", self.contactsArray);
}
}
}
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [_contactsArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
cell.textLabel.text = [_contactsArray objectAtIndex:indexPath.row];
return cell;
}
#end
I hope my pasted code above will help find the "bug" thats causing my code to malfunction.
try calling reloadData on your table view after you populate your array
[myTableView reloadData];
You are adding NSDictionary items to the array, but assigning them to cell.textLabel.text in your cellForRowAtIndexPath:. Try
cell.textLabel.text = [[_contactsArray objectAtIndex:indexPath.row] valueForKey:#"name"];
You do this:
CFIndex nPeople = ABAddressBookGetPersonCount(addressBook);
before requesting access to the address book, so you get -1 and you're stuck in a very consuming loop in getArrayOfPeople. Move the following lines:
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFIndex nPeople = ABAddressBookGetPersonCount(addressBook);
into the if (accessGranted) block.
Did you modify its launch in the appDidFinishLaunching or something else that you think may have had something to do with its normal launch
Check and try following things to debug the problem.
If its your 1st view ,just check if you have connect view to root of Navigation controller
Try to remove the class from the view and check if view is loading.
Try to add a new view Controller without linking class and add it to your navigation controller and check if its displaying white or black screen.
I have implemented MVC in my project. In Model I have a class DBHandler containing below method which is suppose to return me names of saved items
- (NSMutableArray *) displayAllItems
{
NSString* selectAllResume = [NSString stringWithFormat:#"select * from Items"];
sqlite3_stmt* selectStatement = (sqlite3_stmt *)[self getStatement:selectAllResume];
while(sqlite3_step(selectStatement) == SQLITE_ROW)
{
#try {
ResumeDC *resume = [[ResumeDC alloc] init];
NSString *result_output = #"";
int numberOfCoulmns = 1;
for (int a=0; a<numberOfCoulmns; a++)
{
resume.resumeName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectStatement, a)];
NSString *formatout = [NSString stringWithFormat:#"%#", resume.resumeName];
result_output = [result_output stringByAppendingString: formatout];
}
[dataList addObject:result_output]; // the value in the datalist is empty later on.
}
#catch (NSException *exception) {
NSLog(#"We were unable to find the files.");
}
return dataList;
}
}
In View, I have a class SavedItems. Here in viewDidLoad() I am trying to access all the data being read by the above method and get it displayed in the tableview.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.tvMine.delegate = self;
self.tvMine.dataSource = self;
GET_DBHANDLER
[dbHandler displayAllItems];
mydataList = dbHandler.dataList;
numberOfRows = [mydataList count];
[tvMine reloadData];
}
And thats how I am getting it displayed in the table (Just providing it, else the below code is perfect).
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyIdentifier"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"MyIdentifier"];
}
if ([mydataList count] > 0) {
cell.textLabel.text = [mydataList objectAtIndex:indexPath.row];
}
return cell;
}
But I get nothing in return from the displayAllItems when it is being called outside it's class.
The answer should have been what was being asked. I asked people about the method calling and didn't get a single answer. Well the mistake in my code was of method calling as already mentioned by me. Below is the code of doing it the right way
mydataList= [dbHandler displayAllResumes]; // mydataList is an NSMutableArray.
numberOfRows = [mydataList count];
[tvMine reloadData];
Now everything is working perfectly. Thanks for Your Help :/
*Update: This actually works, the style for my custom cell hasn't come across and so the cell looks blank. How then do I get the searchResultsTableView to use my custom cell?
I have implemented a search bar in my table view. Searching, filtering all work when I debug, but when I enter characters into the search bar, the results do not load or show. This is everything I'm doing:
#interface InviteTableViewController ()<UIAlertViewDelegate, UISearchBarDelegate, UISearchDisplayDelegate>
#property(nonatomic, strong) NSNumber *contactsCount;
#property(nonatomic, strong) InviteTableViewCell *selectedCell;
#property(strong, nonatomic) NSMutableArray *filteredContactArray;
#property (weak, nonatomic) IBOutlet UISearchBar *contactsSearchBar;
#end
#implementation InviteTableViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void) extractAllContacts: (ABAddressBookRef) addressBookRef{
NSMutableArray *contactsArray = [NSMutableArray array];
CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople(addressBookRef);
CFIndex numberOfPeople = ABAddressBookGetPersonCount(addressBookRef);
for(int i = 0; i < numberOfPeople; i++){
ABRecordRef person = CFArrayGetValueAtIndex( allPeople, i );
NSString *firstName = (__bridge NSString *)(ABRecordCopyValue(person, kABPersonFirstNameProperty));
if(firstName){
ABMultiValueRef emails = ABRecordCopyValue(person, kABPersonEmailProperty);
for (CFIndex i = 0; i < ABMultiValueGetCount(emails); i++) {
NSString *email = (__bridge_transfer NSString *) ABMultiValueCopyValueAtIndex(emails, i);
MyUser *amUser = [[MyUser alloc] init];
amUser.email =email;
NSString *fullName =[NSString stringWithFormat:#"%# %#",firstName, (__bridge NSString *)(ABRecordCopyValue(person, kABPersonLastNameProperty))];
amUser.fullName = fullName;
[contactsArray addObject:amUser];
}
}
}
NSLog(#"================================count ============= %d", [contactsArray count]);
contactsArray = [contactsArray sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSDate *first = [(MyUser*) a fullName];
NSDate *second = [(MyUser*)b fullName];
return [first compare:second];
}];
self.inviteContactsArray = contactsArray;
self.filteredContactArray = [NSMutableArray arrayWithCapacity:[contactsArray count]];
[self.tableView reloadData];
}
- (void)viewDidLoad
{
[super viewDidLoad];
_contactsCount = [NSNumber numberWithInt:0];
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, NULL);
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined) {
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) {
if (granted) {
[self extractAllContacts: addressBookRef];
[self.tableView reloadData];
} else {
NSString *message = [NSString stringWithFormat:#"You have not given permission to use your address book. Please allow in settings "];
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"Enable Contacts" message:message delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
});
}
else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
// The user has previously given access, add the contact
[self extractAllContacts:addressBookRef];
}
else {
// The user has previously denied access
// Send an alert telling user to change privacy setting in settings app
}
[self.tableView reloadData];
}
- (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
{
if (tableView == self.searchDisplayController.searchResultsTableView) {
return [self.filteredContactArray count];
} else {
return [self.inviteContactsArray count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"InviteCell";
InviteTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if ( cell == nil ) {
cell = [[InviteTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
MyUser *person = nil;
if (tableView == self.searchDisplayController.searchResultsTableView)
{
person = [self.filteredContactArray objectAtIndex:[indexPath row]];
NSMutableArray *tempArray = self.filteredContactArray;
}
else
{
person = [self.inviteContactsArray objectAtIndex:[indexPath row]];
}
//InviteTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
person = [self.inviteContactsArray objectAtIndex:indexPath.row];
cell.nameLabel.text = person.fullName;
cell.emailLabel.text = person.email;
return cell;
}
#pragma mark Content Filtering
-(void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope {
// Update the filtered array based on the search text and scope.
// Remove all objects from the filtered search array
[self.filteredContactArray removeAllObjects];
// Filter the array using NSPredicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.fullName contains[c] %#",searchText];
self.filteredContactArray = [NSMutableArray arrayWithArray:[self.inviteContactsArray filteredArrayUsingPredicate:predicate]];
}
#pragma mark - UISearchDisplayController Delegate Methods
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
[self filterContentForSearchText:searchString
scope:[[self.searchDisplayController.searchBar scopeButtonTitles]
objectAtIndex:[self.searchDisplayController.searchBar
selectedScopeButtonIndex]]];
return YES;
}
#end
This is what it looks like:
Agree with #rdelmar.
BUT There is a kind of tricky behavior in TableView, if you change the code in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
....
InviteTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
....
}
to
InviteTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if added the prefix "self." your code should works fine.
and
if ( cell == nil )
{
cell = [[InviteTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
is unnecessary. it will just create a standard "Subtitle Cell" for you that seems not you want, just remove them.
If you want to use the same cell for your main table and the search results table, you should make the cell in a xib (or you could do it entirely in code). In viewDidLoad, register the nib for both table views,
- (void)viewDidLoad {
[super viewDidLoad];
[self.searchDisplayController.searchResultsTableView registerNib:[UINib nibWithNibName:#"InviteTableViewCell" bundle:nil] forCellReuseIdentifier:#"InviteCell"];
[self.tableView registerNib:[UINib nibWithNibName:#"InviteTableViewCell" bundle:nil] forCellReuseIdentifier:#"inviteCell"];
}
In cellForRowAtIndexPath, you can do something like this,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
InviteTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"InviteCell" forIndexPath:indexPath];
MyUser *person = ([tableView isEqual:self.tableView])? self.inviteContactsArray[indexPath.row] : self.filteredContactArray[indexPath row];
cell.nameLabel.text = person.fullName;
cell.emailLabel.text = person.email;
return cell;
}
If you've already setup your cell in the storyboard, you can copy and paste it into an empty xib file, so you don't have to set it up all over again. You can delete the cell from your storyboard table view since you will be getting the cell from the xib file instead.
Below line in viewdidload should do the trick
self.searchDisplayController.searchResultsTableView.rowHeight = self.tableView.rowHeight
I'm getting an error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0.
because I'm adding an object to one instance of an array, but then reloading the table view from another instance of what is supposed to be the same array. How can I create a single instance of the array and just pass it around from class to class? I can do it easily in java, but in Objective-C you can't make static variables so I'm not sure how to do this.
EDIT: more code.
Here is a method from another class that is called in order to save a file. I'm using Core Data, so first it adds the file to the context (model), then to the array, then it saves the context. This method is in a class called 'Player'
-(BOOL)saveRecording {
Bank *B = [MusikerViewController daBank];
AudioTableViewController *ATVC2 = [MusikerViewController ATVControl];
NSLog(#"Place A");
AudioFile *myAudioFileMusicX314 = [[B addAudioFileEntityToModelWithDate:myDate andURLString:strPath] retain];
NSLog(#"Place B");
myAudioFileMusicX314.type = true;
[ATVC2 addAudioEntityToArray:myAudioFileMusicX314];
NSLog(#"Place C ***********************************************");
if(![B saveContext]) { //save context after adding file to keep consistancy
NSLog(#"addAudioFileEntityToModel is returning a nil managedObjectContext");
return NO;
}
NSLog(#"Place D");
[myDate release];
[strPath release];
[myAudioFileMusicX314 release];
[ATVC2 release];
NSLog(#"Place E");
return YES;
}
The following method is in the class that contains the table view--its caled AudioTableViewController
-(void)addAudioEntityToArray:(AudioFile *)event {
NSIndexPath *indexPath;
if(event.type) {
[[MusikerViewController recordingsArray] addObject:event];//self?
indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
}
else {
[[MusikerViewController downloadsArray] addObject:event];
indexPath = [NSIndexPath indexPathForRow:0 inSection:1];
}
[[self tableView] setEditing:YES animated:NO];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationNone];
}
The following method adds the object to my model
- (AudioFile *)addAudioFileEntityToModelWithDate:(NSDate *)theD andURLString:(NSString *)str {
NSLog(#"addAudio...WithDate -- called");
sound = (AudioFile *)[NSEntityDescription insertNewObjectForEntityForName:#"AudioFile" inManagedObjectContext:managedObjectContext];
sound.creationDate = theD;
sound.soundPath = str; //set the sound path to the sound file's url
[self alertForTitle];
NSLog(#"No problems yet at place D - addaudio...String, sound title is %#", sound.title);
NSLog(#"Context at addAudioFileEntityToModel is: %#", managedObjectContext);
return sound;
}
here is the important parts of MusikViewController.h -- it keeps track of recordingsArray and downloadsArray
#interface MusikerViewController : UIViewController {
}
NSMutableArray *recordingsArray;
NSMutableArray *downloadsArray;
+ (NSMutableArray *)recordingsArray;
+ (NSMutableArray *)downloadsArray;
and MusikViewController.m
+ (NSMutableArray *)recordingsArray {
NSLog(#"recordingsArray called");
if(!recordingsArray) {
recordingsArray = [[NSMutableArray alloc] init];
NSMutableArray *bigTempArray = [[[[Bank alloc] init] autorelease] getFetchArray]; //change this
for(AudioFile *af in bigTempArray)
if(af.type) {
[recordingsArray addObject:af];
}
NSLog(#"recordingsArray exists");
}
return recordingsArray;
}
+ (NSMutableArray *)downloadsArray {
NSLog(#"recordingsArray called");
if(!downloadsArray) {
downloadsArray = [[NSMutableArray alloc] init];
// if(!bigTempArray)
NSMutableArray *bigTempArray = [[[[Bank alloc] init] autorelease] getFetchArray];
for(AudioFile *af in bigTempArray)
if(!af.type) {
[downloadsArray addObject:af];
}
}
return downloadsArray;
}
and some AudioTableViewController methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
NSLog(#"Place F");
if(section == 0) {
return [[MusikerViewController recordingsArray] count];
}
else if (section == 1) {
return [[MusikerViewController downloadsArray] count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
AudioFile *event;
if(indexPath.section == 0) {
event = (AudioFile *)[[MusikerViewController recordingsArray] objectAtIndex:indexPath.row];
} else if (indexPath.section == 1) {
NSLog(#"downAry indexPath caled at cellForRow...Path");
event = (AudioFile *)[[MusikerViewController downloadsArray] objectAtIndex:indexPath.row];
}
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
if(event.title) {
cell.detailTextLabel.text = [Player dateString:event.creationDate];
cell.textLabel.text = event.title;
} else {
cell.textLabel.text = [Player dateString:event.creationDate];
cell.detailTextLabel.text = nil;
}
return cell;
}
- (void)viewDidLoad //viewDidLoad for AudioTableViewController
{
[[self tableView] reloadData];
NSLog(#"viewDidLoad called for AudioTableViewController");
[super viewDidLoad];
self.title = #"Audio Files";//put this in application delegate
// Set up the buttons.
self.navigationItem.leftBarButtonItem = self.editButtonItem;
}
If your data model changes you need to either reload the tableView, or insert/remove the rows otherwise the tableview will freak out on you. It doesn't matter how many times tableView:numberOfRowsInSection is called, your data model can't return a different number without first reloading or updating the tableview.
Based on your logging my guess is that your downloads array is changing quantities. It makes it past the NSLog then dies in _endCellAnimationsWithContext.
I'm willing to change a specific header view of my UITableView when I click a row.
I've read all posts about it yet. I tried "reloadData", "setNeedDisplay", "reloadSections:withRowAnimation:", and several others ideas... there is nothing to do. My header view either doesn't update or it does weird things like updating only when I move the table view (which is not what I'm willing to achieve).
My code looks like this for now (regarding the UITableView delegates methods):
-(NSInteger)numberOfSectionsInTableView:(UITableView*)tableView {
if(tableView==_storeTableView){
return [_storeDataArray count];
} else {
return 1;
}
}
-(UIView*)tableView:(UITableView*)tableView viewForHeaderInSection:(NSInteger)section {
if(tableView==_storeTableView){
HouraStoreHeaderModel *headerModel = [self.headerInfoArray objectAtIndex:section];
if (!headerModel.headerView) {
NSString *shelfName = headerModel.shelf;
headerModel.headerView = [[[HouraStoreHeaderView alloc] initWithFrame:CGRectMake(0.0, 0.0, _storeTableView.bounds.size.width, 80) title:shelfName section:section subheaderNumber:([headerModel.openedSubHeaders count]-1) delegate:self] autorelease];
}
return headerModel.headerView;
} else {
return nil;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if(tableView==_storeTableView){
HouraStoreHeaderModel *headerModel = [self.headerInfoArray objectAtIndex:section];
NSDictionary *myDict = _storeDataDict;
for (NSInteger i = 0; i < [headerModel.openedSubHeaders count]; i++) {
myDict = [myDict objectForKey:[headerModel.openedSubHeaders objectAtIndex:i]];
}
NSInteger numberOfRowsInSection = [[myDict allKeys] count];
return headerModel.open ? numberOfRowsInSection : 0;
} else if(tableView==_searchTableView){
return [_resultArray count];
} else {
return 0;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
if(tableView==_storeTableView){
HouraStoreHeaderModel *headerModel = [self.headerInfoArray objectAtIndex:indexPath.section];
NSDictionary *myDict = _storeDataDict;
for (NSInteger i = 0; i < [headerModel.openedSubHeaders count]; i++) {
myDict = [myDict objectForKey:[headerModel.openedSubHeaders objectAtIndex:i]];
}
cell.accessoryView=[[[HouraStoreCellView alloc] initWithFrame:CGRectMake(0.0, 0.0, _storeTableView.bounds.size.width, 50) title:[[myDict allKeys] objectAtIndex:indexPath.row]] autorelease];
return cell;
} else if (tableView==_searchTableView) {
cell.textLabel.text = [_resultArray objectAtIndex:indexPath.row];
return cell;
} else {
return cell;
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
HouraStoreHeaderModel *headerModel = [self.headerInfoArray objectAtIndex:section];
NSInteger height = 59.0 + ([headerModel.openedSubHeaders count]-1)*41.0;
return height;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if(tableView==_storeTableView){
HouraStoreHeaderModel *headerModel = [self.headerInfoArray objectAtIndex:indexPath.section];
NSDictionary *myDict = _storeDataDict;
for (NSInteger i = 0; i < [headerModel.openedSubHeaders count]; i++) {
myDict = [myDict objectForKey:[headerModel.openedSubHeaders objectAtIndex:i]];
}
if ([[myDict objectForKey:[[myDict allKeys] objectAtIndex:indexPath.row]] isKindOfClass:[NSDictionary class]]) {
[self cellOpened:indexPath];
} else {
[_activityIndicatorView startAnimating];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(_listProductsFoundedFinished:)
name:HouraSearchProductsDone
object:nil];
NSString *searchString = [[myDict allKeys] objectAtIndex:indexPath.row];
searchString = [searchString stringByReplacingOccurrencesOfString:#"\"" withString:#"\\u0022"];
[_singleton.util beginSearchProducts:searchString context:#"2"];
}
} else if(tableView==_searchTableView){
_searchBar.text = [_resultArray objectAtIndex:indexPath.row];
[_searchBar resignFirstResponder];
[_activityIndicatorView startAnimating];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(_listProductsFoundedFinished:)
name:HouraSearchProductsDone
object:nil];
[_singleton.util beginSearchProducts:_searchBar.text context:#"2"];
}
}
-(void)headerView:(HouraStoreHeaderView*)headerView headerOpened:(NSInteger)headerOpened {
if (self.openSectionIndex!=NSNotFound) {
[self closeAllHeaders];
}
//[self closeAllHeaders];
HouraStoreHeaderModel *headerModel =nil;
headerModel = [self.headerInfoArray objectAtIndex:headerOpened];
headerModel.open = YES;
headerModel.headerView.disclosureButton.selected = YES;
NSDictionary *myDict = _storeDataDict;
for (NSInteger i = 0; i < [headerModel.openedSubHeaders count]; i++) {
myDict = [myDict objectForKey:[headerModel.openedSubHeaders objectAtIndex:i]];
}
NSInteger countOfRowsToInsert = [[myDict allKeys] count];
NSMutableArray *indexPathsToInsert = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < countOfRowsToInsert; i++) {
[indexPathsToInsert addObject:[NSIndexPath indexPathForRow:i inSection:headerOpened]];
}
NSMutableArray *indexPathsToDelete = [[NSMutableArray alloc] init];
NSInteger previousOpenSectionIndex = self.openSectionIndex;
if (previousOpenSectionIndex != NSNotFound) {
HouraStoreHeaderModel *previousHeaderModel = [self.headerInfoArray objectAtIndex:previousOpenSectionIndex];
previousHeaderModel.open = NO;
previousHeaderModel.headerView.disclosureButton.selected = NO;
[previousHeaderModel.headerView toggleOpenWithUserAction:NO];
NSInteger countOfRowsToDelete = [[[_storeDataDict objectForKey:previousHeaderModel.shelf ] allKeys] count];
for (NSInteger i = 0; i < countOfRowsToDelete; i++) {
[indexPathsToDelete addObject:[NSIndexPath indexPathForRow:i inSection:previousOpenSectionIndex]];
}
}
UITableViewRowAnimation insertAnimation;
UITableViewRowAnimation deleteAnimation;
if (previousOpenSectionIndex == NSNotFound || headerOpened < previousOpenSectionIndex) {
insertAnimation = UITableViewRowAnimationTop;
deleteAnimation = UITableViewRowAnimationBottom;
} else {
insertAnimation = UITableViewRowAnimationBottom;
deleteAnimation = UITableViewRowAnimationTop;
}
[_storeTableView beginUpdates];
[_storeTableView deleteRowsAtIndexPaths:indexPathsToDelete withRowAnimation:deleteAnimation];
[_storeTableView insertRowsAtIndexPaths:indexPathsToInsert withRowAnimation:insertAnimation];
[_storeTableView endUpdates];
self.openSectionIndex = headerOpened;
}
-(void)headerView:(HouraStoreHeaderView*)headerView headerClosed:(NSInteger)headerClosed {
HouraStoreHeaderModel *headerModel = [self.headerInfoArray objectAtIndex:headerClosed];
headerModel.open = NO;
headerModel.headerView.disclosureButton.selected = NO;
[headerModel cleanOpenedSubHeaders];
[self.headerInfoArray replaceObjectAtIndex:headerClosed withObject:headerModel];
NSInteger countOfRowsToDelete = [_storeTableView numberOfRowsInSection:headerClosed];
if (countOfRowsToDelete > 0) {
NSMutableArray *indexPathsToDelete = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < countOfRowsToDelete; i++) {
[indexPathsToDelete addObject:[NSIndexPath indexPathForRow:i inSection:headerClosed]];
}
[_storeTableView deleteRowsAtIndexPaths:indexPathsToDelete withRowAnimation:UITableViewRowAnimationTop];
}
self.openSectionIndex = NSNotFound;
}
-(void)cellOpened:(NSIndexPath*)indexPath {
HouraStoreHeaderModel *headerModel = [self.headerInfoArray objectAtIndex:indexPath.section];
[self headerView:headerModel.headerView headerClosed:indexPath.section];
[headerModel addOpenedSubHeaders:[[[_storeDataDict objectForKey:headerModel.shelf] allKeys] objectAtIndex:indexPath.row]];
[self.headerInfoArray replaceObjectAtIndex:indexPath.section withObject:headerModel];
headerModel = [self.headerInfoArray objectAtIndex:indexPath.section];
[self headerView:headerModel.headerView headerOpened:indexPath.section];
}
-(void)closeAllHeaders {
for (NSInteger i = 0; i < [self.headerInfoArray count]; i++) {
HouraStoreHeaderModel *headerModel = [self.headerInfoArray objectAtIndex:i];
[self headerView:headerModel.headerView headerClosed:i];
}
}
What I'd like to do is, when I click a row, the section header update so it contains a new button with the row text. Then I dismiss the row and reload new datas in the section rows. I managed to handle the rows perfectly. But I can't find a way to get this header view updated.
Thx for any idea.
You just change it directly. I created an instance variable in the header file for a label that I will put in the header's view I'll create:
#interface MainViewController : UITableViewController {
// creating my datasource array instance variable
NSArray *_items;
// this is the label I will add to the header view when I create it
UILabel *_headerLabel;
}
#end
And in my tableView when they select a row I call a function that simply changes the text on the label:
#implementation MainViewController
- (id)init {
self = [super initWithStyle:UITableViewStyleGrouped];
/ filling my datasource with test strings
_items = #[#"one", #"two"];
return self;
}
- (void)changeHeaderLabel:(NSString *)newLabel {
// when this function gets called and is passed a string, I will simply
// set the text on the label to the new string and viola!
_headerLabel.text = newLabel;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// this table will only have a single section for demo purposes
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// return the count of my datasource array
return _items.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// attempt to create a cell by reusing one with a given identifier
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
// if I wasn't able to reuse one
if (cell == nil) {
// create one from scratch with that identifier
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
// now simply set the text on the cell from my data source array of strings
cell.textLabel.text = _items[indexPath.row];
// and return the cell
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// deselect the row so the cell automatically fades out after selection
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// here you could do one of two things, either get a reference to the cell itself,
// and then get the value stored in it's textLabel
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
NSString *newHeaderTitleString = selectedCell.textLabel.text;
// OR you can get it right from your datasource
NSString *newHeaderTitleString = _items[indexPath.row];
// then just call the above function with the string as the single param
[self changeHeaderLabel:newHeaderTitleString];
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
// here I just create a view that will span the whole frame and is an arbitrary height
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 80)];
// set the background color to clear
headerView.backgroundColor = [UIColor clearColor];
// then I initialize my instance variable with a frame that's centered in the view
// for aesthetic purposes
_headerLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, self.view.frame.size.width - 10, 80)];
// then I set the text color, add an autoresizing mask so if the view rotates
// it still remains centered properly, set the text to some starting value,
// and add it to the headerView I previously created
_headerLabel.textColor = [UIColor darkGrayColor];
_headerLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
_headerLabel.text = #"Before";
[headerView addSubview:_headerLabel];
// then I return the headerView
return headerView;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
// return an arbitrary height here for testing
return 80;
}
That results in the following:
If you have any questions let me know! This is just a quick example to demonstrate it, but you may want to customize the view in a different way altogether. This should at least solve your problem and give you a starting point to work from.
Have you tried reloadRowsAtIndexPaths:withRowAnimation: where you set the row property of the NSIndexPath passed in as NSNotFound? So reloading just the header of section 3, for instance would look like
NSIndexPath * headerIndexPath = [NSIndexPath indexPathForRow: NSNotFound section:3];
[self.tableView reloadRowsAtIndexPaths:#[headerIndexPath] withRowAnimation: UITableViewRowAnimationAutomatic];
I guarantee nothing, but I'm pretty sure it used to work before, because I used it a couple of times.
But even if it works, it's still a hack that might get broken by Apple any time.
edit
Ok, never mind. I tried this with iOS 7 in Xcode 5 and for some reason, even with NSNotFound as the row number, it still reloads the whole sections (with all its cells). So this does not work any more, damn.