Kann ich ein UIRefreshControl in einer UIScrollView verwenden?

77

Ich habe bereits ungefähr 5 UIScrollViewin meiner App, die alle mehrere .xibDateien laden . Wir wollen jetzt a verwenden UIRefreshControl. Sie sind für die Verwendung mit UITableViewControllern (gemäß UIRefreshControl-Klassenreferenz) erstellt. Ich möchte nicht wiederholen, wie alle 5 UIScrollViewfunktionieren. Ich habe bereits versucht, das UIRefreshControlin meinem zu verwenden UIScrollView, und es funktioniert wie erwartet, bis auf ein paar Dinge.

  1. Kurz nachdem sich das Aktualisierungsbild in den Loader verwandelt hat, UIScrollViewspringt das Bild um etwa 10 Pixel nach unten, was nur dann nicht der Fall ist, wenn ich sehr vorsichtig bin, das Bild UIScrollviewsehr langsam nach unten zu ziehen .

  2. Als ich nach unten scrollen und den Reload initiieren, dann lassen Sie die gehen von UIScrollViewderUIScrollView ich die los wo ich sie loslasse. Nach dem Nachladen UIScrollViewspringt das Gerät ohne Animation nach oben.

Hier ist mein 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];
}

Gibt es eine Möglichkeit, eine Menge Zeit zu sparen und eine zu verwenden UIRefreshControl in a zu verwenden UIScrollView?

Dankeschön!!!

Sir RupertIII
quelle
1
+1 für die Entdeckung, dass Sie dies sogar tun können. Aber ich habe Probleme, beide Symptome zu reproduzieren. Ich stelle ein völlig anderes Problem fest, das attributedTitlebeim ersten Herunterziehen zum Aktualisieren an der falschen Stelle angezeigt wird, es sei denn, ich habe attributedTitlebeim ersten Erstellen des Aktualisierungssteuerelements explizit die Initiale festgelegt (z. B. "Zum Aktualisieren ziehen"). Also ein paar Fragen: 1. Initialisieren Sie das attributedTitlewährend dieser ersten Erstellung auf irgendetwas? Auch 2. Verwenden Sie das automatische Layout? 3. Machst du noch etwas mit irgendwelchen UIScrollViewDelegateMethoden?
Rob

Antworten:

17

Seit iOS 10 verfügt UIScrollView bereits über eine refreshControl-Eigenschaft . Dieses refreshControl wird angezeigt, wenn Sie ein UIRefereshControl erstellen und dieser Eigenschaft zuweisen.

Es gibt keine Notwendigkeit , UIRefereshControl als Subview mehr hinzuzufügen.

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

Ein UIRefreshControl-Objekt ist ein Standardsteuerelement, das Sie an ein beliebiges UIScrollView-Objekt anhängen

Code und Zitat von https://developer.apple.com/documentation/uikit/uirefreshcontrol

MacMark
quelle
94

Ich muss UIRefreshControlmit einem arbeiten 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");
        });
    });
}

Die Datenaktualisierung muss in einem separaten Thread durchgeführt werden, da sonst der Hauptthread (mit dem die Benutzeroberfläche die Benutzeroberfläche aktualisiert) gesperrt wird. Während der Haupt-Thread mit dem Aktualisieren der Daten beschäftigt ist, ist die Benutzeroberfläche ebenfalls gesperrt oder eingefroren, und Sie sehen nie die glatten Animationen oder den Spinner.

EDIT: ok, ich mache das gleiche wie OP und habe jetzt Text hinzugefügt (dh "Zum Aktualisieren ziehen") und es muss wieder auf den Haupt-Thread zurückgegriffen werden, um diesen Text zu aktualisieren.

Antwort aktualisiert.

Padin215
quelle
4
Sie werden Ihre wahrscheinlich endRefreshingin einen Hauptwarteschlangenblock stellen wollen , da es sich um eine UI-Methode handelt.
Scott Berrevoets
Das habe ich auch gedacht und mein erster Beitrag beinhaltete das Abrufen des Hauptthreads, aber dies verursacht keine Fehler oder Abstürze.
Padin215
Diese Lösung funktioniert bei mir. Meiner Ansicht nach hat der uirefreshcontrol-Spinner jedoch alles im Griff. Gibt es eine Möglichkeit, unter meiner Ansicht zu erscheinen?
Salx
@Salx ... gleiche Ausgabe: /
jsetting32
2
@ jsetting32 - Ich habe mein Problem mit dieser Codezeile behoben: [refreshControl.superview sendSubviewToBack: refreshControl];
Salx
42

