Obj-C - Add activity indicator to pull down to refresh? - ios

I've added a 'pull down to refresh' function to my TableView inside a ViewController. The data refresh works perfectly, but I'm wondering how I can add the classic activity indicator into the top when the refresh is executed?
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
[_refreshControl addTarget:self action:#selector(refreshData) forControlEvents:UIControlEventValueChanged];
//[self.mytable addSubview:refreshControl];
UITableViewController *tableViewController = [[UITableViewController alloc] init];
tableViewController.tableView = self.tableView;
tableViewController.refreshControl = _refreshControl;
}
-(void)refreshData
{
UITableViewController *tableViewController = [[UITableViewController alloc] init];
tableViewController.tableView = self.tableView;
[self.tableView reloadData];
[tableViewController.refreshControl endRefreshing];
}

It looks like the issue is that both viewDidLoad and refreshData are creating new tableViewControllers but they should be updating your existing one.
If your class in ViewController.m is a subclass of UITableViewController you can update your code to use the current view controller.
- (void)viewDidLoad {
[super viewDidLoad];
[_refreshControl addTarget:self action:#selector(refreshData) forControlEvents:UIControlEventValueChanged];
self.refreshControl = _refreshControl;
}
-(void)refreshData
{
[self.tableView reloadData];
[self.refreshControl endRefreshing];
}
Another issue is that refreshData immediately calls endRefreshing so the refresh Controller wont have any time to be visible before it ends. Normally you'd have an asynchronous task running, like a network request, then call endRefreshing once that completes. To fake it you can delay calling endRefreshing with the following.
-(void)refreshData
{
// Wait 1 sec before ending refreshing for testing
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self.tableView reloadData];
[self.refreshControl endRefreshing];
});
}

This is what ended up working!
ViewController.h
#property (nonatomic, strong) UIRefreshControl *refreshControl;
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.refreshControl = [[UIRefreshControl alloc] init];
self.refreshControl.attributedTitle = [[NSAttributedString alloc]initWithString:#"Pull To Refresh"];
[self.refreshControl addTarget:self action:#selector(refreshData) forControlEvents:UIControlEventValueChanged];
[self.tableView addSubview:self.refreshControl];
}
-(void)refreshData
{
// Wait 1 sec before ending refreshing for testing
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self.tableView reloadData];
[self.refreshControl endRefreshing];
});
}

Related

Table pull to refresh not working

when i refresh the table then its is no refresh and not show the circle on the screen .
This is my code:-
refreshControl = [[UIRefreshControl alloc]init];
[self.tableHolidays addSubview:refreshControl];
[refreshControl addTarget:self action:#selector(refreshTable) forControlEvents:UIControlEventValueChanged];
- (void)refreshTable {
[refreshControl endRefreshing];
[self.tableHolidays reloadData];
}
Try interchanging the sequence
- (void)refreshTable {
[self.tableHolidays reloadData];
[refreshControl endRefreshing];
}
- (void)viewDidLoad
{
[super viewDidLoad];
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(refresh:) forControlEvents:UIControlEventValueChanged];
[self.tableView addSubview:refreshControl];
}
- (void)refresh:(UIRefreshControl *)refreshControl
{
// Do your stuff here, when done:
[refreshControl endRefreshing];
}
First complete all operations on Table while refreshing & then end refreshing.
- (void)refreshTable {
[self.tableHolidays reloadData];
[refreshControl endRefreshing];
}

UITableView has extra space on top during "pull down to refresh"

