web-dev-qa-db-fra.com

Comment rendre le défilement d'un TableView dans ScrollView se comporter naturellement

Je dois faire cette application qui a une configuration étrange.

Comme indiqué dans l'image suivante, la vue principale est une vue UIScrollView. Ensuite, il devrait y avoir un UIPageView et chaque page du PageView devrait avoir un UITableView.

 figure

J'ai fait tout ça jusqu'à présent. Mais mon problème est que je veux que le défilement se fasse sur se comporte _ {naturellement

La prochaine est ce que je veux dire naturellement. Actuellement, lorsque je fais défiler une des vues UITableViews, il fait défiler la vue de table (et non la vue de défilement). Mais je veux qu'il défile ScrollView à moins que le scrollview ne puisse pas défiler car il est arrivé en haut ou en bas (dans ce cas, j'aimerais qu'il défile dans la table).

Par exemple, supposons que mon scrollview défile vers le haut. Puis je pose le doigt sur la table (de la page en cours d’affichage) et commence à faire défiler bas. Dans ce cas, je veux que le scrollview défile (pas de table). Si je continue à faire défiler mon défilement vers le bas et qu’il atteint le bas, si je retire mon doigt de l’affichage pour le remettre au-dessus de la tebleview et le fais défiler à nouveau, je veux que mon tableau se défile maintenant, car le défilement a atteint son bas et il capable de continuer à faire défiler.

Avez-vous les gars une idée sur la façon de mettre en œuvre ce défilement?

Je suis vraiment perdu avec ça. Toute aide sera grandement apprécier :(

Merci!

48

La solution permettant de gérer simultanément la vue de défilement et la vue de table tourne autour de la variable UIScrollViewDelegate. Par conséquent, demandez à votre contrôleur de vue de se conformer à ce protocole:

class ViewController: UIViewController, UIScrollViewDelegate {

Je vais représenter la vue par défilement et la vue sous forme de points de vente:

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

Nous devrons également suivre la hauteur du contenu de la vue défilante ainsi que la hauteur de l’écran. Vous verrez pourquoi plus tard.

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

Une petite configuration est nécessaire dans 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
}

où j’ai arrêté de rebondir pour garder les choses simples. Les paramètres clés sont les délégués de la vue par défilement et de la vue tableau, le défilement de cette dernière étant d'abord désactivé.

Celles-ci sont nécessaires pour que la méthode déléguée scrollViewDidScroll: puisse atteindre le bas de la vue de défilement et atteindre le haut de la vue du tableau. Voici cette méthode:

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

La méthode déléguée détecte quand la vue de défilement a atteint son point le plus bas. Lorsque cela est arrivé, vous pouvez faire défiler la vue tableau. Il détecte également lorsque la vue de table atteint le sommet où la vue de défilement est réactivée.

J'ai créé un GIF pour démontrer les résultats:

 enter image description here

44
Daniel Zhang

Modification de la réponse de Daniel pour la rendre plus efficace et sans bug.

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

Le projet complet peut être vu ici: https://gitlab.com/vineetks/TableScroll.git

16
Vineet Kumar Singh

Après de nombreuses tentatives et erreurs, voici ce qui a fonctionné le mieux pour moi. La solution doit répondre à deux besoins: 1) déterminer la propriété de défilement à utiliser; tableView ou scrollView? 2) assurez-vous que la tableView n’autorise pas la scrollView tant qu’elle n’a pas atteint le sommet de sa table/contenu. 

Afin de voir si le scrollview doit être utilisé pour le défilement par rapport à la table, j'ai vérifié si l'UIView juste au-dessus de ma table était dans le cadre. Si UIView est dans le cadre, il est prudent de dire que le scrollView devrait avoir l'autorité de faire défiler. Si UIView ne se trouve pas dans le cadre, cela signifie que le tableView occupe toute la fenêtre et qu’il doit par conséquent être autorisé à faire défiler.

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

    }

}

espérons que cela aide quelqu'un!

4
Felecia Genet

Je pense qu'il y a deux options.

