Последние несколько недель я ломаю голову над этим, и у меня нет идей ..
Короткий фон
Я создал свой собственный UICollectionView
фреймворк с пользовательским UICollectionViewFlowLayout
для вертикальной подкачки карточек и создания эффекта стека карточек.
Вот изображение того, на что это похоже:
Задача
Я пытаюсь реализовать функцию, которая позволяет пользователю прокручивать до определенной карты с определенным индексом (например, с использованием функции scrollToItem
).
Теперь проблема в том, что по какой-то причине при попытке прокрутки назад к предыдущей карте работает неправильно.
Ниже приведена запись того, что происходит , я прикрепил к нему tapGestureRecognizer, когда я касаюсь сфокусированной (текущей по центру) карты, она должна прокрутить до этого индекса - 1 (так, предыдущая карта ).
По какой-то причине он делает это только тогда, когда frame.origin.y
нижней карты находится над frame.origin.maxY
сфокусированной (текущей по центру карты) .
Обратите внимание, что я дважды нажимаю на карту, чтобы она заработала, потому что значение frame.origin.y
нижней карты должно быть ниже, чем frame.origin.maxY
верхней карты, по некоторым причинам, чтобы работа.
Основные ответы, которые я уже пробовал
collectionView.scrollToItem(at: convertIndexToIndexPath(for: index), at: .top, animated: animated)
и
if let frame = collectionView.layoutAttributesForItem(at: convertIndexToIndexPath(for: index))?.frame {
collectionView.scrollRectToVisible(frame, animated: true)
}
Некоторые важные вещи, которые нужно знать
Я уже выяснил, какая строка вызывает проблему, внутри подкласса UICollectionViewFlowLayout
(называемого VerticalCardSwiperFlowLayout
), есть функция с именем updateCellAttributes
, которая изменяет некоторые свойства фреймов, чтобы иметь возможность достичь этого эффекта. Проблема возникает в следующей строке:
let finalY = max(cvMinY, cardMinY)
Вот рассматриваемая функция, включая очень расширенное объяснение того, как она работает вверху в комментариях к коду:
/**
Updates the attributes.
Here manipulate the zIndex of the cards here, calculate the positions and do the animations.
Below we'll briefly explain how the effect of scrolling a card to the background instead of the top is achieved.
Keep in mind that (x,y) coords in views start from the top left (x: 0,y: 0) and increase as you go down/to the right,
so as you go down, the y-value increases, and as you go right, the x value increases.
The two most important variables we use to achieve this effect are cvMinY and cardMinY.
* cvMinY (A): The top position of the collectionView + inset. On the drawings below it's marked as "A".
This position never changes (the value of the variable does, but the position is always at the top where "A" is marked).
* cardMinY (B): The top position of each card. On the drawings below it's marked as "B". As the user scrolls a card,
this position changes with the card position (as it's the top of the card).
When the card is moving down, this will go up, when the card is moving up, this will go down.
We then take the max(cvMinY, cardMinY) to get the highest value of those two and set that as the origin.y of the card.
By doing this, we ensure that the origin.y of a card never goes below cvMinY, thus preventing cards from scrolling upwards.
```
+---------+ +---------+
| | | |
| +-A=B-+ | | +-A-+ | ---> The top line here is the previous card
| | | | | +--B--+ | that's visible when the user starts scrolling.
| | | | | | | |
| | | | | | | | | As the card moves down,
| | | | | | | | v cardMinY ("B") goes up.
| +-----+ | | | | |
| | | +-----+ |
| +--B--+ | | +--B--+ |
| | | | | | | |
+-+-----+-+ +-+-----+-+
```
- parameter attributes: The attributes we're updating.
*/
fileprivate func updateCellAttributes(_ attributes: UICollectionViewLayoutAttributes) {
guard let collectionView = collectionView else { return }
var cvMinY = collectionView.bounds.minY + collectionView.contentInset.top
let cardMinY = attributes.frame.minY
var origin = attributes.frame.origin
let cardHeight = attributes.frame.height
if cvMinY > cardMinY + cardHeight + minimumLineSpacing + collectionView.contentInset.top {
cvMinY = 0
}
let finalY = max(cvMinY, cardMinY)
let deltaY = (finalY - cardMinY) / cardHeight
transformAttributes(attributes: attributes, deltaY: deltaY)
// Set the attributes frame position to the values we calculated
origin.x = collectionView.frame.width/2 - attributes.frame.width/2 - collectionView.contentInset.left
origin.y = finalY
attributes.frame = CGRect(origin: origin, size: attributes.frame.size)
attributes.zIndex = attributes.indexPath.row
}
// Creates and applies a CGAffineTransform to the attributes to recreate the effect of the card going to the background.
fileprivate func transformAttributes(attributes: UICollectionViewLayoutAttributes, deltaY: CGFloat) {
let translationScale = CGFloat((attributes.zIndex + 1) * 10)
if let itemTransform = firstItemTransform {
let scale = 1 - deltaY * itemTransform
var t = CGAffineTransform.identity
t = t.scaledBy(x: scale, y: 1)
if isPreviousCardVisible {
t = t.translatedBy(x: 0, y: (deltaY * translationScale))
}
attributes.transform = t
}
}
Вот функция, которая вызывает этот конкретный код:
internal override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let items = NSArray(array: super.layoutAttributesForElements(in: rect)!, copyItems: true)
for object in items {
if let attributes = object as? UICollectionViewLayoutAttributes {
self.updateCellAttributes(attributes)
}
}
return items as? [UICollectionViewLayoutAttributes]
}
Если вы хотите посмотреть сами, вы можете скачать полный проект здесь:
https://github.com/JoniVR/VerticalCardSwiper/archive/development.zip
(если вы оформляете заказ через github, обязательно загрузите ветку разработки)
специфическая проблема Github с некоторыми обсуждениями и, возможно, дополнительной информацией можно найти здесь .
Спасибо за ваше время, любая помощь или информация по этой проблеме будет принята с благодарностью! Также, пожалуйста, дайте мне знать, если у вас есть дополнительные вопросы или если какая-либо важная информация отсутствует
изменить 1:
Примечание: странный эффект возникает только тогда, когда прокрутка к currentIndex - 1
, currentIndex + 1
не вызывает проблем, что, я думаю, несколько объясняет, почему это происходит на let finalY = max(cvMinY, cardMinY)
, но я пока не нашел правильного решения для него ,