【问题标题】:Can I use a UIRefreshControl in a UIScrollView?我可以在 UIScrollView 中使用 UIRefreshControl 吗?
【发布时间】:2013-02-01 01:24:16
【问题描述】:

我的应用程序中已经有大约 5 个UIScrollView,它们都加载了多个.xib 文件。我们现在想使用UIRefreshControl。它们被构建为与 UITableViewControllers 一起使用(每个 UIRefreshControl 类参考)。我不想重新做所有 5 UIScrollView 的工作方式。我已经尝试在我的UIScrollView 中使用UIRefreshControl,除了一些事情之外,它可以正常工作。

  1. 就在刷新图片进入加载器后,UIScrollView向下跳约10个像素,只有当我非常小心地将UIScrollview向下拖得很慢时才会发生这种情况。

  2. 当我向下滚动并启动重新加载,然后放开 UIScrollViewUIScrollView 停留在我放开的位置。重新加载完成后,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 方法做其他事情吗?

标签: ios iphone uiscrollview xamarin.ios uirefreshcontrol


【解决方案1】:

从 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()
}

【讨论】:

    【解决方案2】:

    由于 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

    【讨论】:

      【解决方案3】:

      对于 Jumping 问题,覆盖 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 等,我希望他们(Apple)能解决这个问题。

      【讨论】:

        【解决方案4】:

        我有一个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 相同的事情,我现在已经向它添加了一些文本(即“拉动刷新”),它确实需要回到主线程来更新该文本.

        更新答案。

        【讨论】:

        • 您可能希望将您的 endRefreshing 放在主队列块中,因为它是一种 UI 方法。
        • 我也是这么想的,我的第一篇文章包括检索主线程,但这不会导致任何错误或崩溃。
        • 这个解决方案对我有用。但是,在我看来,uirefreshcontrol 微调器是最重要的。有什么办法可以显示在我的视野下方?
        • @Salx ...同样的问题:/
        • @jsetting32 - 我用这行代码解决了我的问题:[refreshControl.superview sendSubviewToBack:refreshControl];
        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

          这是在 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);
          【解决方案7】:

          我让UIRefreshControlUIScrollView 中正常工作。我继承了UIScrollView,阻止了 contentInset 的更改并覆盖了 contentOffset 设置器:

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

          【讨论】:

          • 这个解决方案起初看起来很有希望,但它有一个(无法解释的?)副作用,即我的滚动视图在加载视图后几秒钟内没有响应用户交互。
          【解决方案8】:

          如果您有幸支持 iOS 10+,您现在可以简单地设置 refreshControlUIScrollView。这与 UITableView 上先前存在的 refreshControl 的工作方式相同。

          【讨论】:

            【解决方案9】:

            对于跳跃问题,Tim Norman's answer 解决了。

            如果您使用的是 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)
                        }
                    }
                }
            }
            

            【讨论】:

              【解决方案10】:

              您可以简单地创建一个刷新控件的实例并将其添加到滚动视图的顶部。然后,在委托方法中,您可以根据自己的要求调整其行为。

              【讨论】:

              • 如果您阅读了我的问题,那就是我所做的。你有不同的方式吗?
              【解决方案11】:

              除了上述答案之外,在某些情况下,您无法设置 contentSize(也许使用自动布局?)或者 contentSize 的高度小于或等于 UIScrollView 的高度。在这些情况下,UIRefreshControl 将不起作用,因为 UIScrollView 不会反弹。

              要解决此问题,请将属性 alwaysBounceVertical 设置为 TRUE

              【讨论】:

              • 按照上述答案,我也遇到了同样的问题。但是,当我将 alwaysBounceVertical 设置为 TRUE 时,效果很好。谢谢老兄。
              • 谢谢。我以为我是唯一遇到这个问题的人。
              • 别忘了scrollView.bounces = true
              • 谢谢,反弹过来的内容高度小于 UIScrollView
              猜你喜欢
              • 2023-03-18
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-10-22
              • 1970-01-01
              • 2019-08-04
              • 1970-01-01
              相关资源
              最近更新 更多