I have a problem with my tableview.
It has a space on top like this:
When I open the TasksTableViewController, the problem doesn't show. But when I open another viewcontroller from TaskTableVC like this:
FilterTasksTableViewController * fttvc = [self.storyboard instantiateViewControllerWithIdentifier:#"FilterTasksTableViewController"];
fttvc.delegate = self;
UINavigationController * navVC = [self.storyboard instantiateViewControllerWithIdentifier:#"PopoverNavigationController"];
[navVC setViewControllers:#[fttvc]];
[self presentViewController:navVC animated:YES completion:nil];
and go back to TaskTableVC, the problem occurs.
When I "pull down to refesh", it goes back to normal.
in my code for TaskTableVC:
- (void)viewWillAppear:(BOOL)animated {
//other code
[self populate];
}
- (void)viewDidLoad {
dispatch_async(dispatch_get_main_queue(), ^{
self.refreshControl = [[UIRefreshControl alloc] init];
self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:#" "];
[self.refreshControl addTarget:self action:#selector(refresh) forControlEvents:UIControlEventValueChanged];
[self setRefreshControl:self.refreshControl];
});
[self populate];
}
- (void)populate {
TaskHandler *handler = [[TaskHandler alloc] initWithContext:_context];
NSArray *allTasks = [handler getAll];
_tasks = [self filterTasks:allTasks];
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"startDate" ascending:NO];
NSArray *descriptors = [NSArray arrayWithObjects:descriptor, nil];
_tasks = [[NSMutableArray alloc] initWithArray:[_tasks sortedArrayUsingDescriptors:descriptors]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
[self.refreshControl endRefreshing];
});
}
I hope you can help me with this weird problem.
I solved my problem with the code.
I thought there was something wrong with the Refresh control so I moved it out of the dispatch_aysnc(dispatch_get_main_queue() and added [self.tableview reloadData . That fixed my problem. Thanks everyone for answering. :)
In view viewWillAppear set .
if ([self respondsToSelector:#selector(edgesForExtendedLayout)]) {
[self setEdgesForExtendedLayout:UIRectEdgeBottom];
}
hope it help you .
You Need to make Sure that your UITableViewCell has correct Height.
For doing that you can simply implement the UITableViewDelegate method :
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 90.0f; //CHANGE TO YOUR DESIRED HEIGHT.
}
I hope this helps.

self.tableView reloadData not working

[self.tableView reloadData] not updating numberOfRowsInSection when called.
Delegate and datasource set programmatically in viewDidLoad
Also, put in a UIRefreshControl even though I'm using the tableView in a normal UIViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
UIRefreshControl *refreshControl = [[UIRefreshControl alloc]init];
[refreshControl addTarget:self action:#selector(refresh:) forControlEvents:UIControlEventValueChanged];
self.tableView = [[UITableView alloc]initWithFrame:self.view.bounds];
self.tableView.dataSource = self;
self.tableView.delegate = self;
[self.view addSubview:self.tableView];
[self.tableView addSubview:refreshControl];
}
Refresh Control calls location object to get location. Self gets notified when location object has received the location. Notification calls selector getLocation to make API call with location as parameters
- (void)refresh:(UIRefreshControl *)refreshControl {
[self.loadTableArray removeAllObjects];
LPLocationManager *locationObject = [LPLocationManager sharedManager];
[locationObject.locationManager startUpdatingLocation];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(getLocation:) name:#"locationNotification" object:nil];
[refreshControl endRefreshing];
}
GET request parameters sent to API request object and request object calls method on self that loads mutableArray. MutableArray is copied to NSArray and [self.tableView reloadData] is called but numberOfRowsInSection doesn't update.
-(void)getLocation:(NSNotification*)notifications{
self.latitude = notifications.userInfo[kSetLat];
self.longitude = notifications.userInfo[kSetLong];
self.requestObject = [[LPRequestObject alloc]init];
[self.requestObject getLocationMediaWithLat:self.latitude andLong:self.longitude];
}
-(void)updateMutableArray:(NSArray*)array{
self.loadTableArray = [[NSMutableArray alloc]init];
for (NSDictionary *dictionary in array) {
[self.loadTableArray addObject:dictionary];
}
self.loadTableArrayCopy = [self.loadTableArray copy];
[self.tableView reloadData];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [self.loadTableArrayCopy count];
}
The tableView is added to a subview in a custom container view with code to achieve in mainContentVC
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.locationViewController = [[LPLocationViewController alloc]init];
self.locationViewController.view.frame = self.view.bounds;
self.currentViewController = self.locationViewController;
[self addChildViewController:self.currentViewController];
[self.currentViewController didMoveToParentViewController:self];
[self.containerView addSubview:self.currentViewController.view];
self.diaryViewController = [[LPDiaryViewController alloc]init];
self.diaryViewController.view.frame = self.view.bounds;
self.settingsController = [[LPSettingsViewController alloc]init];
self.settingsController.view.frame = self.view.bounds;
if ((self.currentViewController = self.locationViewController)) {
self.locationButton.enabled = NO;
}
}

UIRefreshControl on viewDidLoad

I'm using the following code to create a UIRefreshControl:
- (void) viewDidLoad
{
[super viewDidLoad];
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(doLoad) forControlEvents:UIControlEventValueChanged];
self.refreshControl = refreshControl;
}
- (void) doLoad
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// Instead of sleeping, I do a webrequest here.
[NSThread sleepForTimeInterval: 5];
dispatch_async(dispatch_get_main_queue(), ^{
[tableView reloadData];
[self.refreshControl endRefreshing];
});
});
}
It works great. If I navigate to my view, drag the table, the code runs and the data displays.
However, what I would like to do is have the view in the 'loading' state as soon as it appears (that way the user knows something is going on). I have tried adding the following:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.refreshControl beginRefreshing];
}
But it does not seem to work. When I navigate to the view, it looks like a regular view (refresh control is not visible), plus when I try to pull the refresh control, it never finished loading.
Obviously I'm going about this the wrong way. Any suggestions on how I should handle this?
Try this:
- (void) viewWillAppear: (BOOL) animated
{
[super viewWillAppear: animated];
self.tableView.contentOffset = CGPointMake(0, -self.refreshControl.frame.size.height);
[self.refreshControl beginRefreshing];
// kick off your async refresh!
[self doLoad];
}
Remember to call endRefreshing at some point!
EDIT to add full working sample:
This sample view controller, built and run in iOS6.1 as the root viewcontroller starts with the UIRefreshControl already visible and animating when the app launches.
TSTableViewController.h
#interface TSTableViewController : UITableViewController
#end
TSTableViewController.m
#import "TSTableViewController.h"
#implementation TSTableViewController
{
NSMutableArray* _dataItems;
}
- (void) viewDidLoad
{
[super viewDidLoad];
self.refreshControl = [UIRefreshControl new];
[self.refreshControl addTarget: self
action: #selector( onRefresh: )
forControlEvents: UIControlEventValueChanged];
}
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear: animated];
self.tableView.contentOffset = CGPointMake(0, -self.refreshControl.frame.size.height);
[self.refreshControl beginRefreshing];
[self onRefresh: nil];
}
- (void) onRefresh: (id) sender
{
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
_dataItems = [NSMutableArray new];
for ( int i = 0 ; i < arc4random() % 100 ; i++ )
{
CFUUIDRef uuid = CFUUIDCreate( NULL );
[_dataItems addObject: CFBridgingRelease(CFUUIDCreateString( NULL, uuid)) ];
CFRelease( uuid );
}
[self.refreshControl endRefreshing];
[self.tableView reloadData];
});
}
#pragma mark - Table view data source
- (NSInteger) numberOfSectionsInTableView: (UITableView *) tableView
{
return 1;
}
- (NSInteger) tableView:(UITableView *) tableView numberOfRowsInSection: (NSInteger) section
{
return _dataItems.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault
reuseIdentifier: nil];
cell.textLabel.text = [_dataItems objectAtIndex: indexPath.row];
return cell;
}
#end
Manually modifying the contentOffset is insecure and wrong and can lead to unexpected behavior in some cases. This solution works without touching the contentOffset at all:
func showRefreshControl(show: Bool) {
if show {
refreshControl?.beginRefreshing()
tableView.scrollRectToVisible(CGRectMake(0, 0, 1, 1), animated: true)
} else {
refreshControl?.endRefreshing()
}
}
Another option is fire a UIControlEventValueChanged in your viewDidAppear: to trigger an initial refresh.
- (void) viewDidAppear: (BOOL) animated
{
[super viewDidAppear: animated];
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(doLoad) forControlEvents:UIControlEventValueChanged];
self.refreshControl = refreshControl;
[self.refreshControl beginRefreshing];
}
You never set self.refreshControl

