I am making an application that is retrieving JSON data and placing it into a UITableView. I am just wondering what the best practice's are for dealing with failure to load data, no internet connection, etc. I have also implemented a pull to refresh on the table, so I want it to reload through that. As of right now I have everything working correctly, but it is my first time doing it so I am just wanting to see if I'm missing something.
Here is how I have it setup:
// Assume there are method's that retrieve the data
- (void)viewDidLoad
{
[self loadData];
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
refreshControl.tintColor = [UIColor darkGrayColor];
[refreshControl addTarget:self action:#selector(reloadTable) forControlEvents:UIControlEventAllEvents];
self.refreshControl = refreshControl;
}
- (void)reloadTable
{
[self loadData];
if (self.jsonCodeData) {
[self.tableView reloadData];
[self.refreshControl endRefreshing];
} else {
[self.refreshControl endRefreshing];
}
}
So basically I load the data when the view load's and create/initialize a UIRefreshControl. The reloadTable method is where I implemented my answer to this question. Is that correct or is there a better way to do it? Also I am wanting there to be a filler "No Data: Pull to Refresh" type image when nothing is loaded. What is the best way to make that happen when I am using a UITableViewController? I just added another prototype cell that I marked to use for needing a refresh.
An issue that I am coming across right now is that I am having to pull to refresh twice if a user has no internet and then happen's to get it. They are having to pull it once to essentially get the data, but the if statement won't pass that time around. The next time they pull it will actually load the table.
The main point of this is just seeing if this is the best way to implement a failure on data retrieval.
Related
I have an app where I have slider where I have used icarousel. Below slider I have list of events. When I click on event, it go to event details.
What I noticed is that my iPhone is restarting if I go to event details from list and click back button from the details screen.
This is happening with iPhone 6 & above devices only.
Click to see the stacktrace
As its big, I provide link.
Just to add, I have also used UIRefreshControl on UITableView. Below is the code I have used.
refreshControl = [[UIRefreshControl alloc]init];
[mainTableView addSubview:refreshControl];
[refreshControl addTarget:self action:#selector(refreshTable) forControlEvents:UIControlEventValueChanged];
-(void) refreshTable {
[indicator startAnimating];
indexCounter.text = #"0";
indexCounter.hidden = YES;
occasionsArray = [[NSMutableArray alloc] init];
actualOccasionsArray = [[NSMutableArray alloc] init];
mainOccasionsArray = [[NSMutableArray alloc] init];
[refreshControl endRefreshing];
[mainTableView reloadData];
[self fetchHomeScreenOccassions]; // this is where I call webservice
}
Congratulations. You have:
Found a crasher bug in the NSURLSession helper daemon
Managed to create an animation on a view or other item that is so huge that you crashed the window server.
I can't tell you exactly how your code managed to do either of those things, but... one thing I notice is that you're nuking the entire table view and then reloading it. That's not necessarily the best way to approach this problem. Instead, you should load the data, and (assuming it comes in a consistent order) iterate through the items in your existing array as you receive items, deleting anything that should have been received already and adding any new items that aren't in the array yet.
I've added a sub view like this in the main view:
BTLPXYPad *XYPad = [[BTLPXYPad alloc] initWithFrame: CGRectMake (30, 10, 280, 460)];
[window addSubview:XYPad];
done all my bits that i need to and then removed it using this in the BTLPXYPad class:
[self removeFromSuperview];
What I need is to perform a task once it has gone. I know that with a UIViewController type class I could use viewDidDissapear but I can't seem to find the same thing for a UIView Type. Can anyone help please?
To know when you a view has actually been removed you could implement didMoveToSuperview and check if the superview is now nil
- (void)didMoveToSuperview;
{
[super didMoveToSuperview];
if (!self.superview) {
NSLog(#"Removed from superview");
}
}
[self removeFromSuperview];
What I need is to perform a task once it has gone.
When you say [self removeFromSuperview], it is gone. There is a delay of just one runloop for it to look gone, but removing the view removes the view.
So the solution is just to proceed to your "task once it is gone":
[self removeFromSuperview];
// do your task
If it seems like the "do your task" code is in the wrong place - it isn't. The fact that you need the view to be notified when it is gone shows that your architecture was wrong to start with. A view shouldn't be performing any "task"; it is View in the Model-View-Controller structure. It just displays stuff. Your view controller is Controller; it is the one to do the task.
Nor should the Controller need to consult the view at this point, because the view should not have been storing any important data to begin with. Data is Model, and should already have been retrieve and stored by the Controller before this moment.
In our iPgone & iPad app we use push segue transitions between different ui contollers, most of them extend UICollectionViewController. In each controller we load data from our internal API. Loading is done viewWillAppear or viewDidLoad.
Now, the thing is, that this API call sometime can take a second or two, or even three... well, lot's of stuff there, let's assume we can't change it. But, we can change the user experience and at least add the "loading" circle indicator. The thing is, what I can't understand by means of correct concept, while transition from A to B, the "load" is done at B, while page A still presented.
So, question is "how do I show indicator on page A, while loading controller for page B?"
Thanks all,
Uri.
Common approach in this case is to load data in destination view controller NOT in main thread. You can show indicator while loading data in background thread and then remove it.
Here is sample of code from my project solving the same problem:
- (void) viewDidLoad {
...
// add indicator
self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
self.spinner.hidesWhenStopped = YES;
self.spinner.center = self.view.center;
[self.view addSubview:self.spinner];
...
// fetch news
[self.spinner startAnimating];
__weak typeof(self) weakSelf = self
[[BitrixApiClient sharedInstance] getLatestNewsWithCompletionBlock:^(NSArray *newsArray, NSUInteger maxPageCount, NSUInteger currentPageNumber, NSError *error) {
if (!error) {
weakSelf.newsArray = newsArray;
weakSelf.currentPageNumber = currentPageNumber;
[weakSelf.newsTableView reloadData];
}
// stop spinning
[weakSelf.spinner stopAnimating];
}];
}
This is my first question so excuse me for being a newbie.
I am working with a CollectionView that shows images downloaded from the internet. The problem appears when I try to do it asynchronously.
#interface myViewController
{
NSArray *ArrayOfSections
}
#end
-(void)viewDidLoad{
[self performSelectorInBackground:#selector(refreshImages) withObject:nil];
}
-(void)refreshImages{
... //Get information from the net
NSArray internetInfo = ...;
[self performSelectorOnMainThread:#selector(refreshCollectionView:) withObject:internetInfo waitUntilDone:NO];
}
-(void)refreshCollectionView:(NSArray tempArray){
ArrayOfSections = tempArray;
}
This code is not working. It shows an empty CollectionView, although I have double checked that the information stored on ArrayOfSections is correct.
Moreover, if I do it synchronously (I change only viewDidLoad).
-(void)viewDidLoad{
[self refreshImage];
}
Everything works fine. I am going bananas. Please help
I think it's because you're not telling the collection view to reload. Your refresh method updates the model but not the view.
If you're fetching the data on a background thread, the main thread can continue it's lifecycle, which involves querying the collection view datasource and delegate methods then updating the view, but it will be doing this too soon in your case, as the model isn't ready. That's why you need to tell it to do that again, when the model is ready, at the end of your data fetch. Since you block the thread when doing it synchronously, it won't reach the collection view methods until the model is ready, which is why it works that way.
Using Parse, after I'm logged in I am presented PFQueryTableViewController that displays a list of tasks and another detail view controller that allows me to edit the task detail and segue back. The issue right now is that the PFQueryTableViewController does not reflect the new changes after I finished editing and popping the task detail view off the stack. However the table view list does get updated when I go back to the login screen(view before the PFQueryTableViewController) and re-enter the table view again. I've tried the following:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.tableView reloadData];
}
and also
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self loadObjects];
[self.tableView reloadData];
}
Yet they don't seem to take effect. My guess is that the data is updated after the view is popped off and the table view appears. I'm just wondering if anyone has any insight on this while I'm investigating. Thanks!
You could try re-querying the queryForTable method in viewDidAppear (this would naturally use an API request on every view appearance however)
- (void)viewDidAppear:(BOOL)animated {
[self queryForTable];
}
This answer assumes that you are using Local Datastore and want to see changes made in it be reflected in a PFQueryTableViewController.
Because the ParseUI classes do not implement any form of caching though the local datastore, changes made in the detail view will not appear in the PFQueryTableViewController until the save operation has completed and the tableView has fetched the new items from Parse.
One solution to your problem would be adding a category to the PFQueryTableViewController that modifies how it fetches data to include what is in the Local Datastore as well.
You should make sure the data is saved before popping your view controller.
Use Parse's save method with completion handler.
[request saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded) {
[self.navigationController popViewControllerAnimated:YES];
}
}];
You can use [self loadObjects] to trigger a refresh for all objects in the PFQueryTableViewController.