So erstellen Sie den Bildlauf einer Tabellenansicht in ScrollView auf natürliche Weise

78

Ich muss diese App machen, die eine seltsame Konfiguration hat.

Wie im nächsten Bild gezeigt, ist die Hauptansicht eine UIScrollView. Darin sollte sich dann eine UIPageView befinden, und jede Seite der PageView sollte eine UITableView haben.

Zahl

Ich habe das alles bisher gemacht. Mein Problem ist jedoch, dass sich das Scrollen natürlich verhält .

Das nächste meine ich natürlich . Wenn ich derzeit in einer der UITableViews scrolle, wird die Tabellenansicht (nicht die Bildlaufansicht) gescrollt. Ich möchte jedoch, dass die Bildlaufansicht gescrollt wird, es sei denn, die Bildlaufansicht kann nicht gescrollt werden, da sie oben oder unten angezeigt wird (in diesem Fall möchte ich, dass die Tabellenansicht gescrollt wird).

Angenommen, meine Bildlaufansicht wird derzeit nach oben gescrollt. Dann lege ich meinen Finger über die Tabellenansicht (der aktuell angezeigten Seite) und beginne mit dem Scrollen nach unten . In diesem Fall möchte ich, dass die Bildlaufansicht gescrollt wird (nein, die Tabellenansicht). Wenn ich meine Bildlaufansicht weiter nach unten scrolle und sie den unteren Rand erreicht, wenn ich meinen Finger vom Display entferne und ihn wieder über die Tebleansicht lege und wieder nach unten scrolle, möchte ich, dass meine Tabellenansicht jetzt nach unten scrollt, da die Bildlaufansicht ihren unteren Rand erreicht hat und dies nicht der Fall ist in der Lage, weiter zu scrollen.

Habt ihr eine Idee, wie man dieses Scrollen implementiert?

