UITableViewCell - Display Loader whilst pulling data - ios

I have a fitness plan page with an app which is a UITableViewController with multiple custom uitableviewcells. It relies on a reasnably large data feed - I'd like to show a loader when accessing this page whilst the data feed is being pulled back from the server.
I have setup a custom uiTableviewCell containg a styled loader message / activity indicator and would like to show this on load - then when the data is available - refresh the tableview and deque the data into the relevant cells.
Currently I have the following method in my viewdidload method, which currently shows an alert if the feed hasn't completed its load-
[[RKObjectManager sharedManager].HTTPClient setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
if(status == AFNetworkReachabilityStatusNotReachable)
{
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:nil
message:#"There is no network connection!"
delegate:nil
cancelButtonTitle:#"Dismiss"
otherButtonTitles:nil];
[alert show];
}
else
{
i'd like to alter this to show the loader cell instead - then refresh the view once the data load is complete -
I've altered to the following -
[[RKObjectManager sharedManager].HTTPClient setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
if(status == AFNetworkReachabilityStatusNotReachable)
{
_WoHpTV.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
workoutBannerCell *cell = [_WoHpTV
dequeueReusableCellWithIdentifier:#"loaderCell" ];
}
else
{
so i've got a refernce to the custom cell in the above - but my question is how do I add it to my TableView?

i've got a refernce to the custom cell in the above - but my question
is how do I add it to my TableView?
You need to implement datasource methods of UITableView. Since you want to show loading indicators in each of the table cell you would need a temp data (You cannot load 0 cells in table view even to show loader you need some visible cells). In view did load create an array of temp objects and call reloadData of tableview.
for(int i = 0; i < 5; i++) {
[_dataArray addObject:#{#"text":#"text_value"}];
_isDataLoaded = NO;
[_table reloadData];
Now this will populate your table with Temp data. Once HTTP call is done set that BOOL isDataLoaded to YES
Then in Data Source methods -
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (!_isDataLoaded)
return _dataArray.count; // No data return count for Temp Data
else
return _feedArray.count; // Return correct feed items count
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCustomCell *cell = [_tableView dequeueReusableCellWithIdentifier:#"cell_id"];
if (cell == nil)
// Initialise cell here
if (!_isDataLoaded) { // Data has not yet loaded. Set loaders
NSDictionary *data = [_dataArray objectAtIndex:indexPath.row];
// set Cell properties here
} else {
// Fetch data from feed array
}
}
And in your question you are trying to detect if feed is loaded or not based on AFNetworkReachabilityStatusNotReachable. Which is actually incorrect, as this state indicates no internet connection. To track availability of data a simple BOOL as shown above will do.

Related

Objective c - Some array items are not shown in my UITableView (some shown twice)

I have my main view controller that shows a UITableView.
Each cell of this are custom (I've created a UIView for custom presentation).
For showing these items in my tableView, I populate an array with the content of the "allFilesFolderPath" folder with this code:
- (void)configureView {
_itemArray = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:allFilesFolderPath error:nil];
}
and
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.itemArray count];
}
and I create my custom cells for showing them with :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
myItem = [self.itemArray objectAtIndex:row];
NSLog(#"My Item : %#", _itemArray.description);
static NSString *CellIdentifer = #"cardCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifer];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifer];
}
return cell;
}
When I print the array with the NSLog, I get the correct list of item and in the alphabetical order (like how they are stored in the Documents location on my iPhone):
My Item : (
Music,
Music10,
Music2,
Music3,
Music4,
Music5,
Music6,
Music7,
Music8,
Music9,
Photos,
Videos
)
But when I run the app in my iPhone (or in the simulator), the cells are correctly displayed (in the order) until the eighth item. After this number, in my case, instead of having "Music8", "Music9", "Photos", "Video" I come back to the beginning of th array so "Music", "Music10", "Music2" and "Music3"
To better understand what I get, here is the screenshots :
I'm really lost! I've searched (and search again) what I'm doing wrong but I don't find anything, everything is correct for me.
Please help me to find my issue so that I can sleep normally.
EDIT: here is the method I've set to retrieve the myItem string from my other class :
+ (NSString *)getItemName {
return myItem;
}
And here is how I retrieve it from my other class :
NSString *test = [ViewController getItemName];
_itemName.text = test;
EDIT2 : Here is the code used for setting my custom TableViewCell
(sorry for missing these informations
#import "TableViewCell.h"
#implementation TableViewCell
- (void)awakeFromNib {
// Initialization code
[self cardSetup];
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (void)cardSetup {
_cardView.layer.masksToBounds = NO;
_cardView.layer.shadowOffset = CGSizeMake(0, 1);
_cardView.layer.shadowRadius = 1;
_cardView.layer.shadowOpacity = 0.3;
NSString *test = [ViewController getItemName];
_itemName.text = test;
}
#end
There is this call named "dequeueReusableCell...". Table view cells are reused. If 8 cells fit on the screen, and you scroll the view up, your ninth row will reuse the cell that was used for the first row. That's why you have to set up your cell in cellForRowAtIndexPath, which apparently you refuse to do.
Cells are used just for display. They are not used for storing data. You should have a data model, accessed by everyone. cellForRowAtIndexPath reads from that data model. And then if something happens (for example by tapping on a button in a cell) that changes the data model, then you change the data model, and the data model should tell all the interested parties that the model has changed.
Your cell in one view and a UILabel elsewhere should definitely not be connected at all. Any changes should propagate through your data model.
You're not using myItem anywhere in cellForRowAtIndexPath: Your cells seem to be getting their text from some other method, when they should be getting it from celForRowAtIndexPath:

Updating Table Value when it is scrolled

I need to display data in a cell, when the table gets loaded and when it is scrolled. I get data into the table rows the very first time the app is run, but when I scroll the table, the table does not update all values. It shows only the last fetched value from the service.
My Service request format is :
http://vapp.sites.net/......userId=967730&rowsPerPage=10&nextCursorMark=1
Note - when i call service, the NextCursorMark is increased at response, which i m passing to next service call at scroll time.When nextCursorMark is nil i have to stop scrolling the table because there s no more new response data.
Response :
{
MESSAGE: "Success",
STATUS_CODE: 200,
REQUEST: [ ],
RESPONSE: {
OrderList: [..<items>..],
nextCursorMark: "2",
totalOrderCount: 45
}
}
when the table gets loaded for the first time nextCursorMark value is 1 and on each scroll the value will be increased based on totalOrderCount. How can I manage the table view datasource when it is scrolled and the service is called, on each scroll?
My code below:
1 - when loading the Table
#pragma mark - UITableViewDataSource Methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)theTableView
{
return self.arrOrderList.count;
}
- (NSInteger)tableView:(UITableView *)theTableView numberOfRowsInSection:(NSInteger)section
{
OrderList * objOrderList = (OrderList *)[self.arrOrderList objectAtIndex:section];
return objOrderList.orderDetailsList.count;
}
- (UITableViewCell *)tableView:(UITableView *)theTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"OrderDetailsListCell";
OrderDetailsListCell * cell = (OrderDetailsListCell *)[theTableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[OrderDetailsListCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
OrderList * objOrderList = (OrderList *)[self.arrOrderList objectAtIndex:indexPath.section];
OrderDetailsList * objOrderDetailsList = (OrderDetailsList *)[objOrderList.orderDetailsList objectAtIndex:indexPath.row];
[cell loadDataWithOrderDetailList:objOrderDetailsList];
return cell;
}
- (void) ServiceCallForOrderHistoryWithCurrentPageMark:(NSString *)page
{
if(![[VCSpinnerView sharedInstance] isSpinnerAddedToTheView:self.view])
[[VCSpinnerView sharedInstance] showInView:self.view];
__block __weak VCMyOrderViewController* blockMyOrderListView = self;
[[VCModelManager sharedInstance] getMyOrderDetail:[[VCConfiguration sharedConfig] getLoggedInUserId] andPageNumber:page completionBlock:^(id result, NSError *error)
{
if (!error)
{
blockMyOrderListView.orderHistoryDataObject = (OrderHistoryBaseClass *)result;
dispatch_async(dispatch_get_main_queue(), ^{
[blockMyOrderListView setDataSourceMyOrders:blockMyOrderListView.orderHistoryDataObject.rESPONSE];});
}
else
{
NSLog(#"\nError->%#",[error debugDescription]);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[VCSpinnerView sharedInstance] removeSpinnerView];
});
}];
}
- (void)setDataSourceMyOrders:(OrderRESPONSE *)response
{
self.orderHistoryDataObject.rESPONSE = response;
[self setPageMark:self.orderHistoryDataObject.rESPONSE.nextCursorMark];
[self setPageCount:[self.orderHistoryDataObject.rESPONSE.totalOrderCount integerValue]];
self.arrOrderList = [NSMutableArray arrayWithArray:self.orderHistoryDataObject.rESPONSE.orderList];
TrackOrderView *trackOrderView = [[TrackOrderView alloc] init];
[trackOrderView setDelegate:self];
[self.tblMyOrder setTableHeaderView:trackOrderView];
[self.tblMyOrder reloadData];
}
2 - when scrolling the Table
- (void) scrollViewDidScroll:(UIScrollView *)scrollView
{
if (self.arrOrderList.count < self.pageCount)
{
[self ServiceCallForOrderHistoryWithCurrentPageMark:self.pageMark];
}
}
You need to:
Have a mutable array to store the full list
Have variable to store the next URL (or next cursor mark)
In viewDidLoad, set the mutable array to an empty array, and the URL to the initial URL
Still in viewDidLoad, call your method to load more data
This method should append the data received to your mutable array, update the URL with the next URL to call (or nil if there's no more data)
The method should call beginUpdates, then insertRowsAtIndexPaths:withRowAnimation: and then endUpdates
In scrollViewDidScroll, check the scroll offset against the size of the table and the visible size, and if you're near the end, call the method to load more data.
Of course, the method that loads additional data should exit right away when the URL to load is nil

IOS Searchbar returns wrong data

I am going crazy trying to get my IOS Searchbar working for the Iphone. I access data from a remote server and populate a content file. I then do a filter which creates a filtered content file. I then do a [self.tableView reloadData()]. It works fine the first time around. Then I change my scope and do another fetch of data from my server and filter it and do another reload. However, the second time the table shows the first 9 items from the previous display rather than the new 9 items from the filtered file. I console display the file count in the filtered file which in this case is 9 in the tableView numberOfRowsInSection: I also display each item going through the cellForRowAtIndexPath. In the cellForRowAtIndexPath I am displaying the correct 9 unfiltered items but they do not show up on the table! The table shows the first 9 items from the old display instead.
My question is doesn't the new data display on the table instead of the old data even though the count is correct? Why am I displaying the correct items on the console but yet the display shows items from the old display. WHat do I need to do to make the new data appear? I know this is pretty hard to comprehend but I am listing some of my code below in hopes that someone can give me a clue on why the table view is not being updated with the latest data.
// This is where I get data back from the server.
self.listContent = [[NSArray alloc] init];
if(_scopeIndex == 0)
self.listContent = [jsonObject objectForKey:#"burials"];
else
if(_scopeIndex == 1)
self.listContent = [jsonObject objectForKey:#"obits"];
else
self.listContent = [jsonObject objectForKey:#"photos"];
if(self.listContent > 0)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self filterContentForSearchText:searchString scope:
[[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:[self.searchDisplayController.searchBar selectedScopeButtonIndex]]];
[self.tableView reloadData];
});
}
Below is where the data is filtered. In this case the unfiltered and filtered file are the same.
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope{
/*
Update the filtered array based on the search text and scope.
*/
[self.filteredListContent removeAllObjects];// First clear the filtered array.
/*
Search the listContent for names that match and add to the filtered array.
*/
for (int i = 0; i < [self.listContent count]; i++)
{
NSComparisonResult result = [self.listContent[i][#"LastName"] compare:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) range:NSMakeRange(0, [searchText length])];
if (result == NSOrderedSame)
{
[self.filteredListContent addObject:self.listContent[i]];
}
// }
}
}
This is where I get the table count of filtered items.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if (tableView == self.searchDisplayController.searchResultsTableView)
{
NSLog(#"Filtered Name count = %i", [self.listContent count]);
return [self.filteredListContent count];
}
else
{
NSLog(#"Name count = %i", [self.listContent count]);
return [self.listContent count];
}
}
ANd this is where I update the cells in my table:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *kCellID = #"cellID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellID];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellID];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
if(indexPath.row > [self.filteredListContent count] - 1)
return cell;
NSDictionary *burial = [self.filteredListContent objectAtIndex:indexPath.row];
NSString *lastname = burial[#"LastName"];
NSString *firstname = burial[#"FirstName"];
NSString *burialname = [NSString stringWithFormat: #"%#, %#", lastname, firstname];
cell.textLabel.text = burialname;
NSLog(#"Cell name= %# index path=%i", cell.textLabel.text, indexPath.row);
return cell;
}
I changed my logic to go to the server one time to get my content and this time included scope indicators in my content table. This enables me to process scope filters without having to go back to the server for data for a specific scope. Doing this resulted in proper view tables being displayed when changing scope. I would not recommend going to the server ascynchronously whenever the scope changes on the search as it really screws up the view table.
Some things to try:
Maybe you're reloading your tableView too soon, or
Your cellForRowAtIndexPath needs to distinguish between the table views (just as your numberOfRowsInSection does), or
Maybe you don't have all of your delegates set up correctly. The searchBar is used with a UISearchDisplayController, which has 2 delegates: searchResultsDataSource and searchResultsDelegate. Make sure those are set to self (or whatever class handles these).
How are you using searchbar? Logic should be that you have tableview with all data and the you use search bar to filter out matching results and add them to search data array and while searchbar is active you display search data array.
Is
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
used somewhere?
Added following
If you have this function set up you can update search array and reload it using something like
if (self.searchDisplayController.searchResultsTableView != nil)
{
//Updates searchData array
[self searchDisplayController:self.searchDisplayController shouldReloadTableForSearchString:searchBar1.text];
//Updates display
[self.searchDisplayController.searchResultsTableView reloadData];
}
else
{
[_channelsTableView reloadData];
}
It is calling searchDisplayController with current search bar text. When new tableview data comes from server you can activate searchDisplayController to refresh search results array and then it is possible to refresh the display.

Inside cellForRowAtIndexPath, saving cell to #property, always last cell in tableview

Using the following delegate method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
PLOTCheckinTableViewCell *cell = (PLOTCheckinTableViewCell *)[self.checkinsTableView dequeueReusableCellWithIdentifier:CheckinCellIdentifier forIndexPath:indexPath];
[cell setSwipeGestureWithView:crossView color:redColor mode:MCSwipeTableViewCellModeSwitch state:MCSwipeTableViewCellState2 completionBlock:^(MCSwipeTableViewCell *cell, MCSwipeTableViewCellState state, MCSwipeTableViewCellMode mode) {
self.indexPathToDelete = [tableView indexPathForCell:cell];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Delete?"
message:#"Are you sure your want to remove this checkin?"
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
[alertView show];
}];
}
Then inside the UIAlertView delegate method:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
// No
if (buttonIndex == 0) {
}
// Yes
else {
PLOTCheckinTableViewCell *cell = (PLOTCheckinTableViewCell *)[self.checkinsTableView cellForRowAtIndexPath:self.indexPathToDelete];
[self.checkins removeObjectAtIndex:self.indexPathToDelete.row];
[self.checkinsTableView deleteRowsAtIndexPaths:#[self.indexPathToDelete] withRowAnimation:UITableViewRowAnimationFade];
self.indexPathToDelete = nil;
}
}
However, whenever I hit "Ok" in the alert view, the cell that's deleted is always the last one in the tableview, ie. not the cell the user actually swiped.
Is it something to do with the dequeuing?
Yes. You should not be referencing to your cell. Instead, try to reference to the actual object that caused the cell to exist (i.e. if this is a list of messages, the message that is displayed within that cell).
The cell object is being reused and the UIAlertView wont know about that. Even if you have 1000 items in your list, you are not going to have more than 20 cells. They are always going to be reused thorough your table view scrolling.
You should be looking into removing an element from your data source array instead of removing the cell itself. After removing the element, you can always reload your table view to visually reflect the elemnt removed state.

Issues with TableView and use of Audio AVplayer

No other questions I have found have helped me fix my issue, I worry however this may be due to my own incompetence.
I have created a table... this table displays a list of audio track options for the user. The idea behind my application is that the user selects the tracks they want from the list, which then receive checkmarks. The user then presses a 'Play' button and these tracks are streamed/layered together and play as a form of 'soundscape'.
I am having two issues at the present time.
Number 1:
When the first option in the table is checkmarked...
so is the 9th option further down the list...
When the second option is selected...
the the 10th option is also selected etc...
This is not ideal as it is selecting options the user does not intend. I have seen similar posts but my attempts to fix this based on those has not worked.
Issue Number 2:
Implementing the audio from here has proved extremely difficult... I've been trying to use the AVplayer? Which I still don't know if it is the correct option... any help with this also will be greatly appreciated.
I have .plists for the text in the table, the thumbnail names, and the track names, the .mp3 files and thumbnails are correctly placed in my project. The audio however I cannot get to work.
Here is the code of my .m file.
#import "ASMRTableViewController.h"
#interface ASMRTableViewController ()
#end
#implementation ASMRTableViewController
{
NSArray *names;
NSArray *thumbnails;
NSArray *tracks;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Find out the path of the property list
NSString *path = [[NSBundle mainBundle] pathForResource:#"ExternalData" ofType:#"plist"];
// Load the file content and read the data into arrays
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
names = [dict objectForKey:#"Name"];
thumbnails = [dict objectForKey:#"Thumbnail"];
tracks = [dict objectForKey:#"Track"];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [names count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ASMRTableIdentifier = #"ASMRTableItem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ASMRTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ASMRTableIdentifier];
}
cell.textLabel.text = [names objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageNamed:[thumbnails objectAtIndex:indexPath.row]];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//Checkmark
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
//Selecting an option
if (cell.accessoryType == UITableViewCellAccessoryNone)
{
//Initialise Alert
UIAlertView *messageAlert = [[UIAlertView alloc]
initWithTitle:#"Option Selected"
message:[names objectAtIndex:indexPath.row]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
// Display Alert Message
[messageAlert show];
//Add Tick
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
//Deselecting an option
else
{
//Initialise Alert
UIAlertView *messageAlert = [[UIAlertView alloc]
initWithTitle:#"Option Deselected"
message:[names objectAtIndex:indexPath.row]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
// Display Alert Message
[messageAlert show];
//Remove Tick
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
#end
I will be happy to provide any more information I can, unfortunately my knowledge is extremely limited.
You're likely seeing duplicate checkmarks because the tableview is re-using cells. Data in the re-used cell isn't automatically cleared for the next cell that uses it, so you have to make sure to set all of the UI elements for the cell in cellForRowAtIndexPath. In your case, all you have to do is create an array to remember the "checked"/"unchecked" state for each track and then set the accessory type for the cell in cellForRowAtIndexPath.
As for the audio, try using the AVAudioPlayer. There's a bunch of really nice basic tutorials that guide you through using it. If you're still having trouble, maybe post some of your audio code.

Resources