可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I want to implement "cards" in my app like Safari tabs or App Store search.
I will show user one card in a center of screen and part of previous and next cards at left and right sides. (See App Store search or Safari tabs for example)
I decided to use UICollectionView
, and I need to change page size (didn't find how) or implement own layout subclass (don't know how)?
Any help, please?
回答1:
Below is the simplest way that I've found to get this effect. It involves your collection view and an extra secret scroll view.
Set up your collection views
- Set up your collection view and all its data source methods.
- Frame the collection view; it should span the full width that you want to be visible.
Set the collection view's contentInset
:
_collectionView.contentInset = UIEdgeInsetsMake(0, (self.view.frame.size.width-pageSize)/2, 0, (self.view.frame.size.width-pageSize)/2);
This helps offset the cells properly.
Set up your secret scrollview
- Create a scrollview, place it wherever you like. You can set it to
hidden
if you like.
- Set the size of the scrollview's bounds to the desired size of your page.
- Set yourself as the delegate of the scrollview.
- Set its
contentSize
to the expected content size of your collection view.
Move your gesture recognizer
Add the secret scrollview's gesture recognizer to the collection view, and disable the collection view's gesture recognizer:
[_collectionView addGestureRecognizer:_secretScrollView.panGestureRecognizer];
_collectionView.panGestureRecognizer.enabled = NO;
Delegate
- (void) scrollViewDidScroll:(UIScrollView *)scrollView {
CGPoint contentOffset = scrollView.contentOffset;
contentOffset.x = contentOffset.x - _collectionView.contentInset.left;
_collectionView.contentOffset = contentOffset;
}
As the scrollview moves, get its offset and set it to the offset of the collection view.
I blogged about this here, so check this link for updates: http://khanlou.com/2013/04/paging-a-overflowing-collection-view/
回答2:
You can subclass UICollectionViewFlowLayout and override like so:
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)contentOffset
withScrollingVelocity:(CGPoint)velocity
{
NSArray* layoutAttributesArray =
[self layoutAttributesForElementsInRect:self.collectionView.bounds];
if(layoutAttributesArray.count == 0)
return contentOffset;
UICollectionViewLayoutAttributes* candidate =
layoutAttributesArray.firstObject;
for (UICollectionViewLayoutAttributes* layoutAttributes in layoutAttributesArray)
{
if (layoutAttributes.representedElementCategory != UICollectionElementCategoryCell)
continue;
if((velocity.x > 0.0 && layoutAttributes.center.x > candidate.center.x) ||
(velocity.x <= 0.0 && layoutAttributes.center.x < candidate.center.x))
candidate = layoutAttributes;
}
return CGPointMake(candidate.center.x - self.collectionView.bounds.size.width * 0.5f, contentOffset.y);
}
This will get the next or previous cell depending on the velocity... it will not snap on the current cell however.
回答3:
@Mike M's answer in Swift…
class CenteringFlowLayout: UICollectionViewFlowLayout {
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView,
let layoutAttributesArray = layoutAttributesForElementsInRect(collectionView.bounds),
var candidate = layoutAttributesArray.first else { return proposedContentOffset }
layoutAttributesArray.filter({$0.representedElementCategory == .Cell }).forEach { layoutAttributes in
if (velocity.x > 0 && layoutAttributes.center.x > candidate.center.x) ||
(velocity.x <= 0 && layoutAttributes.center.x < candidate.center.x) {
candidate = layoutAttributes
}
}
return CGPoint(x: candidate.center.x - collectionView.bounds.width / 2, y: proposedContentOffset.y)
}
}
回答4:
A little edit on Soroush answer, which did the trick for me.
The only edit I made instead of disabling the gesture:
[_collectionView addGestureRecognizer:_secretScrollView.panGestureRecognizer];
_collectionView.panGestureRecognizer.enabled = NO;
I disabled scrolling on the collectionview:
_collectionView.scrollEnabled = NO;
As disabling the gesture disabled the secret scrollview gesture as well.
回答5:
I'll add another solution. The snapping in place is not perfect (not as good as when paging enabled is set, but works well enough).
I have tried implementing Soroush's solution, but it doesn't work for me.
Because the UICollectionView
is a subclass of UIScrollView
it will respond to an important UIScrollViewDelegate
method which is:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint *)targetContentOffset
The targetContentOffset
(an inout pointer) lets you redefine the stopping point for a collection view (the x
in this case if you swipe horizontally).
A quick note about a couple of the variables found below:
self.cellWidth
– this is your collection view cell's width (you can even hardcode it there if you want)
self.minimumLineSpacing
– this is the minimum line spacing you set between the cells
self.scrollingObjects
is the array of objects contained in the collection view (I need this mostly for the count, to know when to stop scrolling)
So, the idea is to implement this method in the collection view's delegate, like so:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint *)targetContentOffset
{
if (self.currentIndex == 0 && velocity.x < 0) {
// we have reached the first user and we're trying to go back
return;
}
if (self.currentIndex == (self.scrollingObjects.count - 1) && velocity.x > 0) {
// we have reached the last user and we're trying to go forward
return;
}
if (velocity.x < 0) {
// swiping from left to right (going left; going back)
self.currentIndex--;
} else {
// swiping from right to left (going right; going forward)
self.currentIndex++;
}
float xPositionToStop = 0;
if (self.currentIndex == 0) {
// first row
xPositionToStop = 0;
} else {
// one of the next ones
xPositionToStop = self.currentIndex * self.cellWidth + (self.currentIndex + 1) * self.minimumLineSpacing - ((scrollView.bounds.size.width - 2*self.minimumLineSpacing - self.cellWidth)/2);
}
targetContentOffset->x = xPositionToStop;
NSLog(@"Point of stopping: %@", NSStringFromCGPoint(*targetContentOffset));
}
Looking forward to any feedback you may have which makes the snapping in place better. I'll also keep on looking for a better solution...
回答6:
The previous is quite complicated, UICollectionView is a subclass of UIScrollView, so just do this:
[self.collectionView setPagingEnabled:YES];
You are all set to go.
See this detailed tutorial.