Étant donné que vous connaissez la taille de la vue de défilement et de la vue principale, vous ne pouvez pas savoir si la vue de défilement est au bas ou non. 

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

Alors, quand ça a frappé; vous définissez essentiellement 

[contentScrollView setScrollEnabled:NO];

et inversement pour votre tableView.

L’autre chose, qui est plus précise je pense, est d’ajouter Gesture à votre point de vue. 

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

Ainsi, lorsque vous ajoutez Gesture, vous pouvez simplement contrôler la vue active en modifiant setScrollEnabled dans respondToTapGesture.

1
Berke Atmaca

Aucune des réponses ici ne fonctionnait parfaitement pour moi. Chacun avait son propre problème nuancé (besoin de faire un balayage répété quand une des vues défilait vers le bas, ou l'indicateur de défilement ne paraissant pas correct, etc.), alors j'ai décidé de donner une autre réponse.

Ole Begemann a fait un excellent travail à ce sujet/ https://oleb.net/blog/2014/05/scrollviews-inside-scrollviews/

Bien qu’il s’agisse d’une publication ancienne, les concepts s’appliquent toujours aux API actuelles. En outre, il a mis en œuvre une approche maintenue (compatible Xcode 9) d'Object-C https://github.com/eyeem/OLEContainerScrollView

1
Andy Obusek

Je me débattais aussi avec ce problème. Il y a une solution très simple.

Dans le constructeur d'interface:

  1. créer un ViewController simple
  2. ajoutez une simple vue, ce sera notre en-tête, et le contraindra à superview
    • c'est la vue rouge sur l'exemple ci-dessous
    • J'ai ajouté 12 pixels de haut, de gauche et de droite et ai défini une hauteur fixe de 128 pixels.
  3. incorporer un PageViewController, en s'assurant qu'il est contraint à la superview, et pas à l'en-tête

 ib 

Maintenant, voici la partie amusante: pour chaque page que vous ajoutez, assurez-vous que son tableau a un décalage par rapport au haut. C'est tout. Vous pouvez le faire avec ce code, par exemple (en supposant que vous utilisez UITableViewController en tant que page):

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

Pas de défilement désordonné dans le défilement du tableau, pas de manipulation avec les délégués, pas de défilement dupliqué, comportement parfaitement naturel. Si vous ne pouvez pas voir l'en-tête, c'est probablement à cause de la couleur d'arrière-plan de tableView. Vous devez le régler sur clear pour que l'en-tête soit visible sous la tableView.

1
Mateusz

J'ai essayé la solution marquée comme étant la bonne réponse, mais cela ne fonctionnait pas correctement. L'utilisateur doit cliquer deux fois sur la vue tableau pour faire défiler l'écran. Après cela, je ne pouvais plus faire défiler l'écran en entier. Donc, je viens d'appliquer le code suivant dans viewDidLoad ():

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

Et le code ci-dessous est la mise en œuvre des actions:

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

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

Peut-être que la force brute, mais fonctionne parfaitement: à propos, j'utilise auto layout.

pour la tableView (ou collectionView ou autre), définissez une hauteur arbitraire dans le storyboard et créez un point de vente pour class. Le cas échéant (viewDidLoad () ou ...), définissez la hauteur de la tableView suffisamment grande pour que cette dernière n'ait pas besoin de défiler. (besoin de connaître le nombre de lignes à l'avance) Ensuite, seul le scrollView externe défilera correctement.

0
Brian Hong

J'ai trouvé une bibliothèque géniale MXParallaxHeader

Dans Storyboard, définissez simplement UIScrollView class sur MXScrollView, puis la magie aura lieu.

J'ai utilisé cette classe pour gérer ma UIScrollView lorsque j'intègre une vue de conteneur UIPageViewController. vous pouvez même insérer une vue d’en-tête de parallaxe pour plus de détails. 

De plus, cette bibliothèque fournit Cocoapods et Carthage

J'ai joint une image ci-dessous qui représente UIViewHierarchy . MXScrollView Hierarchy

0
Farshad Mousalou