Can I use a UIRefreshControl in a UIScrollView?

IosIphoneUiscrollviewxamarin.iosUirefreshcontrol

Ios Problem Overview


I have about 5 UIScrollView's already in my app which all load multiple .xib files. We now want to use a UIRefreshControl. They are built to be used with UITableViewControllers (per UIRefreshControl class reference). I do not want to re-do how all 5 UIScrollView work. I have already tried to use the UIRefreshControl in my UIScrollView's, and it works as expected except for a few things.

  1. Just after the refresh image turns into the loader, the UIScrollView jumps down about 10 pixels, which only does not happen when I am very careful to drag the UIScrollview down very slowly.

  2. When I scroll down and initiate the reload, then let go of the UIScrollView, the UIScrollView stays where I let it go. After it is finished reloading, the UIScrollView jumps up to the top with no animation.

Here is my code:

-(void)viewDidLoad
{
      UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
      [refreshControl addTarget:self action:@selector(handleRefresh:) forControlEvents:UIControlEventValueChanged];
      [myScrollView addSubview:refreshControl];
}

-(void)handleRefresh:(UIRefreshControl *)refresh {
      // Reload my data
      [refresh endRefreshing];
}

Is there any way I can save a bunch of time and use a UIRefreshControl in a UIScrollView?

Thank You!!!

Ios Solutions


Solution 1 - Ios

I got a UIRefreshControl to work with a UIScrollView:

- (void)viewDidLoad
{
    UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
    scrollView.userInteractionEnabled = TRUE;
    scrollView.scrollEnabled = TRUE;
    scrollView.backgroundColor = [UIColor whiteColor];
    scrollView.contentSize = CGSizeMake(500, 1000);
    
    UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
    [refreshControl addTarget:self action:@selector(testRefresh:) forControlEvents:UIControlEventValueChanged];
    [scrollView addSubview:refreshControl];
    
    [self.view addSubview:scrollView];
}

- (void)testRefresh:(UIRefreshControl *)refreshControl
{    
    refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:@"Refreshing data..."];

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        [NSThread sleepForTimeInterval:3];//for 3 seconds, prevent scrollview from bouncing back down (which would cover up the refresh view immediately and stop the user from even seeing the refresh text / animation)

        dispatch_async(dispatch_get_main_queue(), ^{
            NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
            [formatter setDateFormat:@"MMM d, h:mm a"];
            NSString *lastUpdate = [NSString stringWithFormat:@"Last updated on %@", [formatter stringFromDate:[NSDate date]]];
        
            refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:lastUpdate];

            [refreshControl endRefreshing];
        
            NSLog(@"refresh end");
        });
    });
}

Need to do the data update on a separate thread or it will lock up the main thread (which the UI uses to update the UI). So while the main thread is busy updating the data, the UI is also locked up or frozen and you never see the smooth animations or spinner.

EDIT: ok, I'm doing the same thing as OP and i've now added some text to it (ie, "Pull to Refresh") and it does need to get back onto the main thread to update that text.

Updated answer.

Solution 2 - Ios

Adding to above answers, in some situations you can't set the contentSize (using auto layout perhaps?) or the contentSize's height is less than or equal the height of the UIScrollView. In these cases, the UIRefreshControl won't work because the UIScrollView won't bounce.

To fix this set the property alwaysBounceVertical to TRUE.

Solution 3 - Ios

Since iOS 10 UIScrollView already has a refreshControl property. This refreshControl will appear when you create a UIRefereshControl and assign it to this property.

There's no need to add UIRefereshControl as a subview anymore.

func configureRefreshControl () {
   // Add the refresh control to your UIScrollView object.
   myScrollingView.refreshControl = UIRefreshControl()
   myScrollingView.refreshControl?.addTarget(self, action:
                                      #selector(handleRefreshControl),
                                      for: .valueChanged)
}
    
@objc func handleRefreshControl() {
   // Update your content…

   // Dismiss the refresh control.
   DispatchQueue.main.async {
      self.myScrollingView.refreshControl?.endRefreshing()
   }
}

> A UIRefreshControl object is a standard control that you attach to any UIScrollView object

Code and quote from https://developer.apple.com/documentation/uikit/uirefreshcontrol

Solution 4 - Ios

If and when you are fortunate enough to be supporting iOS 10+, you can now simply set the refreshControl of the UIScrollView. This works the same way as the previously existing refreshControl on UITableView.

Solution 5 - Ios

Here is how you do this in C# / Monotouch. I cant find any samples for C# anywhere, so here it is.. Thanks Log139!

public override void ViewDidLoad ()
{
    //Create a scrollview object
    UIScrollView MainScrollView = new UIScrollView(new RectangleF (0, 0, 500, 600)); 

    //set the content size bigger so that it will bounce
    MainScrollView.ContentSize = new SizeF(500,650);
    
    // initialise and set the refresh class variable 
    refresh = new UIRefreshControl();
    refresh.AddTarget(RefreshEventHandler,UIControlEvent.ValueChanged);
    MainScrollView.AddSubview (refresh);
}