In einigen Situationen können Sie die contentSize nicht festlegen (möglicherweise mithilfe des automatischen Layouts?), Oder die Höhe der contentSize ist kleiner oder gleich der Höhe der UIScrollView. In diesen Fällen funktioniert das UIRefreshControl nicht, da das UIScrollView nicht abprallt.

Um dies zu beheben, setzen Sie die Eigenschaft alwaysBounceVertical auf TRUE .

Diego Frata
quelle
1
Ich habe mich dem gleichen Problem gestellt, indem ich der obigen Antwort gefolgt bin. Als ich jedoch das alwaysBounceVertical auf TRUE hielt, funktionierte es gut. Danke Kumpel.
Noundla Sandeep
Vielen Dank. Ich dachte, ich wäre der einzige, der dieses Problem hat.
MetaSnarf
Vergiss auch nicht scrollView.bounces = true!
SoftDesigner
Vielen Dank, Bounce kommen über Inhaltshöhe weniger als UIScrollView
Ramy Sabry
14

Falls und wenn Sie Glück haben , zu unterstützen iOS 10+ sind, können Sie jetzt einfach das Set refreshControlder UIScrollView. Dies funktioniert auf die gleiche Art und Weise wie die zuvor bestehenden refreshControlauf UITableView.

Ben Packard
quelle
11

So machen Sie das in C # / Monotouch. Ich kann nirgendwo Beispiele für C # finden, also hier ist es .. Danke 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 ();
        });
    });
}
John Gavilan
quelle
Vielen Dank. Funktioniert super! Ich habe es in einem WebView wie diesem verwendet:webView.ScrollView.AddSubview (refresh);
Jannie Theunissen
2

Für das Thema Springen die Antwort von Tim Norman löst .

Hier ist die schnelle Version, wenn Sie swift2 verwenden:

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)
            }
        }
    }
}
Sicherlich
quelle
2

Wie es in Swift 3 geht:

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()
}
Camilo Ortegón
quelle
1

Ich habe die UIRefreshControlArbeit im Inneren richtig gemacht UIScrollView. Ich habe geerbt UIScrollView, das Ändern von contentInset blockiert und den contentOffset-Setter überschrieben:

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)
            }
        }
    }
}
Anton Zherdev
quelle
1
Diese Lösung schien zunächst vielversprechend, hatte jedoch den (ungeklärten?) Nebeneffekt, dass meine Bildlaufansicht nach dem Laden der Ansicht einige Sekunden lang nicht auf Benutzerinteraktionen reagierte.
Cthulhu
1

Überschreiben Sie contentInset für das Jumping-Problem nur vor iOS 9. Ich habe nur versucht, ein Sprungproblem zu vermeiden:

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)

funktioniert unter iOS 9 10 11 und so weiter, und ich hoffe, dass sie (Apple) das Problem nur beheben.

Dante
quelle
0

Ab iOS 10 verfügt eine UIScrollView über eine refreshControl-Eigenschaft, die Sie auf ein UIRefreshControl festlegen können. Wie immer müssen Sie den Rahmen des Steuerelements nicht verwalten. Konfigurieren Sie einfach das Steuerelement, legen Sie die Zielaktion für das valueChanged-Ereignis fest und weisen Sie es der Eigenschaft zu.

Unabhängig davon, ob Sie eine einfache Bildlaufansicht, eine Tabellenansicht oder eine Sammlungsansicht verwenden, sind die Schritte zum Erstellen eines Aktualisierungssteuerelements dieselben.

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()
}
Sudan Suwal
quelle
-3

Sie können einfach eine Instanz des Aktualisierungssteuerelements erstellen und oben in der Bildlaufansicht hinzufügen. Anschließend passen Sie in den Delegate-Methoden das Verhalten an Ihre Anforderungen an.

Gianluca Tranchedone
quelle
Wenn Sie meine Frage lesen, habe ich das getan. Machst du es anders?
SirRupertIII