web-dev-qa-db-fra.com

Déplacer une vue lors du défilement dans UITableView

J'ai une UIView avec une UITableView dessous:

enter image description here

Ce que j'aimerais faire, c'est que la vue au-dessus de la UITableView se déplace vers le haut lorsque l'utilisateur commence à faire défiler le tableau afin de disposer de plus d'espace pour la UITableView (et diminue lorsque vous faites défiler à nouveau).

Je sais que cela se fait normalement avec une vue d'en-tête de table, mais mon problème est que ma vue de tableau se trouve à l'intérieur d'un onglet (en fait, il s'agit d'une vue de page à défilement horizontal implémentée à l'aide de TTSliddingPageviewcontroller). Ainsi, alors que je n’ai qu’une seule UIView supérieure, il y a trois UITableViews.

Est-il possible d'accomplir cela manuellement? Ma première idée est de tout mettre dans une UIScrollView, mais selon la documentation d'Apple, il ne faut jamais placer une UITableView dans une UIScrollView car cela conduit à un comportement imprévisible.

23
pajevic

UITableView étant une sous-classe de UIScrollView, le délégué de votre vue table peut recevoir des méthodes UIScrollViewDelegate.

Dans le délégué de votre vue de table:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    static CGFloat previousOffset;
    CGRect rect = self.view.frame;
    rect.Origin.y += previousOffset - scrollView.contentOffset.y;
    previousOffset = scrollView.contentOffset.y;
    self.view.frame = rect;
}
17
NobodyNada

Solution pour Swift (fonctionne parfaitement avec l'option Bounce activée pour l'affichage par défilement):

 var oldContentOffset = CGPointZero
 let topConstraintRange = (CGFloat(120)..<CGFloat(300))

 func scrollViewDidScroll(scrollView: UIScrollView) {

    let delta =  scrollView.contentOffset.y - oldContentOffset.y

    //we compress the top view
    if delta > 0 && topConstraint.constant > topConstraintRange.start && scrollView.contentOffset.y > 0 {
        topConstraint.constant -= delta
        scrollView.contentOffset.y -= delta
    }

    //we expand the top view
    if delta < 0 && topConstraint.constant < topConstraintRange.end && scrollView.contentOffset.y < 0{
        topConstraint.constant -= delta
        scrollView.contentOffset.y -= delta
    }

    oldContentOffset = scrollView.contentOffset
 }
23
George Muntean

Swift 3 & 4:

    var oldContentOffset = CGPoint.zero
    let topConstraintRange = (CGFloat(0)..<CGFloat(140))

    func scrollViewDidScroll(_ scrollView: UIScrollView) {

        let delta =  scrollView.contentOffset.y - oldContentOffset.y

        //we compress the top view
        if delta > 0 && yourConstraint.constant > topConstraintRange.lowerBound && scrollView.contentOffset.y > 0 {
            yourConstraint.constant -= delta
            scrollView.contentOffset.y -= delta
        }

        //we expand the top view
        if delta < 0 && yourConstraint.constant < topConstraintRange.upperBound && scrollView.contentOffset.y < 0{
            yourConstraint.constant -= delta
            scrollView.contentOffset.y -= delta
        }
        oldContentOffset = scrollView.contentOffset
    }
7
Dvole

Approche plus simple et rapide 

- (void)scrollViewDidScroll:(UIScrollView *)scrollView 
{

    CGRect rect = self.view.frame;
    rect.Origin.y =  -scrollView.contentOffset.y;
    self.view.frame = rect;

}
6

J'ai ajouté des contraintes à la dernière solution pour éviter certains comportements étranges en cas de défilement rapide.

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let delta =  scrollView.contentOffset.y - oldContentOffset.y

    //we compress the top view
    if delta > 0 && topConstraint.constant > topConstraintRange.lowerBound && scrollView.contentOffset.y > 0 {
        searchHeaderTopConstraint.constant = max(topConstraintRange.lowerBound, topConstraint.constant - delta)
        scrollView.contentOffset.y -= delta
    }

    //we expand the top view
    if delta < 0 && topConstraint.constant < topConstraintRange.upperBound && scrollView.contentOffset.y < 0 {
        topConstraint.constant = min(searchHeaderTopConstraint.constant - delta, topConstraintRange.upperBound)
        scrollView.contentOffset.y -= delta
    }
    oldContentOffset = scrollView.contentOffset
}
3
Exception

Quelqu'un a demandé le code de ma solution, alors je le publie ici en guise de réponse. Le mérite de l'idée devrait toujours aller à NobodyNada.

Dans ma UITableViewController, j'implémente cette méthode de délégué:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"TableViewScrolled" object:nil userInfo:scrollUserInfo];
}