UIRefreshControl EXC_BAD_ACCESS

I'm trying to implement UIRefreshControl in a UIViewController. I can't use UITableViewController because the tableView is just one segment of my viewController.
In most cases this workaround works like charm. But sometimes (random occurrence) the app crashes with EXC_BAD_ACCESS code=1
- (void)viewDidLoad {
[super viewDidLoad];
UIRefreshControl * refCon = [[UIRefreshControl alloc] init];
[refCon addTarget:self selector:#selector(refresh:) forControlEvent:UIControlEventValueChanged];
[tableView addSubView:refCon];
}
- (void)refresh:(UIRefreshControl *)sender {
[NSThred detachNewThreadSelector:#selector(doRefresh:) toTarget:self withObject:sender];
}
- (void)doRefresh:(UIRefreshControl *)sender {
[self checkUpdate];
[self loadData];
[sender endRefreshing];
}
You're invoking -endRefreshing on a background thread. Don't do that.
And also, adding a UIRefreshControl directly as a subview of UITableView is not guaranteed to work. You should be using a UITableViewController.
The first you put a tag at UIRefreshControl
UIRefreshControl * refCon = [[UIRefreshControl alloc] init];
[refCon addTarget:self selector:#selector(refresh:) forControlEvent:UIControlEventValueChanged];
refCon.tag = 101 //for example
[tableView addSubView:refCon];
When the tableView finish of reload then you put:
UIRefreshControl *getRefreshControl = (UIRefreshControl*)[self.tablaPildoras viewWithTag:101];
[getRefreshControl endRefreshing];

Resources