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
Related
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];
});
}
What is the most appropriate method for reloading my view controller?
To expand upon the scenario, I have a UITableView having populated cells within a View Controller.
I create new cells by transitioning to a new "Create Cell" view controller, however when I call the dismissViewControllerAnimated function, the new cell does not appear.
I cannot use a segue as the initial view controller is a component of a tab bar view controller, so the tab bar disappears if I segue from the "Create Cell" view controller.
Then how can I reload the view, upon a successful dismissal of the "Create Cell" view controller?
Thanks in advance.
Code, in question, that needs refreshing (Implemented the non-working notification method):
#interface MyListings ()
#property (weak, nonatomic) IBOutlet UIButton *createListingButton;
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#property (strong, nonatomic) ListingModel *listItem;
#property (strong, nonatomic) UserModel *usr;
#end
#implementation MyListings
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(newCellCreated:)
name:#"newCellCreated"
object:nil];
self.listItem = [[ListingModel alloc] init];
self.usr = [[UserModel alloc]init];
NSDate *currentDateTime = [NSDate date];
FIRUser *user = [FIRAuth auth].currentUser;
if ([self.usr getDataForUser:user.email])
{
if ([self.listItem getDataForUser:[NSString stringWithFormat:#"%d", self.usr.user_id]])
{
NSLog(#"Got listings");
};
}
titles = self.listItem.title;
currentBids = self.listItem.starting_bid;
stillAvailables = self.listItem.timer;
listingIDs = self.listItem.listing_id;
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateFormat:#"YYYY/MM/dd"];
for(int idx = 0; idx < [self.listItem.listing_id count]; idx++)
{
NSDate *d1 = [dateFormatter dateFromString:self.listItem.timer[idx]];
if([d1 compare:currentDateTime] == NSOrderedDescending)
{
stillAvailables[idx] = #"Expired";
}
else
{
stillAvailables[idx] = #"Available";
}
}
_createListingButton.layer.cornerRadius = 8;
_createListingButton.layer.borderWidth = 1.5f;
_createListingButton.layer.borderColor = [UIColor whiteColor].CGColor;
[_createListingButton addTarget:self action:#selector(createListingButtonHighlightBorder) forControlEvents:UIControlEventTouchDown];
[_createListingButton addTarget:self action:#selector(createListingButtonUnhighlightBorder) forControlEvents:UIControlEventTouchUpInside];
[_createListingButton addTarget:self action:#selector(createListingButtonUnhighlightBorder) forControlEvents:UIControlEventTouchDragExit];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleLightContent;
}
- (void) newCellCreated:(NSNotification *) notification
{
if ([[notification name] isEqualToString:#"newCellCreated"])
[self.tableView reloadData];
}
- (void)createListingButtonHighlightBorder
{
_createListingButton.layer.borderColor = [UIColor colorWithRed:0.61 green:0.00 blue:0.02 alpha:1.0].CGColor;
}
- (void)createListingButtonUnhighlightBorder
{
_createListingButton.layer.borderColor = [UIColor whiteColor].CGColor;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return titles.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyListingsTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyListingsTableViewCell"];
[cell updateCellWithTitle:[titles objectAtIndex:indexPath.row] currentBid:[currentBids objectAtIndex:indexPath.row] stillAvailable:[stillAvailables objectAtIndex:indexPath.row] listingID:[listingIDs objectAtIndex:indexPath.row]];
cell.backgroundColor = [UIColor clearColor];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self performSegueWithIdentifier:#"editListing" sender:self];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"editListing"])
{
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
EditListing *destController = segue.destinationViewController;
destController.listId = self.listItem.listing_id[indexPath.row];
}
}
You can have notification or delegate methods.
As you didn't post any piece of code, replace the names as your convenience.
I'll give you a short example using notification.
In your TableViewController:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "newCellCreated"), object: nil, queue: nil) { (notification) in
self.tableView.reloadData()
}
}
In your CreateCellViewController:
func closeView() {
self.dismiss(animated: true) {
NotificationCenter.default.post(NSNotification.Name(rawValue: "newCellCreated"))
}
}
EDIT - Objective C version
In your TableViewController:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(newCellCreated:)
name:#"newCellCreated"
object:nil];
}
- (void) newCellCreated:(NSNotification *) notification
{
if ([[notification name] isEqualToString:#"newCellCreated"])
[self.tableView reloadData];
}
In your CreateCellViewController:
-(void) closeView {
[self dismissViewControllerAnimated:NO completion:^{
[[NSNotificationCenter defaultCenter]
postNotificationName:#"newCellCreated"
object:self];
}];
}
[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;
}
}
well I have my RSS application running perfect, and now i'm trying to add some indicator with MBProgressHUD.
I implement this code in the View Controller under ViewDidLoad
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
HUD.labelText = #"Loading";
[HUD show:YES];
so far so good. the indicator is working but of course never disappearing.
I am a newbie, I tried to add to my [HUD hide:yes] in certain parts of the implementation file but it's not working .
how can I hide the indicator when the data finish to load ?
here is my implementation file.
#implementation ListadoArticulosViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// HUD setting
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
HUD.labelText = #"Loading";
[HUD show:YES];
NSURL *feedURL = [NSURL URLWithString:#"http://girlsonlyapp.wordpress.com/feed/"];
feedParser = [[MWFeedParser alloc] initWithFeedURL:feedURL];
feedParser.delegate = self;
feedParser.feedParseType = ParseTypeFull;
feedParser.connectionType = ConnectionTypeAsynchronously;
listadoArticulos = [[NSMutableArray alloc] init];
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(cargaArticulos) forControlEvents:UIControlEventValueChanged];
self.refreshControl = refreshControl;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self cargaArticulos];
}
- (void)cargaArticulos {
[feedParser parse];
}
#pragma mark MWFeedParserDelegate
- (void)feedParserDidStart:(MWFeedParser *)parser {
NSLog(#"Comienza el parseo");
// We emptied the list of items to avoid accumulating
// in subsequent calls
[listadoArticulos removeAllObjects];
// We put up the refresh Control
[self.refreshControl beginRefreshing];
}
- (void)feedParser:(MWFeedParser *)parser didParseFeedInfo:(MWFeedInfo *)info {
// Once we have recovered the items we
// The name of the blog as the view controller title
self.title = #"titles";
}
- (void)feedParser:(MWFeedParser *)parser didParseFeedItem:(MWFeedItem *)item {
// Add the item to the array downloaded
[listadoArticulos addObject:item];
}
- (void)feedParserDidFinish:(MWFeedParser *)parser {
// Como ya ha finalizado el parse, denemos el parser
[feedParser stopParsing];
// Detenemos el refresh control
[self.refreshControl endRefreshing];
// Refrescamos el table view
[self.tableView reloadData];
// trying to do something
}
- (void)feedParser:(MWFeedParser *)parser didFailWithError:(NSError *)error {
NSLog(#"Ha ocurrido un error al tratar de recuperar los artÃculos.");
// En caso de que este funcionando el refresh control y
// se produzca un error, lo detenemos.
if ([self.refreshControl isRefreshing]) {
// Detenemos el refresh control
[self.refreshControl endRefreshing];
}
}
#pragma mark UITableViewDelegate / UITableViewDataSource
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ArticuloCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ArticuloCell"];
MWFeedItem *item = [listadoArticulos objectAtIndex:indexPath.row];
cell.titulo.text = item.title;
// Guy Cohen - this is the second label that we can customize , orignally it was link, but it changed
// it to summary
cell.descripcion.text = item.summary;
return cell;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return listadoArticulos.count;
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
ArticuloCell *celda = (ArticuloCell *)sender;
MWFeedItem *item = [listadoArticulos objectAtIndex:[self.tableView indexPathForCell:celda].row];
DetalleViewController *detalleVC = (DetalleViewController *)segue.destinationViewController;
[detalleVC setItem:item];
}
#end
May I recommend using SVProgressHUD instead?
https://github.com/samvermette/SVProgressHUD
You just have to call [SVProgressHUD show] and [SVProgressHUD dismiss]
to show:
[[MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES] setLabelText:#"Loading"];
to hide:
[MBProgressHUD hideAllHUDsForView:self.navigationController.view animated:YES];
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];