我能在UIScrollView中使用UIRefreshControl吗?

84

我在我的应用程序中已经使用了约5个UIScrollView,它们都加载多个.xib文件。现在我们想要使用UIRefreshControl,但根据UIRefreshControl类参考文档,它们是构建用于与UITableViewControllers一起使用的。我不想重新制作所有5个UIScrollView的工作方式。我已经尝试在我的UIScrollView中使用UIRefreshControl,它可以按预期工作,但仍有一些问题:

  1. 刷新图像变成加载器后,UIScrollView会向下跳大约10个像素,只有当我非常小心地缓慢拖动UIScrollview时才不会发生这种情况。

  2. 当我向下滚动并启动重新加载,然后放开UIScrollView时,UIScrollView停留在我放开它的位置。等它结束重新加载后,UIScrollView会无动画地跳到顶部。

以下是我的代码:

-(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];
}

有没有办法在UIScrollView中使用UIRefreshControl来节省时间呢?

谢谢!!!


1
发现你甚至可以做到这一点让我很高兴。但是,我遇到了一些问题,无法重现任何一个症状。我注意到一个完全不同的问题,即除非我在第一次创建刷新控件时明确设置初始attributedTitle(例如设置为"下拉刷新"),否则attributedTitle会出现在错误的位置,第一次下拉刷新时。因此,有几个问题:1. 在初始化创建时是否将attributedTitle设置为任何内容?另外,2. 是否使用自动布局?3. 是否对任何UIScrollViewDelegate方法进行其他操作? - Rob
11个回答

97

我成功地让 UIRefreshControlUIScrollView 配合使用:

- (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");
        });
    });
}

需要在单独的线程上进行数据更新,否则它将锁定主线程(UI使用主线程更新UI)。因此,当主线程正在忙于更新数据时,UI也被锁定或冻结,您永远无法看到平滑的动画或旋转器。
编辑:好的,我正在做与OP相同的事情,现在我已经添加了一些文本(即“下拉以刷新”),并且确实需要回到主线程来更新该文本。
更新的答案。

4
由于endRefreshing是一个UI方法,所以您可能希望将其放在主队列块中进行。 - Scott Berrevoets
我也这样认为,我的第一篇帖子包括检索主线程,但这不会导致任何错误或崩溃。 - Padin215
这个解决方案对我有效。然而,uirefreshcontrol旋转器在我的视图中处于最上层。有没有办法让它显示在我的视图下面? - Salx
2
@jsetting32 - 我用这行代码解决了我的问题:[refreshControl.superview sendSubviewToBack:refreshControl]; - Salx
这不适用于iOS 10。对于iOS 10,请参考Ben Packard的答案。 - Siyu
显示剩余6条评论

46

除了以上答案之外,有些情况下你无法设置contentSize(可能使用自动布局?),或者contentSize的高度小于或等于UIScrollView的高度。在这些情况下,UIRefreshControl将无法工作,因为UIScrollView不会弹起。

为了解决这个问题,请将属性alwaysBounceVertical设置为TRUE


1
我遇到了与上面答案相同的问题。然而,当我将alwaysBounceVertical设置为TRUE时,它就可以正常工作了。谢谢伙计。 - Noundla Sandeep
谢谢。我以为只有我遇到了这个问题。 - MetaSnarf
不要忘记同时添加 scrollView.bounces = true - SoftDesigner
谢谢,反弹过来的内容高度小于UIScrollView。 - Ramy Sabry

32
自从 iOS 10 开始,UIScrollView 已经具有 refreshControl 属性。当你创建一个 UIRefereshControl 并将其分配给此属性时,refreshControl 将出现。
现在不再需要将 UIRefereshControl 添加为子视图。
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()
   }
}

一个UIRefreshControl对象是一个标准控件,您可以将其附加到任何UIScrollView对象上。
来自https://developer.apple.com/documentation/uikit/uirefreshcontrol的代码和引用。

15

如果你幸运地支持iOS 10+的话,现在你可以简单地设置UIScrollViewrefreshControl。这与之前已存在于UITableView上的refreshControl相同工作方式。


12

这是如何在C# / Monotouch中完成的。我无法在任何地方找到C#的示例,所以在这里..感谢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 ();
        });
    });
}

非常感谢。效果很棒!我是这样在 WebView 上使用的:webView.ScrollView.AddSubview (refresh); - Jannie Theunissen

2
关于跳转问题,Tim Norman的回答解决了它。
如果您正在使用Swift2,这里是Swift版本:
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)
            }
        }
    }
}

2

如何在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()
}

1
我让UIRefreshControlUIScrollView中正常工作。我继承了UIScrollView,阻止了contentInset的更改,并重写了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)
            }
        }
    }
}

1
这个解决方案一开始看起来很有前途,但它有一个(未解释的?)副作用,就是在加载视图后几秒钟内我的滚动视图无法响应用户交互。 - cthulhu

1

对于跳动问题,仅覆盖contentInset只能在iOS 9之前解决它。 我刚试了一种避免跳动问题的方法:

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)

适用于iOS 9、10、11等版本,我希望他们(苹果)能够修复这个问题。


0
从iOS 10开始,UIScrollView具有refreshControl属性,您可以将其设置为UIRefreshControl。与往常一样,您无需管理控件的框架。只需配置控件,设置valueChanged事件的目标操作并分配给属性即可。
无论您是使用普通滚动视图、表视图还是集合视图,创建刷新控件的步骤都是相同的。
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()
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接