Ich bin wirklich verloren damit. Jede Hilfe wird es sehr schätzen :(

Vielen Dank!

Marcela Mukel Balady
quelle
Was passiert, wenn Ihr Benutzer in der Bildlaufansicht wieder nach oben scrollen möchte? Müssen sie in der Tabellenansicht ganz nach oben scrollen?
Daniel Zhang
Nein @DanielZhang. In diesem Fall sollte die Bildlaufansicht nach oben scrollen, bis sie nicht mehr scrollen kann. Dann wäre es an der Zeit, dass die Tabellenansicht nach oben rollt.
Marcela Mukel Balady
Ich habe eine Antwort hinzugefügt und festgestellt, dass ich das Gegenteil von dem getan habe, was Sie in Bezug auf das Scrollen in der Tabellenansicht nach oben beschreiben. So scheint es mir natürlich. Können Sie weiter beschreiben, wie das Verhalten anders sein sollte?
Daniel Zhang

Antworten:

59

Die Lösung für die gleichzeitige Behandlung der Bildlaufansicht und der Tabellenansicht dreht sich um die UIScrollViewDelegate. Lassen Sie Ihren View Controller daher diesem Protokoll entsprechen:

class ViewController: UIViewController, UIScrollViewDelegate {

Ich werde die Bildlaufansicht und die Tabellenansicht als Ausgänge darstellen:

@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var tableView: UITableView!

Wir müssen auch die Höhe des Inhalts der Bildlaufansicht sowie die Bildschirmhöhe verfolgen. Du wirst später sehen warum.

let screenHeight = UIScreen.mainScreen().bounds.height
let scrollViewContentHeight = 1200 as CGFloat

Eine kleine Konfiguration ist erforderlich in viewDidLoad::

override func viewDidLoad() {
    super.viewDidLoad()

    scrollView.contentSize = CGSizeMake(scrollViewContentWidth, scrollViewContentHeight)
    scrollView.delegate = self
    tableView.delegate = self 
    scrollView.bounces = false
    tableView.bounces = false
    tableView.scrollEnabled = false
}

wo ich das Hüpfen ausgeschaltet habe, um die Dinge einfach zu halten. Die Schlüsseleinstellungen sind die Delegaten für die Bildlaufansicht und die Tabellenansicht, und das Scrollen der Tabellenansicht wird zuerst deaktiviert.

Diese sind erforderlich, damit die scrollViewDidScroll:Delegatmethode den unteren Rand der Bildlaufansicht und den oberen Rand der Tabellenansicht erreichen kann. Hier ist diese Methode:

func scrollViewDidScroll(scrollView: UIScrollView) {
    let yOffset = scrollView.contentOffset.y

    if scrollView == self.scrollView {
        if yOffset >= scrollViewContentHeight - screenHeight {
            scrollView.scrollEnabled = false
            tableView.scrollEnabled = true
        }
    }

    if scrollView == self.tableView {
        if yOffset <= 0 {
            self.scrollView.scrollEnabled = true
            self.tableView.scrollEnabled = false
        }
    }
}

Die Delegate-Methode erkennt, wann die Bildlaufansicht ihren unteren Rand erreicht hat. In diesem Fall kann die Tabellenansicht gescrollt werden. Es wird auch erkannt, wann die Tabellenansicht den oberen Rand erreicht, an dem die Bildlaufansicht wieder aktiviert wird.

Ich habe ein GIF erstellt, um die Ergebnisse zu demonstrieren:

Geben Sie hier die Bildbeschreibung ein

Daniel Zhang
quelle
7
Hey Daniel, vielen Dank für deine Antwort. Es klappt! Obwohl der Benutzer jedes Mal zweimal scrollen muss, wenn ein Bildlauf aktiviert / deaktiviert wird. Ich meine, das Scrollen überträgt sich in diesem Moment nicht von einer Schriftrolle zur anderen. Haben Sie eine Vorstellung davon, wie dies erreicht werden kann?
Nochmals vielen
1
Gern geschehen, Marcela. Um Ihre Frage zu beantworten, kenne ich keine bequeme Möglichkeit, den Bildlauf von einer Bildlaufansicht in eine andere zu übertragen. Unter stackoverflow.com/questions/21554327/… finden Sie hilfreiche Informationen . Manchmal lohnt es sich nicht, etwas zu verfolgen, das aufgrund der damit verbundenen Arbeit und des Risikos, dass es in zukünftigen Betriebssystemversionen kaputt geht, übermäßige Anpassungen erfordert.
Daniel Zhang
@DanielZhang, um das Scrollen zu übertragen, teilen Sie einfach das .contentOffset zwischen den Ansichten, die Sie synchronisieren möchten, wenn die Delegatmethode scrollViewDidScroll aufgerufen wird.
Ian
@ Daniel Zhang Gute Arbeit und Demo, aber in meinem Fall gab es ein Problem im Zusammenhang mit Einschränkungen, die ich für Ansicht, Bildlaufansicht und Tabellenansicht festgelegt habe. Sobald ich es repariert habe, funktioniert es in 1 Sekunde.
Ravi
@ DanielZhang Wie hoch ist die Tabellenansicht für diesen Code?
e.ozmen
28

Daniels Antwort wurde geändert, um sie effizienter und fehlerfreier zu gestalten.

@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var tableHeight: NSLayoutConstraint!

override func viewDidLoad() {
    super.viewDidLoad()
    //Set table height to cover entire view
    //if navigation bar is not translucent, reduce navigation bar height from view height
    tableHeight.constant = self.view.frame.height-64
    self.tableView.isScrollEnabled = false
    //no need to write following if checked in storyboard
    self.scrollView.bounces = false
    self.tableView.bounces = true
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 20
}

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let label = UILabel(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 30))
    label.text = "Section 1"
    label.textAlignment = .center
    label.backgroundColor = .yellow
    return label
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
    cell.textLabel?.text = "Row: \(indexPath.row+1)"
    return cell
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if scrollView == self.scrollView {
        tableView.isScrollEnabled = (self.scrollView.contentOffset.y >= 200)
    }

    if scrollView == self.tableView {
        self.tableView.isScrollEnabled = (tableView.contentOffset.y > 0)
    }
}

Das vollständige Projekt finden Sie hier: https://gitlab.com/vineetks/TableScroll.git

Vineet Kumar Singh
quelle
@ Vinit Kumar Singh Großartig, es hat das Problem gelöst. Vielen Dank
Vinayak Bhor
@Vineet Kumar Wenn ich keine Navigationsleiste habe, wie geht das? Wenn wir in Ihrem Code die Navigationsleiste entfernen, wird nur bis zur 11. Zeile
gescrollt
1
64 ist die Standardhöhe der Navigationsleiste. Versuchen Sie, es in der viewDidLoad () -Methode auf Null zu reduzieren.
Vineet Kumar Singh
Dies ist die funktionierende Lösung. @ Marcela Bitte akzeptieren Sie diese Antwort
Naveed Ahmad
Arbeitslösung, Arbeitete für mich.
Pritesh
4

