ios 在 UICollectionView 上动态设置布局会导致莫名其妙的 contentOffset 变化
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13780138/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Dynamically setting layout on UICollectionView causes inexplicable contentOffset change
提问by Timothy Moose
According to Apple's documentation (and touted at WWDC 2012), it is possible to set the layout on UICollectionView
dynamically and even animate the changes:
根据 Apple 的文档(并在 WWDC 2012 上吹捧),可以UICollectionView
动态设置布局,甚至可以为更改设置动画:
You normally specify a layout object when creating a collection view but you can also change the layout of a collection view dynamically. The layout object is stored in the collectionViewLayout property. Setting this property directly updates the layout immediately, without animating the changes. If you want to animate the changes, you must call the setCollectionViewLayout:animated: method instead.
您通常在创建集合视图时指定布局对象,但您也可以动态更改集合视图的布局。布局对象存储在 collectionViewLayout 属性中。直接设置此属性会立即更新布局,而不会对更改进行动画处理。如果要为更改设置动画,则必须改为调用 setCollectionViewLayout:animated: 方法。
However, in practice, I've found that UICollectionView
makes inexplicable and even invalid changes to the contentOffset
, causing cells to move incorrectly, making the feature virtually unusable. To illustrate the problem, I put together the following sample code that can be attached to a default collection view controller dropped into a storyboard:
但是,在实践中,我发现 对UICollectionView
进行了莫名其妙甚至无效的更改contentOffset
,导致单元格移动不正确,使该功能几乎无法使用。为了说明这个问题,我整理了以下示例代码,这些代码可以附加到放入故事板的默认集合视图控制器上:
#import <UIKit/UIKit.h>
@interface MyCollectionViewController : UICollectionViewController
@end
@implementation MyCollectionViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"CELL"];
self.collectionView.collectionViewLayout = [[UICollectionViewFlowLayout alloc] init];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 1;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:@"CELL" forIndexPath:indexPath];
cell.backgroundColor = [UIColor whiteColor];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"contentOffset=(%f, %f)", self.collectionView.contentOffset.x, self.collectionView.contentOffset.y);
[self.collectionView setCollectionViewLayout:[[UICollectionViewFlowLayout alloc] init] animated:YES];
NSLog(@"contentOffset=(%f, %f)", self.collectionView.contentOffset.x, self.collectionView.contentOffset.y);
}
@end
The controller sets a default UICollectionViewFlowLayout
in viewDidLoad
and displays a single cell on-screen. When the cells is selected, the controller creates another default UICollectionViewFlowLayout
and sets it on the collection view with the animated:YES
flag. The expected behavior is that the cell does not move. The actual behavior, however, is that the cell scroll off-screen, at which point it is not even possible to scroll the cell back on-screen.
所述控制器设置一个默认的UICollectionViewFlowLayout
在viewDidLoad
与显示屏幕上的一个单元格。当单元格被选中时,控制器会创建另一个默认值UICollectionViewFlowLayout
并将其设置在带有animated:YES
标志的集合视图上。预期的行为是单元格不移动。然而,实际行为是单元格滚动到屏幕外,此时甚至不可能将单元格滚动回屏幕。
Looking at the console log reveals that the contentOffset has inexplicably changed (in my project, from (0, 0) to (0, 205)). I posted a solution for the solution for the non-animated case(i.e. animated:NO
), but since I need animation, I'm very interested to know if anyone has a solution or workaround for the animated case.
查看控制台日志显示 contentOffset 莫名其妙地发生了变化(在我的项目中,从 (0, 0) 到 (0, 205))。我发布了非动画案例( i.e. animated:NO
)的解决方案的解决方案,但由于我需要动画,我很想知道是否有人有动画案例的解决方案或解决方法。
As a side-note, I've tested custom layouts and get the same behavior.
作为旁注,我已经测试了自定义布局并获得了相同的行为。
采纳答案by tassinari
I have been pulling my hair out over this for days and have found a solution for my situation that may help.
In my case I have a collapsing photo layout like in the photos app on the ipad. It shows albums with the photos on top of each other and when you tap an album it expands the photos. So what I have is two separate UICollectionViewLayouts and am toggling between them with [self.collectionView setCollectionViewLayout:myLayout animated:YES]
I was having your exact problem with the cells jumping before animation and realized it was the contentOffset
. I tried everything with the contentOffset
but it still jumped during animation. tyler's solution above worked but it was still messing with the animation.
几天来,我一直在为此烦恼,并为我的情况找到了可能有帮助的解决方案。就我而言,我有一个折叠的照片布局,就像 ipad 上的照片应用程序一样。它显示带有照片的相册,当您点击相册时,它会展开照片。所以我拥有的是两个单独的 UICollectionViewLayouts 并且我在它们之间切换,[self.collectionView setCollectionViewLayout:myLayout animated:YES]
我遇到了你在动画之前跳跃的单元格的确切问题,并意识到它是contentOffset
. 我尝试了一切,contentOffset
但它在动画过程中仍然跳跃。上面泰勒的解决方案有效,但它仍然与动画混乱。
Then I noticed that it happens only when there were a few albums on the screen, not enough to fill the screen. My layout overrides -(CGSize)collectionViewContentSize
as recommended. When there are only a few albums the collection view content size is less than the views content size. That's causing the jump when I toggle between the collection layouts.
然后我注意到只有当屏幕上有几张专辑时才会发生这种情况,不足以填满屏幕。我的布局-(CGSize)collectionViewContentSize
按照推荐覆盖。当只有几个相册时,集合视图内容大小小于视图内容大小。当我在集合布局之间切换时,这会导致跳转。
So I set a property on my layouts called minHeight and set it to the collection views parent's height. Then I check the height before I return in -(CGSize)collectionViewContentSize
I ensure the height is >= the minimum height.
因此,我在布局上设置了一个名为 minHeight 的属性,并将其设置为集合视图父级的高度。然后我在返回之前检查高度,-(CGSize)collectionViewContentSize
确保高度 >= 最小高度。
Not a true solution but it's working fine now. I would try setting the contentSize
of your collection view to be at least the length of it's containing view.
不是一个真正的解决方案,但它现在工作正常。我会尝试将contentSize
您的集合视图的长度设置为至少它包含视图的长度。
edit:Manicaesar added an easy workaround if you inherit from UICollectionViewFlowLayout:
编辑:如果您从 UICollectionViewFlowLayout 继承,Manicaesar 添加了一个简单的解决方法:
-(CGSize)collectionViewContentSize { //Workaround
CGSize superSize = [super collectionViewContentSize];
CGRect frame = self.collectionView.frame;
return CGSizeMake(fmaxf(superSize.width, CGRectGetWidth(frame)), fmaxf(superSize.height, CGRectGetHeight(frame)));
}
回答by cdemiris99
UICollectionViewLayout
contains the overridable method targetContentOffsetForProposedContentOffset:
which allows you to provide the proper content offset during a change of layout, and this will animate correctly. This is available in iOS 7.0 and above
UICollectionViewLayout
包含可覆盖的方法targetContentOffsetForProposedContentOffset:
,它允许您在布局更改期间提供适当的内容偏移量,这将正确设置动画。这在 iOS 7.0 及更高版本中可用
回答by tyler
This issue bit me as well and it seems to be a bug in the transition code. From what I can tell it tries to focus on the cell that was closest to the center of the pre-transition view layout. However, if there doesn't happen to be a cell at the center of the view pre-transition then it still tries to center where the cell would be post-transition. This is very clear if you set alwaysBounceVertical/Horizontal to YES, load the view with a single cell and then perform a layout transition.
这个问题也困扰着我,它似乎是转换代码中的一个错误。据我所知,它试图关注最接近过渡前视图布局中心的单元格。但是,如果在转换前视图的中心没有碰巧一个单元格,那么它仍然会尝试将单元格置于转换后的中心位置。如果您将 alwaysBounceVertical/Horizontal 设置为 YES,使用单个单元格加载视图,然后执行布局转换,这将非常清楚。
I was able to get around this by explicitly telling the collection to focus on a specific cell (the first cell visible cell, in this example) after triggering the layout update.
我能够通过在触发布局更新后明确告诉集合关注特定单元格(在本例中为第一个单元格可见单元格)来解决这个问题。
[self.collectionView setCollectionViewLayout:[self generateNextLayout] animated:YES];
// scroll to the first visible cell
if ( 0 < self.collectionView.indexPathsForVisibleItems.count ) {
NSIndexPath *firstVisibleIdx = [[self.collectionView indexPathsForVisibleItems] objectAtIndex:0];
[self.collectionView scrollToItemAtIndexPath:firstVisibleIdx atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES];
}
回答by Timothy Moose
Jumping in with a late answer to my own question.
对我自己的问题给出了一个迟到的答案。
The TLLayoutTransitioninglibrary provides a great solution to this problem by re-tasking iOS7s interactive transitioning APIs to do non-interactive, layout to layout transitions. It effectively provides an alternative to setCollectionViewLayout
, solving the content offset issue and adding several features:
该TLLayoutTransitioning库提供了重新布置任务iOS7s互动转变的API,这个问题很好的解决方案做非交互式,版面布局的转变。它有效地提供了一种替代方案setCollectionViewLayout
,解决了内容偏移问题并添加了几个功能:
- Animation duration
- 30+ easing curves (courtesy of Warren Moore's AHEasing library)
- Multiple content offset modes
- 动画持续时间
- 30 多个缓动曲线(由 Warren Moore 的AHEasing 库提供)
- 多种内容偏移模式
Custom easing curves can be defined as AHEasingFunction
functions. The final content offset can be specified in terms of one or more index paths with Minimal, Center, Top, Left, Bottom or Right placement options.
自定义缓动曲线可以定义为AHEasingFunction
函数。可以根据一个或多个索引路径指定最终内容偏移量,并带有最小、居中、顶部、左侧、底部或右侧放置选项。
To see what I mean, try running the Resize demoin the Examples workspace and playing around with the options.
要了解我的意思,请尝试在示例工作区中运行Resize 演示并尝试使用选项。
The usage is like this. First, configure your view controller to return an instance of TLTransitionLayout
:
用法是这样的。首先,配置您的视图控制器以返回一个实例TLTransitionLayout
:
- (UICollectionViewTransitionLayout *)collectionView:(UICollectionView *)collectionView transitionLayoutForOldLayout:(UICollectionViewLayout *)fromLayout newLayout:(UICollectionViewLayout *)toLayout
{
return [[TLTransitionLayout alloc] initWithCurrentLayout:fromLayout nextLayout:toLayout];
}
Then, instead of calling setCollectionViewLayout
, call transitionToCollectionViewLayout:toLayout
defined in the UICollectionView-TLLayoutTransitioning
category:
然后,而不是调用setCollectionViewLayout
,调用transitionToCollectionViewLayout:toLayout
在UICollectionView-TLLayoutTransitioning
类别中定义:
UICollectionViewLayout *toLayout = ...; // the layout to transition to
CGFloat duration = 2.0;
AHEasingFunction easing = QuarticEaseInOut;
TLTransitionLayout *layout = (TLTransitionLayout *)[collectionView transitionToCollectionViewLayout:toLayout duration:duration easing:easing completion:nil];
This call initiates an interactive transition and, internally, a CADisplayLink
callback that drives the transition progress with the specified duration and easing function.
此调用启动一个交互式过渡,并在内部启动一个CADisplayLink
回调,该回调以指定的持续时间和缓动函数驱动过渡进度。
The next step is to specify a final content offset. You can specify any arbitrary value, but the toContentOffsetForLayout
method defined in UICollectionView-TLLayoutTransitioning
provides an elegant way to calculate content offsets relative to one or more index paths. For example, in order to have a specific cell to end up as close to the center of the collection view as possible, make the following call immediately after transitionToCollectionViewLayout
:
下一步是指定最终的内容偏移量。您可以指定任意值,但 中toContentOffsetForLayout
定义的方法UICollectionView-TLLayoutTransitioning
提供了一种计算相对于一个或多个索引路径的内容偏移量的优雅方法。例如,为了让特定单元格尽可能靠近集合视图的中心,请在 之后立即进行以下调用transitionToCollectionViewLayout
:
NSIndexPath *indexPath = ...; // the index path of the cell to center
TLTransitionLayoutIndexPathPlacement placement = TLTransitionLayoutIndexPathPlacementCenter;
CGPoint toOffset = [collectionView toContentOffsetForLayout:layout indexPaths:@[indexPath] placement:placement];
layout.toContentOffset = toOffset;
回答by Fattie
2019 actual solution
2019年实际解决方案
Say you have a number of layouts for your "Cars" view.
假设您的“汽车”视图有多种布局。
Let's say you have three.
假设你有三个。
CarsLayout1: UICollectionViewLayout { ...
CarsLayout2: UICollectionViewLayout { ...
CarsLayout3: UICollectionViewLayout { ...
It will jumpwhen you animate between layouts.
当您在布局之间设置动画时,它会跳跃。
It's just an undeniable mistake by Apple. It jumps when you animate, without question.
这只是苹果不可否认的错误。毫无疑问,它会在您制作动画时跳跃。
The fix is this:
修复方法是这样的:
You must have a global float, and, the following base class:
您必须有一个全局浮点数,以及以下基类:
var avoidApplefworupCarsLayouts: CGPoint? = nil
class FixerForCarsLayouts: UICollectionViewLayout {
override func prepareForTransition(from oldLayout: UICollectionViewLayout) {
avoidApplefworupCarsLayouts = collectionView?.contentOffset
}
override func targetContentOffset(
forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
if avoidApplefworupCarsLayouts != nil {
return avoidApplefworupCarsLayouts!
}
return super.targetContentOffset(forProposedContentOffset: proposedContentOffset)
}
}
.
.
So here are the three layouts for your "Cars" screen:
所以这里是“汽车”屏幕的三种布局:
CarsLayout1: FixerForCarsLayouts { ...
CarsLayout2: FixerForCarsLayouts { ...
CarsLayout3: FixerForCarsLayouts { ...
That's it.
就是这样。
It now works.
它现在可以工作了。
Apple, thanks for creating another mystery problem that should never have existed.
Apple,感谢您创造了另一个本不应该存在的神秘问题。
Footnotes
脚注
- Incredibly obscurely, you could have different "sets" of layouts (for Cars, Dogs, Houses, etc.), which could (conceivably) collide. For this reason, have a global and a base class as above for each "set".
- 令人难以置信的是,您可能有不同的“组”布局(用于汽车、狗、房屋等),它们可能(可以想象)发生冲突。出于这个原因,每个“集合”都有一个如上所述的全局和基类。
2. This was invented by passing user @Isaacliu, above, many years ago. Thanks.
2. 这是多年前通过用户@Isaacliu 发明的。谢谢。
- A detail, FWIW in Isaacliu's code fragment,
finalizeLayoutTransition
is added. In fact it's not necessary logically.
- 添加了一个细节,Isaacliu 的代码片段中的 FWIW
finalizeLayoutTransition
。事实上,逻辑上没有必要。
The fact is, until Apple change how it works, every time you animate between collection view layouts, you do have to do this. That's life!
事实是,除非 Apple 改变它的工作方式,否则每次在集合视图布局之间设置动画时,都必须这样做。这就是生活!
回答by nikolsky
Easy.
简单。
Animate your new layout and collectionView's contentOffset in the same animation block.
在同一个动画块中为您的新布局和 collectionView 的 contentOffset 设置动画。
[UIView animateWithDuration:0.3 animations:^{
[self.collectionView setCollectionViewLayout:self.someLayout animated:YES completion:nil];
[self.collectionView setContentOffset:CGPointMake(0, -64)];
} completion:nil];
It will keep self.collectionView.contentOffset
constant.
它会保持self.collectionView.contentOffset
不变。
回答by Isaac liu
If you are simply looking for the content offset to not change when transition from layouts, you can creating a custom layout and override a couple methods to keep track of the old contentOffset and reuse it:
如果您只是希望在从布局转换时不更改内容偏移量,您可以创建自定义布局并覆盖几个方法来跟踪旧的 contentOffset 并重用它:
@interface CustomLayout ()
@property (nonatomic) NSValue *previousContentOffset;
@end
@implementation CustomLayout
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
{
CGPoint previousContentOffset = [self.previousContentOffset CGPointValue];
CGPoint superContentOffset = [super targetContentOffsetForProposedContentOffset:proposedContentOffset];
return self.previousContentOffset != nil ? previousContentOffset : superContentOffset ;
}
- (void)prepareForTransitionFromLayout:(UICollectionViewLayout *)oldLayout
{
self.previousContentOffset = [NSValue valueWithCGPoint:self.collectionView.contentOffset];
return [super prepareForTransitionFromLayout:oldLayout];
}
- (void)finalizeLayoutTransition
{
self.previousContentOffset = nil;
return [super finalizeLayoutTransition];
}
@end
All this is doing is saving the previous content offset before the layout transition in prepareForTransitionFromLayout
, overwriting the new content offset in targetContentOffsetForProposedContentOffset
, and clearing it in finalizeLayoutTransition
. Pretty straightforward
所有这些都是在布局转换之前保存之前的内容偏移量prepareForTransitionFromLayout
,覆盖新的内容偏移量targetContentOffsetForProposedContentOffset
,并清除它finalizeLayoutTransition
。很简单
回答by Tommy
If it helps add to the body of experience: I encountered this problem persistently regardless of the size of my content, whether I had set a content inset, or any other obvious factor. So my workaround was somewhat drastic. First I subclassed UICollectionView and added to combat inappropriate content offset setting:
如果它有助于增加体验:无论我的内容大小、是否设置了内容插入或任何其他明显因素,我都一直遇到这个问题。所以我的解决方法有点激烈。首先,我将 UICollectionView 子类化并添加以对抗不适当的内容偏移设置:
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
{
if(_declineContentOffset) return;
[super setContentOffset:contentOffset];
}
- (void)setContentOffset:(CGPoint)contentOffset
{
if(_declineContentOffset) return;
[super setContentOffset:contentOffset];
}
- (void)setCollectionViewLayout:(UICollectionViewLayout *)layout animated:(BOOL)animated
{
_declineContentOffset ++;
[super setCollectionViewLayout:layout animated:animated];
_declineContentOffset --;
}
- (void)setContentSize:(CGSize)contentSize
{
_declineContentOffset ++;
[super setContentSize:contentSize];
_declineContentOffset --;
}
I'm not proud of it but the only workable solution seems to be completely to reject any attempt by the collection view to set its own content offset resulting from a call to setCollectionViewLayout:animated:
. Empirically it looks like this change occurs directly in the immediate call, which obviously isn't guaranteed by the interface or the documentation but makes sense from a Core Animation point of view so I'm perhaps only 50% uncomfortable with the assumption.
我并不为此感到自豪,但唯一可行的解决方案似乎是完全拒绝集合视图通过调用setCollectionViewLayout:animated:
. 从经验上看,这种变化似乎直接发生在立即调用中,这显然不是由界面或文档保证的,但从核心动画的角度来看是有道理的,所以我可能只有 50% 的人对这个假设感到不舒服。
However there was a second issue: UICollectionView was now adding a little jump to those views that were staying in the same place upon a new collection view layout — pushing them down about 240 points and then animating them back to the original position. I'm unclear why but I modified my code to deal with it nevertheless by severing the CAAnimation
s that had been added to any cells that, actually, weren't moving:
然而,还有第二个问题:UICollectionView 现在在新的集合视图布局上为那些停留在同一位置的视图添加一点跳跃——将它们向下推大约 240 点,然后将它们动画化回原始位置。我不清楚为什么,但我修改了我的代码以通过切断CAAnimation
已添加到任何实际上没有移动的单元格来处理它:
- (void)setCollectionViewLayout:(UICollectionViewLayout *)layout animated:(BOOL)animated
{
// collect up the positions of all existing subviews
NSMutableDictionary *positionsByViews = [NSMutableDictionary dictionary];
for(UIView *view in [self subviews])
{
positionsByViews[[NSValue valueWithNonretainedObject:view]] = [NSValue valueWithCGPoint:[[view layer] position]];
}
// apply the new layout, declining to allow the content offset to change
_declineContentOffset ++;
[super setCollectionViewLayout:layout animated:animated];
_declineContentOffset --;
// run through the subviews again...
for(UIView *view in [self subviews])
{
// if UIKit has inexplicably applied animations to these views to move them back to where
// they were in the first place, remove those animations
CABasicAnimation *positionAnimation = (CABasicAnimation *)[[view layer] animationForKey:@"position"];
NSValue *sourceValue = positionsByViews[[NSValue valueWithNonretainedObject:view]];
if([positionAnimation isKindOfClass:[CABasicAnimation class]] && sourceValue)
{
NSValue *targetValue = [NSValue valueWithCGPoint:[[view layer] position]];
if([targetValue isEqualToValue:sourceValue])
[[view layer] removeAnimationForKey:@"position"];
}
}
}
This appears not to inhibit views that actually do move, or to cause them to move incorrectly (as if they were expecting everything around them to be down about 240 points and to animate to the correct position with them).
这似乎不会抑制实际移动的视图,或导致它们错误地移动(好像他们期望周围的一切都下降约 240 点并与它们一起动画到正确的位置)。
So this is my current solution.
所以这是我目前的解决方案。
回答by Mark Bridges
I've probably spent about two weeks now trying to get various layout to transition between one another smoothly. I've found that override the proposed offset is working in iOS 10.2, but in version prior to that I still get the issue. The thing that makes my situation a bit worse is I need to transition into another layout as a result of a scroll, so the view is both scrolling and transitioning at the same time.
我现在可能已经花了大约两周的时间试图让各种布局在彼此之间顺利过渡。我发现覆盖建议的偏移量在 iOS 10.2 中有效,但在之前的版本中我仍然遇到问题。让我的情况变得更糟的事情是我需要由于滚动而转换到另一个布局,因此视图同时滚动和转换。
Tommy's answer was the only thing that worked for me in pre 10.2 versions. I'm doing the following thing now.
在 10.2 之前的版本中,汤米的回答是唯一对我有用的东西。我现在正在做以下事情。
class HackedCollectionView: UICollectionView {
var ignoreContentOffsetChanges = false
override func setContentOffset(_ contentOffset: CGPoint, animated: Bool) {
guard ignoreContentOffsetChanges == false else { return }
super.setContentOffset(contentOffset, animated: animated)
}
override var contentOffset: CGPoint {
get {
return super.contentOffset
}
set {
guard ignoreContentOffsetChanges == false else { return }
super.contentOffset = newValue
}
}
override func setCollectionViewLayout(_ layout: UICollectionViewLayout, animated: Bool) {
guard ignoreContentOffsetChanges == false else { return }
super.setCollectionViewLayout(layout, animated: animated)
}
override var contentSize: CGSize {
get {
return super.contentSize
}
set {
guard ignoreContentOffsetChanges == false else { return }
super.contentSize = newValue
}
}
}
Then when I set the layout I do this...
然后当我设置布局时,我会这样做......
let theContentOffsetIActuallyWant = CGPoint(x: 0, y: 100)
UIView.animate(withDuration: animationDuration,
delay: 0, options: animationOptions,
animations: {
collectionView.setCollectionViewLayout(layout, animated: true, completion: { completed in
// I'm also doing something in my layout, but this may be redundant now
layout.overriddenContentOffset = nil
})
collectionView.ignoreContentOffsetChanges = true
}, completion: { _ in
collectionView.ignoreContentOffsetChanges = false
collectionView.setContentOffset(theContentOffsetIActuallyWant, animated: false)
})
回答by Mark Hennings
This finally worked for me (Swift 3)
这终于对我有用(Swift 3)
self.collectionView.collectionViewLayout = UICollectionViewFlowLayout()
self.collectionView.setContentOffset(CGPoint(x: 0, y: -118), animated: true)