UIRefreshcontrol jitters when pulled down and held - ios

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

Related

Pull to refresh: screen disappears

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.

UIRefreshControl cancel touch on top of custom cell - Objective-c iOS

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 :)

UIRefreshControl loading icon still loading

i implemented a UIRefreshControl for UICollectionView so that user can pull to refresh the content in UICollectionView. I am testing on the iPad simulator.
On the first attempt, I'm able to pull and refresh the content. However, I notice that the loading icon is still loading and doesn't stop. On my second attempt with the loading icon still showing, I pulled to refresh but it fails to call my selector(refreshCollectionAction).
Here is what I did:
-(void)viewDidLoad
{
// Do any additional setup after loading the view.
[super viewDidLoad];
// Register collectionView pull down to refresh
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(refreshCollectionAction)
forControlEvents:UIControlEventValueChanged];
[self.collectionView addSubview:refreshControl];
self.collectionView.alwaysBounceVertical = YES;
.....
}
-(void)refreshCollectionAction
{
NSLog(#"refresh collection action");
// Empty product Items Array
[[ProductStore getInstance] emptyProductInStore];
NSInteger numOfProductInStore = [[[ProductStore getInstance] allProductItems] count];
if (numOfProductInStore <= 0) {
// Fetch data from webservice and reload collectionView
[self fetchFeed:#""];
}
}
Am I missing some configurations? fetchFeed will request the data from web services. I have verified that the webservice still works.
[self.refreshControl endRefreshing];
Call this method at the end of any refresh operation (whether it was initiated programmatically or by the user) to return the refresh control to its default state. If the refresh control is at least partially visible, calling this method also hides it. If animations are also enabled, the control is hidden using an animation.
UIRefreshControl Class Reference
#interface ProductSearchViewController ()
#property(nonatomic)UIRefreshControl *refreshControl;
#end
- (void)viewDidLoad
{
// Do any additional setup after loading the view.
[super viewDidLoad];
// Register collectionView pull down to refresh
self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:#selector(refreshCollectionAction)
forControlEvents:UIControlEventValueChanged];
[self.collectionView addSubview:self.refreshControl];
self.collectionView.alwaysBounceVertical = YES;
...
}
-(void)refreshCollectionAction
{
NSLog(#"refresh collection action");
// Empty product Items Array
[[posProductStore getInstance] emptyProductInStore];
NSInteger numOfProductInStore = [[[posProductStore getInstance] allProductItems] count];
if (numOfProductInStore <= 0) {
[self fetchFeed:#""];
}
[self.refreshControl endRefreshing];
}
so basically i declare refreshControl as a class variable. as Neil mentioned, i added [self.refreshControl endRefreshing] at the end of method -(void)refreshCollectionAction.

Pull To Refresh in iOS 7

I'm trying to get the pull to refresh feature working properly on iOS 7 in my Table View. On viewDidLoad, I have:
self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:#selector(refreshInvoked:forState:) forControlEvents:UIControlEventValueChanged];
I then run:
-(void) refreshInvoked:(id)sender forState:(UIControlState)state {
// Refresh table here...
[_allEntries removeAllObjects];
[self.tableView reloadData];
[self refresh];
}
When the request that the refresh method invokes is done, in the didCompleteRequest code, I have:
[self.refreshControl endRefreshing];
On iOS 6, this would mean that as you pull down on the table view, it would show the circular arrow that would get stretched out as you pull, and after pulled far enough, it would refresh. Right now, though, I see no circular arrow, just a UIActivityIndicator. It also sometimes will work and sometimes will not. What am I missing?
To add UIRefreshControl in your UITableView...
1) in ViewDidLoad..
- (void)viewDidLoad
{
[super viewDidLoad];
//to add the UIRefreshControl to UIView
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:#"Please Wait..."]; //to give the attributedTitle
[refreshControl addTarget:self action:#selector(refresh:) forControlEvents:UIControlEventValueChanged];
[tblVideoView addSubview:refreshControl];
}
2) call related method to refresh the UITableView data...
- (void)refresh:(UIRefreshControl *)refreshControl
{
[self refreshTableData]; //call function you want
[refreshControl endRefreshing];
}
OR for Swift
let refreshControl : UIRefreshControl = UIRefreshControl.init()
refreshControl.attributedTitle = NSAttributedString.init(string: "Please Wait...")
refreshControl.addTarget(self, action: #selector(refresh), forControlEvents: UIControlEvents.ValueChanged)
feedTable.addSubview(refreshControl)
func refresh(refreshControl:UIRefreshControl){
self.refreshTableData()//call function you want
refreshControl.endRefreshing()
}
The 'UIActivityIndicator' that you are talking about is the new default appearance of a UIRefreshControl.
You pull down and as the circle completes it is showing how close to triggering a refresh you are.
You can remove/hide the default activity indicator, and add in your own images and animations.
There's also a certain threshold value (distance) that the table must be pulled past before the refresh is invoked.
Here's our tutorial for Custom Pull to Refresh controls (in objective-c and swift): http://www.jackrabbitmobile.com/design/ios-custom-pull-to-refresh-control/
Hope it helps, let me know if I can answer anything else
Update for swift
For TableView
- (void)viewDidLoad
{
let refreshControl = UIRefreshControl()
refreshControl.attributedTitle = NSAttributedString(string: "Please Wait..")
tableView.addSubview(refreshControl)
refreshControl.addTarget(self, action: #selector(refreshTable), forControlEvents: UIControlEventValueChanged)
}
- (void)refreshTable {
//Refresh Data here
//......
//Once all the data is fetched. If you are loading asynchronously add the below code inside the async block
refreshControl.endRefreshing()
[tableView reloadData];
}
For UITableViewController
In UITableViewController there is a default property called refreshControl which by default is nil. If you want just initialise the refreshControl and assign it.
let refreshControl = UIRefreshControl()
refreshControl.attributedTitle = NSAttributedString(string: "Please Wait..")
yourTableViewController.refreshControl = refreshControl

UITableView UIRefreshControl Does Not Show Its View The First Time

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]

Resources