Можно ли использовать UIRefreshControl в UIScrollView?


У меня уже есть около 5 UIScrollView в моем приложении, которые все загружают несколько .xib файлов. Теперь мы хотим использовать UIRefreshControl. Они построены для использования с UITableViewControllers (по ссылке на класс UIRefreshControl). Я не хочу переделывать, как работают все 5 UIScrollView. Я уже пытался использовать UIRefreshControl в моем UIScrollView, и это работает, как и ожидалось, за исключением нескольких вещей.

  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];
}
Есть ли способ сэкономить кучу времени и использовать UIRefreshControl в UIScrollView?

Спасибо!!!

8 67

8 ответов:

Я получил UIRefreshControl для работы с 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");
        });
    });
}

Необходимо выполнить обновление данных в отдельном потоке, иначе он заблокирует основной поток (который пользовательский интерфейс использует для обновления пользовательского интерфейса). Таким образом, пока основной поток занят обновлением данных, пользовательский интерфейс также заблокирован или заморожен, и вы никогда не увидите плавную анимацию или спиннер.

EDIT: ок, я делаю то же самое, что и OP, и теперь я добавил к нему текст (т. е." Pull To Refresh"), и ему нужно вернуться в основной поток, чтобы обновить его текст.

Обновленный ответ.

Добавляя к вышеприведенным ответам, в некоторых ситуациях вы не можете установить размер содержимого (возможно, с помощью автоматической компоновки?) или высота contentSize меньше или равна высоте UIScrollView. В этих случаях UIRefreshControl не будет работать, потому что UIScrollView не будет отскакивать.

Чтобы исправить это, установите свойство alwaysBounceVertical в TRUE.

Если и когда вам посчастливится поддерживать iOS 10+, Теперь вы можете просто установить refreshControl из UIScrollView. Это работает так же, как и ранее существовавшие refreshControl на UITableView.

Вот как это делается в 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 ();
        });
    });
}

Для прыжковой проблемы, Ответ Тима Нормана решает ее.

Вот версия swift, если вы используете 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)
            }
        }
    }
}

Как это сделать в 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()
}

Я заставил UIRefreshControl работать должным образом внутри UIScrollView. Я унаследовал 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)
            }
        }
    }
}

Можно просто создать экземпляр элемента управления refresh и добавить его в верхней части представления прокрутки. затем в методах делегирования вы настраиваете его поведение в соответствии с вашими требованиями.