Nach vielen Versuchen und Irrtümern hat dies für mich am besten funktioniert. Die Lösung muss zwei Anforderungen lösen: 1) Bestimmen Sie, wer die Bildlaufeigenschaft verwenden soll. tableView oder scrollView? 2) Stellen Sie sicher, dass die tableView der scrollView keine Berechtigung erteilt, bis sie den oberen Rand ihrer Tabelle / ihres Inhalts erreicht hat.

Um zu sehen, ob die Bildlaufansicht zum Scrollen im Vergleich zur Tabellenansicht verwendet werden soll, habe ich überprüft, ob sich die UIView direkt über meiner Tabellenansicht innerhalb des Rahmens befindet. Wenn sich die UIView innerhalb des Frames befindet, kann man mit Sicherheit sagen, dass die scrollView die Berechtigung zum Scrollen haben sollte. Wenn sich die UIView nicht innerhalb des Frames befindet, bedeutet dies, dass die tableView das gesamte Fenster einnimmt und daher die Berechtigung zum Scrollen haben sollte.

func scrollViewDidScroll(_ scrollView: UIScrollView) {

    if scrollView.bounds.intersects(UIView.frame) == true {
     //the UIView is within frame, use the UIScrollView's scrolling.   

        if tableView.contentOffset.y == 0 {
            //tableViews content is at the top of the tableView.

        tableView.isUserInteractionEnabled = false
       tableView.resignFirstResponder()
        print("using scrollView scroll")

        } else {

            //UIView is in frame, but the tableView still has more content to scroll before resigning its scrolling over to ScrollView.

            tableView.isUserInteractionEnabled = true
            scrollView.resignFirstResponder()
            print("using tableView scroll")
        }

    } else {

        //UIView is not in frame. Use tableViews scroll.

        tableView.isUserInteractionEnabled = true
       scrollView.resignFirstResponder()
        print("using tableView scroll")

    }

}

hoffe das hilft jemandem!

Felecia Genet
quelle
Was soll UIView.frame sein?
Andy Obusek
@AndyObusek jede UIView dicht über Ihrer Tabellenansicht.
Felecia Genet
Könnten Sie bitte ein Projekt davon hochladen, da es viele gibt, die Schwierigkeiten haben zu sehen, wie dies funktioniert. Ich habe Ihre Lösung in Vineets Demo-Projekt unten implementiert (da dies zum Testen verfügbar war), aber die darin enthaltene Tabellenansicht ist blockiert und der Bildlauf wird nicht übertragen.
Pavan
2
Ihre Lösung überträgt die Schriftrolle nicht gleichzeitig. Sie müssen zweimal auf tippen und scrollen, um von der Bildlaufansicht zu einer Tabellenansicht zu gelangen.
Pavan
3

Ich hatte auch mit diesem Problem zu kämpfen. Es gibt eine sehr einfache Lösung.

Im Interface Builder:

  1. Erstellen Sie einen einfachen ViewController
  2. Fügen Sie eine einfache Ansicht hinzu, es wird unser Header sein, und beschränken Sie sie auf die Übersicht
    • Es ist die rote Ansicht im folgenden Beispiel
    • Ich habe 12px von oben, links und rechts hinzugefügt und die feste Höhe auf 128px eingestellt
  3. Betten Sie einen PageViewController ein und stellen Sie sicher, dass er auf die Übersicht und nicht auf den Header beschränkt ist

ib

Jetzt kommt der lustige Teil: Stellen Sie für jede Seite, die Sie hinzufügen, sicher, dass die tableView einen Versatz von oben hat. Das ist es. Sie können dies beispielsweise mit diesem Code tun (vorausgesetzt, Sie verwenden UITableViewController als Seite):

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    let tables = viewControllers.compactMap { $0 as? UITableViewController }
    tables.forEach {
        $0.tableView.contentInset = UIEdgeInsets(top: headerView.bounds.height, left: 0, bottom: 0, right: 0)
        $0.tableView.contentOffset = CGPoint(x: 0, y: -headerView.bounds.height)
    }
}

