如何构建一个包含图像的视差样式/可拉伸的表格视图标题,而不会出现丑陋的黑客攻击?
Posted
技术标签:
【中文标题】如何构建一个包含图像的视差样式/可拉伸的表格视图标题,而不会出现丑陋的黑客攻击?【英文标题】:How do I build a parallax-style/stretchy table view header with an image in it without ugly hacks? 【发布时间】:2015-01-03 06:01:57 【问题描述】:我一直在尝试构建一个由图像组成的视差样式表视图标题,类似于 Yahoo News Digest 应用程序,或者在 Maps.app 中查看业务时。 (当您用橡皮筋固定表格时,图像高度会增加,而向下滚动时,图像的滚动速度会稍慢)。
这是由APParallaxHeader提供的演示视频:
https://www.youtube.com/watch?v=7-JMdapWXGU
我能找到的最好的教程是this tutorial,它基本上包括将图像视图添加为表格视图的子视图。虽然这主要是可行的,但作为子视图添加到 UITableView
是相当无证的,并且在我的测试中似乎不适用于自动布局,因此旋转不能很好地发挥作用。
我在上面链接的库 APParallaxHeader 似乎可以工作,但它的实现真的很混乱,如果我没记错的话,似乎很混乱?
有没有一种我完全忽略的简单方法?
【问题讨论】:
在我看来,您可以使用表头视图来做到这一点。您需要在滚动时调整其高度,并在其中拉伸图像视图(修改其约束或应用变换)。 @rdelmar 你确定吗?当表格视图本身被拉下时,您将如何创建橡皮筋高度效果? @DougSmith 高度和缩放量的控制参数是表格的 contentOffset.y。该值的变化与反弹非常接近。听起来像是一个有趣的练习。 (我同意你的观点,它看起来应该很简单)。 【参考方案1】:在考虑了这个问题之后,我认为复制该外观的最佳方法是使用滚动视图,该滚动视图包含一个图像视图,该视图位于后面(在 z 方向上)并在下方(在 y 方向上)延伸表视图的顶部。在我所做的测试中,我为表格视图提供了一个 100 磅高的标题(在 IB 中),并且具有清晰的背景颜色(表格也需要清晰的背景颜色)。滚动视图和表格视图都固定在控制器主视图的侧面和顶部布局指南(控制器嵌入在导航控制器中,该控制器设置为使其视图不位于顶部栏下方)。表格视图也固定在视图的底部,并且滚动视图被赋予了 200 的固定高度。我给滚动视图的初始偏移量为 50 点,这样当您开始向下拉表格时,滚动视图可以从顶部将更多内容滚动到视图中,同时在底部显示更多内容(滚动视图的偏移量以表格视图偏移量的 1/2 速率移动)。一旦表格视图的偏移量达到 -50,我就停止更改滚动视图的偏移量,并开始缩放。
#define ZOOMPOINT 50
@interface ViewController () <UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate>
@property (weak, nonatomic) IBOutlet UIScrollView *sv;
@property(weak,nonatomic) IBOutlet UITableView *tableView;
@property (strong,nonatomic) UIImageView *iv;
@end
@implementation ViewController
-(void)viewDidLoad
[super viewDidLoad];
self.sv.minimumZoomScale = 1.0;
self.sv.maximumZoomScale = 2.0;
self.sv.delegate = self;
self.iv = [UIImageView new];
self.iv.contentMode = UIViewContentModeScaleAspectFill;
self.iv.translatesAutoresizingMaskIntoConstraints = NO;
-(void)viewDidLayoutSubviews
[self.iv removeFromSuperview];
[self.sv addSubview:self.iv];
[self.sv addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[iv(==width)]|" options:0 metrics:@@"width":@(self.tableView.frame.size.width) views:@@"iv":self.iv]];
[self.sv addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[iv(==250)]|" options:0 metrics:nil views:@@"iv":self.iv]];
self.iv.image = [UIImage imageNamed:@"img.jpg"]; // the image I was using was 500 x 500
self.sv.contentOffset = CGPointMake(0, ZOOMPOINT);
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
if ([scrollView isEqual:self.sv])
return self.iv;
else
return nil;
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
if (scrollView != self.sv)
if (scrollView.contentOffset.y < -ZOOMPOINT)
[self.sv setZoomScale:(scrollView.contentOffset.y + ZOOMPOINT)/-100 + 1]; // the -100 is arbitrary, change to affect the sensitivity of the zooming
else
self.sv.contentOffset = CGPointMake(0, ZOOMPOINT + scrollView.contentOffset.y/2.0);
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
return 20;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
cell.textLabel.text = [NSString stringWithFormat:@"Cell %ld", (long)indexPath.row];
return cell;
我在这里上传了这个项目的副本,http://jmp.sh/LRKF0nM
【讨论】:
干得好!布局约束(我懒惰地省略了)处理 OP 对旋转的要求。我的行为类似,但是在滚动时,它将图像滚动而不是缩小(因为它在表格内)。但我更喜欢依赖较少常量的方法。 jumpshare.com/v/aE6JyBjLLQJ0LpJjVQVv?b=FCoUUjKBOTi3tknKEgsf 很好。但是对于您的图像,最好在缩放之前让它扩展整个高度,以改变图像视图的高度通常是如何操作的。此外,当您在表格视图上拉得太远并且标题不断缩放而不改变其高度时,它显然看起来很奇怪。这是可以避免的吗? @DougSmith,当然,您可以调整它在缩放之前滚动的距离。至于第二点,我相信你也可以通过弄乱参数来改变它。您已经获得了滚动视图的高度、标题视图的高度以及滚动视图的初始偏移量。【参考方案2】:我想我会抛出另一个不使用单独滚动视图的想法。我认为它的扩展方式会更好一些。因此,在这次尝试中,我只是将图像视图添加为主视图的子视图,并将其放置为图像视图的 1/2 位于标题顶部(视图外)上方,与标题下方(最初被表格行隐藏)。下拉表格时,视图以下拉速度的一半向下移动(通过调整约束),因此图像的顶部和底部一起进入视图,然后从那里,我通过使用进行扩展变换。
#import "ViewController.h"
#define ZOOMPOINT -60
@interface ViewController () <UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate>
@property(weak,nonatomic) IBOutlet UITableView *tableView;
@property (weak, nonatomic) IBOutlet UIView *tableHeader;
@property (strong,nonatomic) UIImageView *iv;
@property (strong,nonatomic) NSLayoutConstraint *topCon;
@end
@implementation ViewController
-(void)viewDidLoad
[super viewDidLoad];
self.iv = [UIImageView new];
self.iv.contentMode = UIViewContentModeScaleToFill; //UIViewContentModeScaleAspectFill;
self.iv.translatesAutoresizingMaskIntoConstraints = NO;
self.edgesForExtendedLayout = UIRectEdgeNone;
-(void)viewDidAppear:(BOOL)animated
[super viewDidAppear:animated];
[self.view addSubview:self.iv];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|[iv]|" options:0 metrics:nil views:@@"iv":self.iv]];
self.topCon = [NSLayoutConstraint constraintWithItem:self.iv attribute:NSLayoutAttributeTop relatedBy:0 toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:ZOOMPOINT/2.0];
[self.iv addConstraint:[NSLayoutConstraint constraintWithItem:self.iv attribute:NSLayoutAttributeHeight relatedBy:0 toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:self.tableHeader.frame.size.height - ZOOMPOINT*1.5]];
[self.view addConstraint:self.topCon];
[self.view layoutIfNeeded];
self.iv.image = [UIImage imageNamed:@"img.jpg"];
[self.view sendSubviewToBack:self.iv];
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
if (scrollView.contentOffset.y < 0 && scrollView.contentOffset.y > ZOOMPOINT)
self.topCon.constant = ZOOMPOINT/2.0 - scrollView.contentOffset.y/2.0;
else if (scrollView.contentOffset.y <= ZOOMPOINT)
self.iv.transform = CGAffineTransformMakeScale(1 - (scrollView.contentOffset.y - ZOOMPOINT)/200, 1 - (scrollView.contentOffset.y - ZOOMPOINT)/200);
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
return 20;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
cell.textLabel.text = [NSString stringWithFormat:@"Cell %ld", (long)indexPath.row];
return cell;
项目可以在这里找到,http://jmp.sh/7PXzISZ
【讨论】:
【参考方案3】:您好,这是我关于该主题的中等帖子:
https://medium.com/@jeremysh/creating-a-sticky-header-for-a-uitableview-40af71653b55#.hi79wgtsd
【讨论】:
【参考方案4】:这里只是重复,但是如果标题的自然框架是frame
,并且您已经设置了表格的滚动视图委托,那么缩放的框架将非常类似于:
// in scrollViewDidScroll:
// when the table view is scrolled beyond the header, contentOffset.y is negative
CGFloat headerAspect = frame.size.width / frame.size.height;
CGFloat offsetY = tableView.contentOffset.y;
CGFloat offsetX = offsetY * headerAspect;
// this will enlarge frame since offsets are < 0
frame = CGInsetRect(frame, offsetY, offsetX);
// slide it down to keep the top at the top of the header
frame = CGRectOffset(frame, 0, offsetY / 2.0);
将此与将图像视图上的 contentMode 设置为 UIViewContentModeScaleToFill
相结合,这应该是一个不错的开始。
【讨论】:
我试过这段代码,但它似乎根本不起作用。我使用标题视图的主要问题(我可能对此有误,但它似乎也出现在此代码中)是当您下拉表格视图“橡皮筋”时,表格视图及其标题被物理翻译向下也是如此,所以标题不必在表格视图的边界之外绘制吗? @DougSmith,当滚动视图滚动时,它的子视图相对于它们的父视图(滚动视图)保持固定。子视图确实相对于滚动视图的超级视图移动,但这并不重要。让我多考虑一下。我可能会尝试在今天或明天真正实现这一点。如果我这样做,将在此处更新答案。 哦,好吧,这是有道理的。如果您有时间,我将不胜感激! @DougSmith - 在下面查看【参考方案5】:好的,这是一个有助于构建和试用的答案。
我发现操作表格的实际标题视图的框架太难了,所以我在行上方的表格中添加了一个子视图。为了使该视图显示为常规表格标题,我为表格提供了一个固定大小、透明颜色的标题视图。
主要思路和我上面回答的一样:使用表格的内容偏移量作为修改图像视图框架的参数,使用imageView的内容模式(修正为UIViewContentModeScaleAspectFill)来提供随着框架变化的缩放效果。
这是整个视图控制器。这是从故事板构建的,其中视图控制器位于导航控制器内部。它只不过是一个填充其视图的表格视图,其中包含数据源和委托集。
#import "ViewController.h"
// how much of the image to show when the table is un-scrolled
#define HEADER_HEIGHT (100.0)
// the height of the image scaled down to fit in the header. the real image can/should be taller than this
// i tested this with a 600x400 image
#define SCALED_IMAGE_HEIGHT (200.0)
// zoom image up to this offset
#define MAX_ZOOM (150.0)
@interface ViewController () <UITableViewDataSource, UITableViewDelegate>
@property(weak,nonatomic) IBOutlet UITableView *tableView;
@end
@implementation ViewController
- (void)viewWillAppear:(BOOL)animated
[super viewWillAppear:animated];
// build the header in view will appear after other layout constraints are applied
UIImageView *headerView = (UIImageView *)[self.tableView viewWithTag:99];
if (!headerView)
headerView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"landscape.png"]];
headerView.tag = 99;
headerView.contentMode = UIViewContentModeScaleAspectFill;
headerView.clipsToBounds = YES;
headerView.frame = CGRectMake(0, HEADER_HEIGHT, self.view.bounds.size.width, SCALED_IMAGE_HEIGHT);
[self.tableView addSubview:headerView];
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
CGFloat offsetY = -self.tableView.contentOffset.y - 64;
// minus 64 is kind of a bummer here. this calc wants the offset to be 0
// when no scrolling has happened. for some reason my table view starts at -64
CGFloat clamped = MIN(MAX(offsetY, 0), MAX_ZOOM);
CGFloat origin = -HEADER_HEIGHT - clamped;
CGFloat height = SCALED_IMAGE_HEIGHT + clamped;
UIImageView *headerView = (UIImageView *)[self.tableView viewWithTag:99];
CGRect frame = headerView.frame;
frame.origin.y = origin;
frame.size.height = height;
headerView.frame = frame;
// this is a trick to make the view above the header visible: make the table header a clear UIView
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.bounds.size.width, HEADER_HEIGHT)];
view.backgroundColor = [UIColor clearColor];
return view;
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
return HEADER_HEIGHT;
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
return 30;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
cell.textLabel.text = [NSString stringWithFormat:@"Cell %ld", indexPath.row];
return cell;
@end
【讨论】:
嗯,我想我明白你在做什么了,但这是否适用于旋转和导航栏?这是我遇到的主要问题。 导航栏 - 是的,我也是这样建造的。旋转也可以,但您需要一张在两个方向上看起来都不错的图像(即,对于横向来说足够宽,但在纵向的顶部和底部有足够有用的内容)。 它是如何轮换工作的?您将高度距离硬编码为 64,这仅适用于纵向。状态栏高度 + 导航栏高度的 64 个结果。横向隐藏状态栏,导航栏高度不同,所以这个数字不再有效。 你能上传项目吗? 啊,你让它与顶部对齐,而不是 topLayoutGuide/nav 栏。这会减少很多图像。有没有办法避免这种情况?它也不适用于横向。以上是关于如何构建一个包含图像的视差样式/可拉伸的表格视图标题,而不会出现丑陋的黑客攻击?的主要内容,如果未能解决你的问题,请参考以下文章