scrollUserInfo est une NSDictionary où je mets ma UITableView pour la transmettre avec la notification (je le fais dans viewDidLoad alors je ne dois le faire qu'une fois):

scrollUserInfo = [NSDictionary dictionaryWithObject:self.tableView forKey:@"scrollView"];

Maintenant, dans le contrôleur de vue qui a la vue que je veux sortir de l'écran en faisant défiler je fais ceci dans viewDidLoad:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleScroll:) name:@"TableViewScrolled" object:nil];

Et enfin j'ai la méthode:

- (void)handleScroll:(NSNotification *)notification {
    UIScrollView *scrollView = [notification.userInfo valueForKey:@"scrollView"];
    CGFloat currentOffset = scrollView.contentOffset.y;
    CGFloat height = scrollView.frame.size.height;
    CGFloat distanceFromBottom = scrollView.contentSize.height - currentOffset;

    if (previousOffset < currentOffset && distanceFromBottom > height) {
        if (currentOffset > viewHeight)
            currentOffset = viewHeight;
        self.topVerticalConstraint.constant += previousOffset - currentOffset;
        previousOffset = currentOffset;
    }
    else {
        if (previousOffset > currentOffset) {
            if (currentOffset < 0)
                currentOffset = 0;
            self.topVerticalConstraint.constant += previousOffset - currentOffset;
            previousOffset = currentOffset;
        }
    }
}

previousOffset est une variable d'instance CGFloat previousOffset;.topVerticalConstraint est une NSLayoutConstraint définie en tant que IBOutlet. Il va du sommet de la vue au sommet de son aperçu et la valeur initiale est 0.

Ce n'est pas parfait Par exemple, si l'utilisateur fait défiler très agressivement le mouvement de la vue peut devenir un peu saccadé. Le problème est pire pour les vues de grande taille; si la vue est suffisamment petite, il n'y a pas de problème.

3
pajevic

Pour créer comme cette animation, 

 enter image description here

lazy var redView: UIView = {

    let view = UIView(frame: CGRect(x: 0, y: 0, width: 
    self.view.frame.width, height: 100))
    view.backgroundColor = .red
    return view
}()

var pageMenu: CAPSPageMenu?

override func viewDidLoad() {
     super.viewDidLoad()
     self.view.addSubview(redView)

    let rect = CGRect(x: 0, y: self.redView.frame.maxY, width: self.view.bounds.size.width, height:(self.view.bounds.size.height - (self.redView.frame.maxY)))
    pageMenu?.view.frame = rect
    self.view.addSubview(pageMenu!.view)

  }

override func scrollViewDidScroll(_ scrollView: UIScrollView) {
  let offset = scrollView.contentOffset.y

    if(offset > 100){
        self.redView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: 0)
    }else{
        self.redView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: 100 - offset)
    }

    let rect = CGRect(x: 0, y: self.redView.frame.maxY, width: self.view.bounds.size.width, height:(self.view.bounds.size.height - (self.redView.frame.maxY)))
    pageMenu?.view.frame = rect
}

vous devez changer pageMenu.view avec votre collectionView/tableView

1
Yassine En

Je connais ce post très vieux. J'ai essayé les solutions ci-dessus, mais aucune n'a fonctionné pour moi, mais j'espère que cela pourra vous aider. Ce scénario est assez courant, car Apple a suggéré de ne pas utiliser TableViewController dans un ScrollView, car le compilateur sera confus quant au choix de la personne à qui répondre car il recevra deux appels de délégués, l'un de ScrollViewDelegate et l'autre de UITableViewDelegate. 

Au lieu de cela, nous pouvons utiliser ScrollViewDelegate et désactiver UITableViewScrolling.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    CGFloat currentOffSetY = scrollView.contentOffset.y;
    CGFloat diffOffset = self.lastContentOffset - currentOffSetY;

    self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width, 400 + [self.tableView contentSize].height);

    if (self.lastContentOffset < scrollView.contentOffset.y) {
        tableView.frame = CGRectMake(tableView.frame.Origin.x, tableView.frame.Origin.y , tableView.frame.size.width,  tableView.size.height - diffOffset);
    }

    if (self.lastContentOffset > scrollView.contentOffset.y) {
        tableView.frame = CGRectMake(tableView.frame.Origin.x, tableViewframe.Origin.y, tableViewframe.size.width,  tableView.frame.size.height + diffOffset);
    }

    self.lastContentOffset = currentOffSetY;
}

LastContentOffset est ici défini par CGFloat en tant que propriété

La hiérarchie des vues est la suivante: ViewController -> View contient ScrollView (dont la méthode déléguée est définie ci-dessus) -> Contain TableView.

En utilisant le code ci-dessus, nous augmentons et diminuons manuellement la hauteur de la vue tableau avec la taille du contenu de ScrollView.

N'oubliez pas de désactiver le défilement de TableView.

0
Rishabh Sanghvi