내 앱에 매우 간단한 collectionView가 있습니다 (정사각형 썸네일 이미지의 단일 행).
오프셋이 항상 왼쪽에 전체 이미지를 남기도록 스크롤을 가로 채고 싶습니다. 현재 어디로 든 스크롤하여 잘린 이미지를 남깁니다.
어쨌든이 기능을 사용해야한다는 것을 알고 있습니다.
- (CGPoint)targetContentOffsetForProposedContentOffset:withScrollingVelocity
이것을하기 위해 나는 단지 표준을 사용하고 있습니다 UICollectionViewFlowLayout
. 나는 그것을 하위 분류하지 않습니다.
서브 클래 싱없이 이것을 가로 챌 수있는 방법이 UICollectionViewFlowLayout
있습니까?
감사
답변
좋아요, 대답은 아니오입니다. UICollectionViewFlowLayout을 서브 클래 싱하지 않고는이를 수행 할 수있는 방법이 없습니다.
그러나 서브 클래 싱은 미래에 이것을 읽는 모든 사람에게 매우 쉽습니다.
먼저 서브 클래스 호출을 설정 MyCollectionViewFlowLayout
한 다음 인터페이스 빌더에서 컬렉션 뷰 레이아웃을 Custom으로 변경하고 플로우 레이아웃 서브 클래스를 선택했습니다.
이런 식으로 수행하기 때문에 IB에서 항목 크기 등을 지정할 수 없으므로 MyCollectionViewFlowLayout.m 에이 있습니다 …
- (void)awakeFromNib
{
self.itemSize = CGSizeMake(75.0, 75.0);
self.minimumInteritemSpacing = 10.0;
self.minimumLineSpacing = 10.0;
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.sectionInset = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0);
}
이것은 나를 위해 모든 크기와 스크롤 방향을 설정합니다.
그럼 …
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
CGFloat offsetAdjustment = MAXFLOAT;
CGFloat horizontalOffset = proposedContentOffset.x + 5;
CGRect targetRect = CGRectMake(proposedContentOffset.x, 0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
NSArray *array = [super layoutAttributesForElementsInRect:targetRect];
for (UICollectionViewLayoutAttributes *layoutAttributes in array) {
CGFloat itemOffset = layoutAttributes.frame.origin.x;
if (ABS(itemOffset - horizontalOffset) < ABS(offsetAdjustment)) {
offsetAdjustment = itemOffset - horizontalOffset;
}
}
return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
}
이렇게하면 스크롤이 왼쪽 가장자리에서 여백 5.0으로 끝납니다.
그게 내가해야 할 전부입니다. 코드에서 흐름 레이아웃을 설정할 필요가 전혀 없었습니다.
답변
Dan의 솔루션에 결함이 있습니다. 사용자 플릭을 잘 처리하지 못합니다. 사용자가 빠르게 플릭하고 스크롤하는 경우 너무 많이 움직이지 않고 애니메이션 결함이 있습니다.
내가 제안한 대체 구현은 이전에 제안 된 것과 동일한 페이지 매김을 갖지만 사용자가 페이지 사이를 긋는 것을 처리합니다.
#pragma mark - Pagination
- (CGFloat)pageWidth {
return self.itemSize.width + self.minimumLineSpacing;
}
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
CGFloat rawPageValue = self.collectionView.contentOffset.x / self.pageWidth;
CGFloat currentPage = (velocity.x > 0.0) ? floor(rawPageValue) : ceil(rawPageValue);
CGFloat nextPage = (velocity.x > 0.0) ? ceil(rawPageValue) : floor(rawPageValue);
BOOL pannedLessThanAPage = fabs(1 + currentPage - rawPageValue) > 0.5;
BOOL flicked = fabs(velocity.x) > [self flickVelocity];
if (pannedLessThanAPage && flicked) {
proposedContentOffset.x = nextPage * self.pageWidth;
} else {
proposedContentOffset.x = round(rawPageValue) * self.pageWidth;
}
return proposedContentOffset;
}
- (CGFloat)flickVelocity {
return 0.3;
}
답변
수락 된 답변의 신속한 버전.
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
var offsetAdjustment = CGFloat.greatestFiniteMagnitude
let horizontalOffset = proposedContentOffset.x
let targetRect = CGRect(origin: CGPoint(x: proposedContentOffset.x, y: 0), size: self.collectionView!.bounds.size)
for layoutAttributes in super.layoutAttributesForElements(in: targetRect)! {
let itemOffset = layoutAttributes.frame.origin.x
if (abs(itemOffset - horizontalOffset) < abs(offsetAdjustment)) {
offsetAdjustment = itemOffset - horizontalOffset
}
}
return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
}
Swift 5에 유효합니다 .
답변
다음 은 수직 셀 기반 페이징을 위한 Swift 5의 구현입니다 .
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = self.collectionView else {
let latestOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
return latestOffset
}
// Page height used for estimating and calculating paging.
let pageHeight = self.itemSize.height + self.minimumLineSpacing
// Make an estimation of the current page position.
let approximatePage = collectionView.contentOffset.y/pageHeight
// Determine the current page based on velocity.
let currentPage = velocity.y == 0 ? round(approximatePage) : (velocity.y < 0.0 ? floor(approximatePage) : ceil(approximatePage))
// Create custom flickVelocity.
let flickVelocity = velocity.y * 0.3
// Check how many pages the user flicked, if <= 1 then flickedPages should return 0.
let flickedPages = (abs(round(flickVelocity)) <= 1) ? 0 : round(flickVelocity)
let newVerticalOffset = ((currentPage + flickedPages) * pageHeight) - collectionView.contentInset.top
return CGPoint(x: proposedContentOffset.x, y: newVerticalOffset)
}
몇 가지 참고 사항 :
- 결함이 없습니다
- 페이징을 거짓으로 설정하십시오 ! (그렇지 않으면 작동하지 않습니다)
- 자신 만의 flickvelocity를 쉽게 설정할 수 있습니다.
- 이것을 시도한 후에도 여전히 작동하지 않는 경우
itemSize
, 특히 문제가되는 항목의 크기와 실제로 일치 하는지 확인하십시오.collectionView(_:layout:sizeForItemAt:)
대신 itemSize와 함께 맞춤 변수를 사용하세요. - 을 설정할 때 가장 잘 작동합니다
self.collectionView.decelerationRate = UIScrollView.DecelerationRate.fast
.
다음은 수평 버전입니다 (완전히 테스트하지 않았으므로 실수를 용서하십시오).
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = self.collectionView else {
let latestOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
return latestOffset
}
// Page width used for estimating and calculating paging.
let pageWidth = self.itemSize.width + self.minimumInteritemSpacing
// Make an estimation of the current page position.
let approximatePage = collectionView.contentOffset.x/pageWidth
// Determine the current page based on velocity.
let currentPage = velocity.x == 0 ? round(approximatePage) : (velocity.x < 0.0 ? floor(approximatePage) : ceil(approximatePage))
// Create custom flickVelocity.
let flickVelocity = velocity.x * 0.3
// Check how many pages the user flicked, if <= 1 then flickedPages should return 0.
let flickedPages = (abs(round(flickVelocity)) <= 1) ? 0 : round(flickVelocity)
// Calculate newHorizontalOffset.
let newHorizontalOffset = ((currentPage + flickedPages) * pageWidth) - collectionView.contentInset.left
return CGPoint(x: newHorizontalOffset, y: proposedContentOffset.y)
}
이 코드는 내 개인 프로젝트에서 사용하는 코드를 기반으로합니다. 여기 에서 다운로드하고 예제 대상을 실행하여 확인할 수 있습니다 .
답변
동안 이 답변 나에게 큰 도움이되었습니다 당신이 작은 거리에 빠른 슬쩍 때, 눈에 띄는 플리커있다. 장치에서 재현하는 것이 훨씬 쉽습니다.
나는 이것이 항상 일어날 때 발견 collectionView.contentOffset.x - proposedContentOffset.x
하고 velocity.x
다른 노래가 있습니다.
내 해결책은 속도가 양수인 경우 proposedContentOffset
보다 더 많고 contentOffset.x
음수 인 경우 더 적게 확인하는 것입니다. C #으로되어 있지만 Objective C로 변환하는 것은 매우 간단해야합니다.
public override PointF TargetContentOffset (PointF proposedContentOffset, PointF scrollingVelocity)
{
/* Determine closest edge */
float offSetAdjustment = float.MaxValue;
float horizontalCenter = (float) (proposedContentOffset.X + (this.CollectionView.Bounds.Size.Width / 2.0));
RectangleF targetRect = new RectangleF (proposedContentOffset.X, 0.0f, this.CollectionView.Bounds.Size.Width, this.CollectionView.Bounds.Size.Height);
var array = base.LayoutAttributesForElementsInRect (targetRect);
foreach (var layoutAttributes in array) {
float itemHorizontalCenter = layoutAttributes.Center.X;
if (Math.Abs (itemHorizontalCenter - horizontalCenter) < Math.Abs (offSetAdjustment)) {
offSetAdjustment = itemHorizontalCenter - horizontalCenter;
}
}
float nextOffset = proposedContentOffset.X + offSetAdjustment;
/*
* ... unless we end up having positive speed
* while moving left or negative speed while moving right.
* This will cause flicker so we resort to finding next page
* in the direction of velocity and use it.
*/
do {
proposedContentOffset.X = nextOffset;
float deltaX = proposedContentOffset.X - CollectionView.ContentOffset.X;
float velX = scrollingVelocity.X;
// If their signs are same, or if either is zero, go ahead
if (Math.Sign (deltaX) * Math.Sign (velX) != -1)
break;
// Otherwise, look for the closest page in the right direction
nextOffset += Math.Sign (scrollingVelocity.X) * SnapStep;
} while (IsValidOffset (nextOffset));
return proposedContentOffset;
}
bool IsValidOffset (float offset)
{
return (offset >= MinContentOffset && offset <= MaxContentOffset);
}
이 코드는 사용하고 MinContentOffset
, MaxContentOffset
그리고 SnapStep
당신이 정의하는 이는 사소한해야한다. 내 경우에는 그들은
float MinContentOffset {
get { return -CollectionView.ContentInset.Left; }
}
float MaxContentOffset {
get { return MinContentOffset + CollectionView.ContentSize.Width - ItemSize.Width; }
}
float SnapStep {
get { return ItemSize.Width + MinimumLineSpacing; }
}
답변
긴 테스트 후 깜박임을 수정하는 사용자 지정 셀 너비 (각 셀에는 다른 너비가 있음)로 중앙에 스냅하는 솔루션을 찾았습니다. 스크립트를 자유롭게 개선하십시오.
- (CGPoint) targetContentOffsetForProposedContentOffset: (CGPoint) proposedContentOffset withScrollingVelocity: (CGPoint)velocity
{
CGFloat offSetAdjustment = MAXFLOAT;
CGFloat horizontalCenter = (CGFloat) (proposedContentOffset.x + (self.collectionView.bounds.size.width / 2.0));
//setting fastPaging property to NO allows to stop at page on screen (I have pages lees, than self.collectionView.bounds.size.width)
CGRect targetRect = CGRectMake(self.fastPaging ? proposedContentOffset.x : self.collectionView.contentOffset.x,
0.0,
self.collectionView.bounds.size.width,
self.collectionView.bounds.size.height);
NSArray *attributes = [self layoutAttributesForElementsInRect:targetRect];
NSPredicate *cellAttributesPredicate = [NSPredicate predicateWithBlock: ^BOOL(UICollectionViewLayoutAttributes * _Nonnull evaluatedObject,
NSDictionary<NSString *,id> * _Nullable bindings)
{
return (evaluatedObject.representedElementCategory == UICollectionElementCategoryCell);
}];
NSArray *cellAttributes = [attributes filteredArrayUsingPredicate: cellAttributesPredicate];
UICollectionViewLayoutAttributes *currentAttributes;
for (UICollectionViewLayoutAttributes *layoutAttributes in cellAttributes)
{
CGFloat itemHorizontalCenter = layoutAttributes.center.x;
if (ABS(itemHorizontalCenter - horizontalCenter) < ABS(offSetAdjustment))
{
currentAttributes = layoutAttributes;
offSetAdjustment = itemHorizontalCenter - horizontalCenter;
}
}
CGFloat nextOffset = proposedContentOffset.x + offSetAdjustment;
proposedContentOffset.x = nextOffset;
CGFloat deltaX = proposedContentOffset.x - self.collectionView.contentOffset.x;
CGFloat velX = velocity.x;
// detection form gist.github.com/rkeniger/7687301
// based on http://stackoverflow.com/a/14291208/740949
if (fabs(deltaX) <= FLT_EPSILON || fabs(velX) <= FLT_EPSILON || (velX > 0.0 && deltaX > 0.0) || (velX < 0.0 && deltaX < 0.0))
{
}
else if (velocity.x > 0.0)
{
// revert the array to get the cells from the right side, fixes not correct center on different size in some usecases
NSArray *revertedArray = [[array reverseObjectEnumerator] allObjects];
BOOL found = YES;
float proposedX = 0.0;
for (UICollectionViewLayoutAttributes *layoutAttributes in revertedArray)
{
if(layoutAttributes.representedElementCategory == UICollectionElementCategoryCell)
{
CGFloat itemHorizontalCenter = layoutAttributes.center.x;
if (itemHorizontalCenter > proposedContentOffset.x) {
found = YES;
proposedX = nextOffset + (currentAttributes.frame.size.width / 2) + (layoutAttributes.frame.size.width / 2);
} else {
break;
}
}
}
// dont set on unfound element
if (found) {
proposedContentOffset.x = proposedX;
}
}
else if (velocity.x < 0.0)
{
for (UICollectionViewLayoutAttributes *layoutAttributes in cellAttributes)
{
CGFloat itemHorizontalCenter = layoutAttributes.center.x;
if (itemHorizontalCenter > proposedContentOffset.x)
{
proposedContentOffset.x = nextOffset - ((currentAttributes.frame.size.width / 2) + (layoutAttributes.frame.size.width / 2));
break;
}
}
}
proposedContentOffset.y = 0.0;
return proposedContentOffset;
}
답변
Dan Abramov 의이 답변을 참조하십시오. 여기 Swift 버전
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
var _proposedContentOffset = CGPoint(x: proposedContentOffset.x, y: proposedContentOffset.y)
var offSetAdjustment: CGFloat = CGFloat.max
let horizontalCenter: CGFloat = CGFloat(proposedContentOffset.x + (self.collectionView!.bounds.size.width / 2.0))
let targetRect = CGRect(x: proposedContentOffset.x, y: 0.0, width: self.collectionView!.bounds.size.width, height: self.collectionView!.bounds.size.height)
let array: [UICollectionViewLayoutAttributes] = self.layoutAttributesForElementsInRect(targetRect)! as [UICollectionViewLayoutAttributes]
for layoutAttributes: UICollectionViewLayoutAttributes in array {
if (layoutAttributes.representedElementCategory == UICollectionElementCategory.Cell) {
let itemHorizontalCenter: CGFloat = layoutAttributes.center.x
if (abs(itemHorizontalCenter - horizontalCenter) < abs(offSetAdjustment)) {
offSetAdjustment = itemHorizontalCenter - horizontalCenter
}
}
}
var nextOffset: CGFloat = proposedContentOffset.x + offSetAdjustment
repeat {
_proposedContentOffset.x = nextOffset
let deltaX = proposedContentOffset.x - self.collectionView!.contentOffset.x
let velX = velocity.x
if (deltaX == 0.0 || velX == 0 || (velX > 0.0 && deltaX > 0.0) || (velX < 0.0 && deltaX < 0.0)) {
break
}
if (velocity.x > 0.0) {
nextOffset = nextOffset + self.snapStep()
} else if (velocity.x < 0.0) {
nextOffset = nextOffset - self.snapStep()
}
} while self.isValidOffset(nextOffset)
_proposedContentOffset.y = 0.0
return _proposedContentOffset
}
func isValidOffset(offset: CGFloat) -> Bool {
return (offset >= CGFloat(self.minContentOffset()) && offset <= CGFloat(self.maxContentOffset()))
}
func minContentOffset() -> CGFloat {
return -CGFloat(self.collectionView!.contentInset.left)
}
func maxContentOffset() -> CGFloat {
return CGFloat(self.minContentOffset() + self.collectionView!.contentSize.width - self.itemSize.width)
}
func snapStep() -> CGFloat {
return self.itemSize.width + self.minimumLineSpacing;
}
또는 여기에 요점 https://gist.github.com/katopz/8b04c783387f0c345cd9