Keine unordentliche Schriftrolle in der Tabellenansicht, keine Verwirrung mit Delegierten, keine doppelten Schriftrollen, vollkommen natürliches Verhalten. Wenn Sie den Header nicht sehen können, liegt dies wahrscheinlich an der Hintergrundfarbe von tableView. Sie müssen es auf clear setzen, damit der Header unter der tableView sichtbar ist.

Mateusz
quelle
Eine Benutzerinteraktion mit der Header-Ansicht ist mit dieser Lösung nicht möglich, oder?
Knut Valen
Ich konnte diese Arbeit nicht machen. Die Abschnittsüberschriften für die Tabelle sind über dem contentInset / offset unsichtbar, und die Schaltflächen in der Überschrift können nicht angeklickt werden. Das Scrollen ist flüssig und fühlt sich jedoch gut an.
Knut Valen
2

Keine der Antworten hier hat perfekt für mich funktioniert. Jeder hatte sein eigenes nuanciertes Problem (er musste wiederholt wischen, wenn eine Bildlaufansicht den Boden erreichte oder die Bildlaufanzeige nicht richtig aussah usw.), also dachte ich mir, ich würde eine andere Antwort einwerfen.

Ole Begemann hat eine großartige Beschreibung dazu: https://oleb.net/blog/2014/05/scrollviews-inside-scrollviews/

Obwohl es sich um einen alten Beitrag handelt, gelten die Konzepte weiterhin für die aktuellen APIs. Darüber hinaus gibt es eine gepflegte (Xcode 9-kompatible) Objective-C-Implementierung seines Ansatzes https://github.com/eyeem/OLEContainerScrollView

Andy Obusek
quelle
2

Wenn Sie Probleme mit dem verschachtelten Bildlauf haben, ist dies die einfachste Lösung.

  1. Gehen Sie zu Ihrem Design-Bildschirm
  2. Wählen Sie Ihre Bildlaufansicht aus und deaktivieren Sie dann den Sprung beim Bildlauf
  3. Wenn Ihre Ansicht die Tabellenansicht in der Bildlaufansicht verwendet, deaktivieren Sie auch den Sprung beim Bildlauf in der Tabellenansicht
  4. Führen Sie aus und überprüfen Sie, ob es gelöst ist

Überprüfen Sie, wie Sie das Abprallen beim Scrollen einer Bildlaufansicht deaktivieren

Überprüfen Sie, wie Sie das Abprallen beim Scrollen einer Tabellenansicht deaktivieren können

user12365129
quelle
1

Ich denke, es gibt zwei Möglichkeiten.

Da Sie die Größe der Bildlaufansicht und der Hauptansicht kennen, können Sie nicht feststellen, ob die Bildlaufansicht den unteren Rand erreicht hat oder nicht.

if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)) {
    // reach bottom
}

Also, wenn es traf; Sie setzen im Grunde

[contentScrollView setScrollEnabled:NO];

und umgekehrt für Ihre tableView.

Die andere Sache, die meiner Meinung nach genauer ist, besteht darin, Ihren Ansichten eine Geste hinzuzufügen.

UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(respondToTapGesture:)];

// Specify that the gesture must be a single tap
tapRecognizer.numberOfTapsRequired = 1;

// Add the tap gesture recognizer to the view
[self.view addGestureRecognizer:tapRecognizer];

// Do any additional setup after loading the view, typically from a nib

Also , wenn Sie Gesture hinzufügen, können Sie einfach die aktive Ansicht steuern , indem setScrollEnabledin der respondToTapGesture.

Berke Atmaca
quelle
1

Ich habe eine tolle Bibliothek MXParallaxHeader gefunden

Stellen Sie im Storyboard einfach die UIScrollViewKlasse auf " MXScrollViewMagie" ein.

Ich habe diese Klasse verwendet, um meine UIScrollViewbeim Einbetten einer UIPageViewControllerContaineransicht zu behandeln. Sie können sogar eine Parallaxen-Header-Ansicht einfügen, um weitere Details zu erhalten.

Auch diese Bibliothek bietet CocoapodsundCarthage

Ich habe ein Bild angehängt, das die UIViewHierarchie darstellt. MXScrollView-Hierarchie

Farshad Mousalou
quelle
0