private void RefreshEventHandler (object obj, EventArgs args)
{
	System.Threading.ThreadPool.QueueUserWorkItem ((callback) => {	
  	    InvokeOnMainThread (delegate() {
		System.Threading.Thread.Sleep (3000);			  
                refresh.EndRefreshing ();
		});
	});
}

Solution 6 - Ios

For the Jumping issue, Tim Norman's answer solves it.

Here is the swift version if you are using swift2:

import UIKit

class NoJumpRefreshScrollView: UIScrollView {

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
    // Drawing code
}
*/
override var contentInset:UIEdgeInsets {
    willSet {
        if self.tracking {
            let diff = newValue.top - self.contentInset.top;
            var translation = self.panGestureRecognizer.translationInView(self)
            translation.y -= diff * 3.0 / 2.0
            self.panGestureRecognizer.setTranslation(translation, inView: self)
            }
        }
    }
}

Solution 7 - Ios

How to do it in Swift 3:

override func viewDidLoad() {
    super.viewDidLoad()

    let scroll = UIScrollView()
    scroll.isScrollEnabled = true
    view.addSubview(scroll)

    let refreshControl = UIRefreshControl()
    refreshControl.addTarget(self, action: #selector(pullToRefresh(_:)), for: .valueChanged)
    scroll.addSubview(refreshControl)
}

func pullToRefresh(_ refreshControl: UIRefreshControl) {
    // Update your conntent here
    
    refreshControl.endRefreshing()
}

Solution 8 - Ios

I made UIRefreshControl work properly inside UIScrollView. I inherited UIScrollView, blocked changing of contentInset and overrided contentOffset setter:

class ScrollViewForRefreshControl : UIScrollView {
    override var contentOffset : CGPoint {
        get {return super.contentOffset }
        set {
            if newValue.y < -_contentInset.top || _contentInset.top == 0 {
                super.contentOffset = newValue
            }
        }
    }
    private var _contentInset = UIEdgeInsetsZero
    override var contentInset : UIEdgeInsets {
        get { return _contentInset}
        set {
            _contentInset = newValue
            if newValue.top == 0 && contentOffset.y < 0 {
                self.setContentOffset(CGPointZero, animated: true)
            }
        }
    }
}

Solution 9 - Ios

For the Jumping issue, override contentInset only solves it before iOS 9. I just tried a way to avoid jump issue:

let scrollView = UIScrollView()
let refresh = UIRefreshControl()
//        scrollView.refreshControl = UIRefreshControl() this will cause the jump issue
refresh.addTarget(self, action: #selector(handleRefreshControl), for: .valueChanged)
scrollView.alwaysBounceVertical = true
//just add refreshControl and send it to back will avoid jump issue
scrollView.addSubview(refresh)
scrollView.sendSubviewToBack(refresh)

works on iOS 9 10 11 and so on,and I hope they(Apple) just fix the issue.

Solution 10 - Ios

Starting with iOS 10 a UIScrollView has a refreshControl property that you can set to a UIRefreshControl. As always you do not need to manage the frame of the control. Just configure the control, set the target-action for the valueChanged event and assign to the property.

Regardless of whether you are using a plain scroll view, table view or collection view the steps to create a refresh control are the same.

if #available(iOS 10.0, *) {
  let refreshControl = UIRefreshControl()
  refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
  refreshControl.addTarget(self,
                           action: #selector(refreshOptions(sender:)),
                           for: .valueChanged)
  scrollView.refreshControl = refreshControl
}

@objc private func refreshOptions(sender: UIRefreshControl) {
  // Perform actions to refresh the content
  // ...
  // and then dismiss the control
  sender.endRefreshing()
}

Solution 11 - Ios

You can simply create an instance of the refresh control and add it at the top of the scroll view. then, in the delegate methods you adjust its behavior to your requirements.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSirRupertIIIView Question on Stackoverflow
Solution 1 - IosPadin215View Answer on Stackoverflow
Solution 2 - IosDiego FrataView Answer on Stackoverflow
Solution 3 - IosMacMarkView Answer on Stackoverflow
Solution 4 - IosBen PackardView Answer on Stackoverflow
Solution 5 - IosJohn GavilanView Answer on Stackoverflow
Solution 6 - IosSurelyView Answer on Stackoverflow
Solution 7 - IosCamilo OrtegónView Answer on Stackoverflow
Solution 8 - IosAnton ZherdevView Answer on Stackoverflow
Solution 9 - IosDanteView Answer on Stackoverflow
Solution 10 - IossudaynView Answer on Stackoverflow
Solution 11 - IosGianluca TranchedoneView Answer on Stackoverflow