I have an issue with my UIRefreshControl : after the first time it was triggered, it cancels touch on top of my first custom cell (even if it is hidden), like if it was invisible.
If I remove it from superview, then add it again, the issue is still there.
Actually I have a UITableView that is populated with some data. The user can refresh the table view by pulling it and for that I've added an UIRefreshControl to my table view like this in viewDidLoad()
self.refreshControl = [[UIRefreshControl alloc]init];
self.refreshControl.tintColor = [UIColor colorWithRed:0.35 green:0.78 blue:0.98 alpha:1.0];
[_tableView addSubview:self.refreshControl];
I've not added selector to the refreshControl object because I want the tableView to refresh only when the user stopped touching the screen. So for that I've added this ScrollView's delegate method :
- (void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView {
if (self.refreshControl.isRefreshing) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_sync(dispatch_get_main_queue(), ^{
[self refreshTable];
});
});
}
}
And here is my refresh method :
- (void)refreshTable {
//refresh code
[self.refreshControl endRefreshing];
}
I don't want to put my app online with this issue, even if this does not block user to use it !
Thanks in advance for your help !
I've found the solution by going to this post
So effectively in my case I did not have to add a selector, just implement those UIScrollViewDelegate's method :)
Related
I implemented pull to refresh functionality for my controller:
- viewDidLoad {
self.refresh = [[UIRefreshControl alloc] init];
[self.refresh addTarget:self action:#selector(refreshData) forControlEvents:UIControlEventValueChanged];
[self.tableView addSubview:self.refresh];
}
- (void) refreshData {
__weak myController *weakSelf = self;
[[NetworkcallManager instance] getCall:^(NSString *result){
if(weakSelf != nil) {
[weakSelf.refresh endRefreshing];
[weakSelf.tableView reloadData];
}
}];
}
The refresh works as expected, but when I pull down the screen too hard then the entire screen disappears for a moment and then the data gets refreshed.
Is there a way to avoid disappearance of the screen? I am wondering if it is because of reloadData function. In case it calls 'viewWillAppear' then I can understand the problem I am facing. (My viewWillAppear code results in momentarily blank screen).
Edit:
If I pull down without internet connection (turned off the wifi) then the screen doesn't disappear.
I got the issue. In the code I am purging the table and then updating it. As a result, the screen disappears momentarily.
I am using setNeedsDisplay on my GUI, but there update is sometimes not done. I am using UIPageControllView, each page has UIScrollView with UIView inside.
I have the following pipeline:
1) application comes from background - called applicationWillEnterForeground
2) start data download from server
2.1) after data download is finished, trigger selector
3) use dispatch_async with dispatch_get_main_queue() to fill labels, images etc. with new data
3.1) call setNeedsDisplay on view (also tried on scroll view and page controller)
Problem is, that step 3.1 is called, but changes apper only from time to time. If I swap pages, the refresh is done and I can see new data (so download works correctly). But without manual page turn, there is no update.
Any help ?
Edit: code from step 3 and 3.1 (removed _needRefresh variables pointed in comments)
-(void)FillData {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *stateID = [DataManager ConvertStateToStringFromID:_activeCity.actual_weather.state];
if ([_activeCity.actual_weather.is_night boolValue] == YES)
{
self.contentBgImage.image = [UIImage imageNamed:[NSString stringWithFormat:#"bg_%#_noc", [_bgs objectForKey:stateID]]];
if (_isNight == NO)
{
_bgTransparencyInited = NO;
}
_isNight = YES;
}
else
{
self.contentBgImage.image = [UIImage imageNamed:[NSString stringWithFormat:#"bg_%#", [_bgs objectForKey:stateID]]];
if (_isNight == YES)
{
_bgTransparencyInited = NO;
}
_isNight = NO;
}
[self.contentBgImage setNeedsDisplay]; //refresh background image
[self CreateBackgroundTransparency]; //create transparent background if colors changed - only from time to time
self.contentView.parentController = self;
[self.contentView FillData]; //Fill UIView with data - set labels texts to new ones
//_needRefresh is set to YES after application comes from background
[self.contentView setNeedsDisplay]; //This do nothing ?
[_grad display]; //refresh gradient
});
}
And here is selector called after data download (in MainViewController)
-(void)FinishDownload:(NSNotification *)notification
{
dispatch_async(dispatch_get_main_queue(), ^{
[_activeViewController FillData]; //call method shown before
//try call some more refresh - also useless
[self.pageControl setNeedsDisplay];
//[self reloadInputViews];
[self.view setNeedsDisplay];
});
}
In AppDelegate I have this for application comes from background:
-(void)applicationWillEnterForeground:(UIApplication *)application
{
MainViewController *main = (MainViewController *)[(SWRevealViewController *)self.window.rootViewController frontViewController];
[main UpdateData];
}
In MainViewController
-(void)UpdateData
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(FinishForecastDownload:) name:#"FinishDownload" object:nil]; //create selector
[[DataManager SharedManager] DownloadForecastDataWithAfterSelector:#"FinishDownload"]; //trigger download
}
try this:
[self.view performSelectorOnMainThread:#selector(setNeedsLayout) withObject:nil waitUntilDone:NO];
or check this link:
http://blackpixel.com/blog/2013/11/performselectoronmainthread-vs-dispatch-async.html
setNeedsDisplay triggers drawRect: and is used to "redraw the pixels" of the view , not to configure the view or its subviews.
You could override drawRect: and modify your labels, etc. there but that's not what it is made for and neither setNeedsLayout/layoutSubviews is.
You should create your own updateUI method where you use your fresh data to update the UI and not rely on specialized system calls meant for redrawing pixels (setNeedsDisplay) or adjusting subviews' frames (drawRect:).
You should set all your label.text's, imageView.image's, etc in the updateUI method. Also it is a good idea to try to only set those values through this method and not directly from any method.
None of proposed solutions worked. So at the end, I have simply remove currently showed screen from UIPageControllView and add this screen again. Something like changing the page there and back again programatically.
Its a bit slower, but works fine.
I have created a UIRefreshcontrol in my tableviewcontroller as follows in the viewdidload method :
refresh = [UIRefreshControl.alloc init];
refresh.attributedTitle = [[NSAttributedString alloc] initWithString:#"Pull to Refresh"];
[refresh addTarget:self action:#selector(refreshChatsTableView) forControlEvents:UIControlEventValueChanged];
self.refreshControl = refresh;
the problem is that when I pull it down a little long , the table jitters up giving a pretty unpleasant UI experience. Can anyone please help ? This behavior is experienced only in the Landscape mode.
Here is my code :
-UIRefreshControl *refresh;
-(void)viewDidLoad{
[super viewDidLoad];
arr= [[NSArray alloc]initWithObjects:#"A",#"B",#"C",#"D",#"E",#"A",#"B",#"C",#"D",#"E", nil];
//refresh control
refresh = [UIRefreshControl.alloc init];
refresh.attributedTitle = [[NSAttributedString alloc] initWithString:#"Pull to Refresh"];
[refresh addTarget:self action:#selector(refreshChatsTableView) forControlEvents:UIControlEventValueChanged];
self.refreshControl = refresh;
}
-(void)refreshChatsTableView{
[refresh endRefreshing];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return arr.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell =[[UITableViewCell alloc]init];
cell.textLabel.text = [arr objectAtIndex:indexPath.row ];
return cell;
}
So I just came across this same problem, and I came up with a solution. Basically, if you pull your tableView down far enough and hold it, the ValueChanged control event will fire before the user releases the touch. If you refresh your data and reload the tableView at this point, the table will jump up.
To combat this, I ended up refreshing the table independently of the ValueChanged event. Instead, I used that event, along with the scrollView delegate methods, to only refresh the table after the user had let go.
So first, set up your refreshControl in viewDidLoad.
override func viewDidLoad()
{
super.viewDidLoad()
refreshControl = UIRefreshControl()
refreshControl?.addTarget(self, action: "refreshControlChanged", forControlEvents: UIControlEvents.ValueChanged)
}
The ValueChanged method is simple. If we've already stopped dragging, which happens after a quick swipe down, just refresh the table using your own custom method.
internal func refreshControlChanged()
{
if !tableView.dragging
{
refresh()
}
}
Then, you need to implement didEndDragging. If you've pulled down far enough, the refreshControl will be refreshing, so call refresh. Otherwise, you either haven't pulled down far enough, you were dragging on a different part of the table, or you dragged so quickly that the refreshControl ValueChanged event has yet to fire, and it will refresh the table there.
override func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool)
{
if refreshControl?.refreshing == true
{
refresh()
}
}
And that's it. The table won't get reloaded until after you've stopped dragging it, which avoids the jittery jump that you noticed. I know this is a year old, but I hope this helps anyone else coming across this on Google.
try changing
self.refreshControl = refresh;
for
[self.tableView addSubview:refresh];
[self.tableView sendSubviewToBack:refresh];
+++++++++
if u have problem with attributedString layout with first refreshControl triggered - add this after adding refreshControl as subview
dispatch_async(dispatch_get_main_queue(), ^{
[refreshControl beginRefreshing];
[refreshControl endRefreshing];
});
Just add tableView.alwaysBounceVertical = YES after your [refresh endRefreshing] so table will bounce to top after the refresh control ended its animation.
My solution is similar to the accepted solution. The idea is to update your table view only after the view is at the right place. Therefore, we can add a variable to track if we need to update the view when the scroll view is completely bounced back (don't update it while decelerating).
Also, always update your table view before ending the refreshControl.
// The action function of refreshControl
#objc private func refresh() {
DispatchQueue.global(qos: .utility).async {
// Update data
// ......
// Update the table view
DispatchQueue.main.async {
if self.tableView.isDragging == false {
self.tableView.reloadData()
self.myRefreshControl.endRefreshing()
} else {
self.needEndRefreshing = true
}
}
}
}
override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if self.needEndRefreshing {
self.tableView.reloadData()
self.myRefreshControl.endRefreshing()
self.needEndRefreshing = false
}
}
Usually you use UIRefreshControl to trigger an asynchronous reload operation, and endAnimating should be called after a certain period of time has elapsed. It looks here like the moment the refresh control begins animating, you're ending it immediately. This is probably confusing UIRefreshControl.
Try calling endAnimating after a period of time has elapsed, or in another runloop cycle (e.g. call it in dispatch_async). There appear to be other problems with your code (for example, your tableView:cellForRowAtIndexPath: is creating cells incorrectly), so there might be other problems lurking in there.
SWIFT 4:
In ViewdidLoad()
someCollectionView or someTableView
someCollectionView.refreshControl?.addSubview(refreshControl)
someCollectionView.refreshControl?.sendSubview(toBack: refreshControl)
Worked perfectly for me.
#Devin's answer did help but what completely fixed the issue for me was to make my NavigationBar "Translucent". https://i.stack.imgur.com/TKcdH.png
Using non translucent NavigationBar causes the tableview to jump up slightly when UIControlEventValueChanged is fired for some reason.
XCode 10.1
I'm using ECSlidingViewController in my app. I have a View Controller with a UITableView in it, which uses a UIRefreshControl to execute a long task. When I pull down to refresh, the refresh control starts animating normally but when I go to the top view controller (my main menu in this case) and then go back to my view controller, the refreshControl freezes completely until the task is completed.
To make it clearer, these are the steps I follow:
Pull down my tableView to refresh it.
A long task is executed on background, like:
- (void)pulledToRefresh:(UIRefreshControl *)refreshControl
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
long long a = 0;
for (long long i = 0; i < 100000000000; i++) {
a++;
}
dispatch_async(dispatch_get_main_queue(), ^{
[refreshControl endRefreshing];
});
});
}
While the refresh control is animating, I slide (I don't call the resetTopView:animated: method or any of those ones, I just make a pan gesture) my current view controller to the right to show my Main Menu. When this happens, the refresh control suddenly stops animating (it freezes).
I go back to the view controller I was using at first but it remains frozen.
The task completes and the refresh control ends the refresh naturally, removing itself from my tableView with a nice animation.
Any idea why this happens?
I may have found a solution for this.... Changing the "detectPanGestureRecognizer" method to below, seems to be working for me...
- (void)detectPanGestureRecognizer:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
_isInteractive = YES;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.defaultInteractiveTransition updateTopViewHorizontalCenterWithRecognizer:recognizer];
});
_isInteractive = NO;
}
I have added the functionality of UIRefreshControl in my project that uses a UITableView. The app works by fetching entries from a web service to a tableview. Below is the code i have used to add UIRefreshControl:
- (void)viewDidLoad
{
[super viewDidLoad];
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
refreshControl.tintColor = [UIColor grayColor];
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:#"Updating New Entries"];
[refreshControl addTarget:self action:#selector(pullToRefresh) forControlEvents:UIControlEventValueChanged];
self.refreshControl = refreshControl;
[self pullToRefresh];
}
- (void) pullToRefresh
{
counter = 1;
[self fetchEntriesNew:counter]; // My code for updating table view
[self performSelector:#selector(updateTable) withObject:nil afterDelay:2];
}
- (void)updateTable
{
[self.tableView reloadData];
[self.refreshControl endRefreshing];
}
Now if i pull to refresh, it refreshes by adding new entries if there are any and shows me the following view on top of the tableview:
Everything works great except when the app is launched or opened for the very first time, it does not show the view that i have showed in the above image, although it does refreshes the tableview. I want it to show the refresh control view every time it refreshes it. Can anyone point out what i am doing wrong? Thanks!
UPDATE: I have added [self refreshControl beginRefreshing] and the UIRefreshControl's spinner view is now showing but its above the first entry of the tableview. Can anyone point out how to correct it?
This problem had really puzzled me for a while.I found that 4-inch iOS devices don't have this problem, but 3.5-inch devices do.
I tried to find out the differences between the first time that the refreshControl beginRefreshing itself and when I operated a pull gesture.It's the pull operation.
And I checked Apple's document on UIRefreshControl.It says The control does not initiate the refresh operation directly. Instead, it sends the UIControlEventValueChanged event when a refresh operation should occur.
So I thought maybe I could add something to simulate a pull gesture to trigger refreshControl's showing.
[yourScrollView(or tableView) setContentOffset:CGPointMake(0.0f, -60.0f)
animated:YES];
[yourRefreshControl beginRefreshing];
It works!
PS. UIRefreshControl works with UIScrollView, too. [yourScrollView addSubview:yourRefreshControl] just works.
I would move your [self pullToRefresh] call to viewWillAppear instead of viewDidLoad.
There are two things that can be done to add UIRefreshControl in your tableview neither of them is added in your code
1. [self setRefreshControl:tableRefreshControl];
2. [self.m_TableView addSubview:tableRefreshControl];
Either add 1 or 2 if your class is subclass of UIViewController
If your class is subclass of UITableViewController then try to replace
self.refreshControl = refreshControl; with 2 line
before the code:
[refreshControl beginRefresh]
insert the code:
[refreshControl layoutIfNeeded]