Ich habe versucht, die als richtige Antwort gekennzeichnete Lösung zu finden, aber sie funktionierte nicht richtig. Der Benutzer muss zweimal auf die Tabellenansicht klicken, um einen Bildlauf durchzuführen, und danach konnte ich nicht mehr den gesamten Bildschirm scrollen. Also habe ich gerade den folgenden Code in viewDidLoad () angewendet:

tableView.addGestureRecognizer(UISwipeGestureRecognizer(target: self, action: #selector(tableViewSwiped)))
scrollView.addGestureRecognizer(UISwipeGestureRecognizer(target: self, action: #selector(scrollViewSwiped)))

Und der folgende Code ist die Implementierung der Aktionen:

func tableViewSwiped(){
    scrollView.isScrollEnabled = false
    tableView.isScrollEnabled = true
}

func scrollViewSwiped(){
    scrollView.isScrollEnabled = true
    tableView.isScrollEnabled = false
}
Vinícius Rocha
quelle
0

Vielleicht Brute-Force, aber es funktioniert perfekt: Ich verwende übrigens das automatische Layout.

Legen Sie für tableView (oder collectionView oder was auch immer) eine beliebige Höhe im Storyboard fest und stellen Sie einen Ausgang für die Klasse her. Stellen Sie gegebenenfalls (viewDidLoad () oder ...) die Höhe der tableView so groß ein, dass tableView nicht scrollen muss. (Die Anzahl der Zeilen muss im Voraus bekannt sein.) Dann wird nur die äußere scrollView gut scrollen.

Brian Hong
quelle
Dies funktioniert nicht für Tabellenansichten mit unterschiedlicher Zellenhöhe
Mohammad Reza Koohkan
0

Ein einfacher Trick, wenn Sie dies erreichen möchten, ist das Ersetzen der übergeordneten Bildlaufansicht durch die normale Containeransicht. Wenn Sie der Containeransicht eine Schwenkgeste hinzufügen, können Sie mit der obersten Einschränkung der ersten Ansicht spielen, um negative Werte zuzuweisen. Sie können die Herkunft der Seitenansicht überprüfen, wenn sie den oberen Rand erreicht. Sie können diesen Wert für den Inhaltsversatz der untergeordneten Ansicht der Seitenansicht zuweisen. Bis der Benutzer die Tabellenansicht in der Containeransicht in der obersten Ansicht erreicht, können Sie das Scrollen der Seitentabelle deaktivieren und das manuelle Scrollen zulassen, indem Sie den Inhaltsversatz festlegen. Daher wird die Höhe der Seitenansicht zunächst reduziert (oder beispielsweise außerhalb des Bildschirms) oder weniger unten. Später beim Scrollen nach unten wird es erweitert, um mehr Platz zu beanspruchen.

Die Geste reagiert automatisch nicht mehr, wenn außerhalb der Frames die Navigationsleiste oder eine andere Ansicht außerhalb der Containeransicht angezeigt wird.

Gesten sind ein Schlüssel zu interaktiven Benutzerübergängen, die in vielen Apps verwendet werden. Sie können damit das Scrollen für eine bestimmte Zeit nachahmen.

Amber K.
quelle
0

In meinem Fall verwende ich eine Einschränkung für die Höhe wie folgt:

self.heightTableViewConstraint.constant = self.tableView.contentSize.height
self.scrollView.contentInset.bottom = self.tableView.contentSize.height
Dansp
quelle
-1
CGFloat tableHeight = 0.0f;

YourArray =[response valueForKey:@"result"];

tableHeight = 0.0f;
for (int i = 0; i < [YourArray count]; i ++) {
    tableHeight += [self tableView:self.aTableviewDoc heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
}

self.aTableviewDoc.frame = CGRectMake(self.aTableviewDoc.frame.origin.x, self.aTableviewDoc.frame.origin.y, self.aTableviewDoc.frame.size.width, tableHeight);
Bhadresh Baraiya
quelle
Hallo. Die Frage ist mit "schnell" gekennzeichnet. Bitte schreiben Sie Ihre Antwort gemäß den Tags. Vielen Dank! :)
Eric Aya
1
Sie sollten Ihrem Code auch eine Erklärung hinzufügen. Wie löst dieses Snippet das Problem des OP?
Luca Angeletti