J'ai une UIView
avec une UITableView
dessous:
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 UITableView
s.
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.
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;
}
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
}
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
}
Approche plus simple et rapide
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGRect rect = self.view.frame;
rect.Origin.y = -scrollView.contentOffset.y;
self.view.frame = rect;
}
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
}
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.
Pour créer comme cette animation,
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
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.