gpt4 book ai didi

ios - 向左滑动创建一个新的 collectionViewCell

转载 作者:塔克拉玛干 更新时间:2023-11-02 08:12:26 26 4
gpt4 key购买 nike

我正在尝试实现一种更流畅的方式来为 myCollectionView 添加新的 collectionViewCells(一次只显示一个单元格)。我希望它类似于当用户向左滑动时,如果用户在最后一个单元格上,myCollectionView 在用户滑动时插入一个新单元格,以便用户向左滑动“进入”单元格。而且我一次只允许用户滚动一个单元格。

编辑:所以我觉得用语言来描述它有点困难,所以这里有一个 gif 来展示我的意思

enter image description here

所以在过去的几周里,我一直在尝试以多种不同的方式实现它,我发现最成功的一种是使用 scrollViewWillEndDragging 委托(delegate)方法,我已经这样实现了:

 func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {

// Getting the size of the cells
let flowLayout = myCollectionView.collectionViewLayout as! UICollectionViewFlowLayout
let cellWidth = flowLayout.itemSize.width
let cellPadding = 10.0 as! CGFloat

// Calculating which page "card" we should be on
let currentOffset = scrollView.contentOffset.x - cellWidth/2
print("currentOffset is \(currentOffset)")
let cardWidth = cellWidth + cellPadding
var page = Int(round((currentOffset)/(cardWidth) + 1))

print("current page number is: \(page)")
if (velocity.x < 0) {
page -= 1

}
if (velocity.x > 0) {
page += 1
}
print("Updated page number is: \(page)")
print("Previous page number is: \(self.previousPage)")

// Only allowing the user to scroll for one page!
if(page > self.previousPage) {
page = self.previousPage + 1
self.previousPage = page

}
else if (page == self.previousPage) {
page = self.previousPage
}
else {
page = self.previousPage - 1
self.previousPage = page
}

print("new page number is: " + String(page))
print("addedCards.count + 1 is: " + String(addedCards.count + 1))
if (page == addedCards.count) {
print("reloading data")

// Update data source
addedCards.append("card")

// Method 1
cardCollectionView.reloadData()

// Method 2
// let newIndexPath = NSIndexPath(forItem: addedCards.count - 1, inSection: 0)
// cardCollectionView.insertItemsAtIndexPaths([newIndexPath])

}
// print("Centering on new cell")
// Center the cardCollectionView on the new page
let newOffset = CGFloat(page * Int((cellWidth + cellPadding)))
print("newOffset is: \(newOffset)")
targetContentOffset.memory.x = newOffset

}

虽然我认为我几乎得到了预期的结果,但仍然存在一些问题和我发现的错误。

我主要担心的是,我没有在 myCollectionView 的末尾插入单个单元格,而是重新加载了整个表格。我这样做的原因是,如果我不这样做,那么 myCollectionView.contentOffset 将不会更改,因此在创建新单元格时,myCollectionView 是' t 以新创建的单元格为中心。

1. 如果用户滚动非常缓慢然后停止,则会创建新的单元格,但随后 myCollectionView 会卡在两个单元格之间,它不会居中新创建的单元格。

2.myCollectionView 由于 1. 而位于倒数第二个单元格和最后一个单元格之间时,下一次用户向右滑动,而不是创建一个单元格,而是创建两个单元格。

我还使用了不同的方法来实现此行为,例如使用 scrollViewDidScroll 和其他各种方法,但均无济于事。任何人都可以指出我正确的方向,因为我有点迷路了。

如果您想查看交互,可以通过以下链接下载我的项目: My Example Project

如果您有兴趣,可以使用这些旧方法:

为此我尝试了两种方法,

第一个是:

func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, 
targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// page is the current cell that the user is on
// addedCards is the array that the data source works with
if (page == addedCards.count + 1) {
let placeholderFlashCardProxy = FlashCardProxy(phrase: nil, pronunciation: nil, definition: nil)
addedCards.append(placeholderFlashCardProxy)
let newIndexPath = NSIndexPath(forItem: addedCards.count, inSection: 0)
cardCollectionView.insertItemsAtIndexPaths([newIndexPath])
cardCollectionView.reloadData()

}
}

使用这种方法的问题在于:

  1. 有时我会因为以下原因而崩溃:NSInternalInconsistencyException',原因:'无效更新:第 0 节中的项目数量无效。
  2. 当添加一个新的 collectionCell 时,它有时会显示用户从前一个单元格写入的输入(这有时可能与单元格的出队和重用有关,尽管再次,我不确定,如果有人回答我将不胜感激)
  3. 插入不顺利,我希望用户能够在最后一个单元格向左滑动并“进入”一个新单元格。就像我目前在最后一个单元格上一样,向左滑动会自动将我置于新单元格中,因为现在当我向左滑动时会创建一个新单元格,它不会以新创建的单元格为中心

我使用的第二种方法是:

let swipeLeftGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "swipedLeftOnCell:")
swipeLeftGestureRecognizer.direction = .Left
myCollectionView.addGestureRecognizer(swipeLeftGestureRecognizer)
swipeLeftGestureRecognizer.delegate = self

虽然滑动手势很少响应,但如果我使用点击手势,myCollectionView 总是响应,这很奇怪(我再次知道这本身就是一个问题)

我的问题是哪种方法更好地实现我上面描述的内容?如果没有一个是好的,我应该用什么来创造想要的结果,我已经尝试了两天了,我想知道是否有人可以指出我正确的方向。谢谢!

最佳答案

我希望这能以某种方式帮助您 :)

更新

我更新了代码以解决向任一方向滚动的问题。可以在此处找到更新的要点

New Updated Gist

Old Gist

首先我要为卡片定义一些模型

class Card {
var someCardData : String?
}

接下来,创建一个 Collection View 单元格,里面有一个卡片 View ,我们将对其应用转换

class CollectionViewCell : UICollectionViewCell {

override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(cardView)
self.addSubview(cardlabel)
}

override func prepareForReuse() {
super.prepareForReuse()
cardView.alpha = 1.0
cardView.layer.transform = CATransform3DIdentity
}

override func layoutSubviews() {
super.layoutSubviews()
cardView.frame = CGRectMake(contentPadding,
contentPadding,
contentView.bounds.width - (contentPadding * 2.0),
contentView.bounds.height - (contentPadding * 2.0))

cardlabel.frame = cardView.frame
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

lazy var cardView : UIView = {
[unowned self] in
var view = UIView(frame: CGRectZero)
view.backgroundColor = UIColor.whiteColor()
return view
}()

lazy var cardlabel : UILabel = {
[unowned self] in
var label = UILabel(frame: CGRectZero)
label.backgroundColor = UIColor.whiteColor()
label.textAlignment = .Center
return label
}()
}

接下来使用 Collection View 设置 View Controller 。正如您将看到的,有一个 CustomCollectionView 类,我将在最后定义它。

class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {

var cards = [Card(), Card()]

override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.frame = CGRectMake(0, 0, self.view.bounds.width, tableViewHeight)
collectionView.contentInset = UIEdgeInsetsMake(0, 0, 0, contentPadding)
}

lazy var collectionView : CollectionView = {
[unowned self] in

// MARK: Custom Flow Layout defined below
var layout = CustomCollectionViewFlowLayout()
layout.contentDelegate = self

var collectionView = CollectionView(frame: CGRectZero, collectionViewLayout : layout)
collectionView.clipsToBounds = true
collectionView.showsVerticalScrollIndicator = false
collectionView.registerClass(CollectionViewCell.self, forCellWithReuseIdentifier: "CollectionViewCell")
collectionView.delegate = self
collectionView.dataSource = self
return collectionView
}()

// MARK: UICollectionViewDelegate, UICollectionViewDataSource

func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cards.count
}

func collectionView(collectionView : UICollectionView, layout collectionViewLayout:UICollectionViewLayout, sizeForItemAtIndexPath indexPath:NSIndexPath) -> CGSize {
return CGSizeMake(collectionView.bounds.width, tableViewHeight)
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cellIdentifier = "CollectionViewCell"
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: indexPath) as! CollectionViewCell
cell.contentView.backgroundColor = UIColor.blueColor()

// UPDATE If the cell is not the initial index, and is equal the to animating index
// Prepare it's initial state
if flowLayout.animatingIndex == indexPath.row && indexPath.row != 0{
cell.cardView.alpha = 0.0
cell.cardView.layer.transform = CATransform3DScale(CATransform3DIdentity, 0.0, 0.0, 0.0)
}
return cell
}
}

已更新 - 现在是真正棘手的部分。我要定义 CustomCollectionViewFlowLayout。协议(protocol)回调返回流布局计算的下一个插入索引

protocol CollectionViewFlowLayoutDelegate : class {
func flowLayout(flowLayout : CustomCollectionViewFlowLayout, insertIndex index : NSIndexPath)
}

/**
* Custom FlowLayout
* Tracks the currently visible index and updates the proposed content offset
*/
class CustomCollectionViewFlowLayout: UICollectionViewFlowLayout {

weak var contentDelegate: CollectionViewFlowLayoutDelegate?

// Tracks the card to be animated
// TODO: - Adjusted if cards are deleted by one if cards are deleted
private var animatingIndex : Int = 0

// Tracks thje currently visible index
private var visibleIndex : Int = 0 {
didSet {
if visibleIndex > oldValue {

if visibleIndex > animatingIndex {
// Only increment the animating index forward
animatingIndex = visibleIndex
}

if visibleIndex + 1 > self.collectionView!.numberOfItemsInSection(0) - 1 {
let currentEntryIndex = NSIndexPath(forRow: visibleIndex + 1, inSection: 0)
contentDelegate?.flowLayout(self, insertIndex: currentEntryIndex)
}

} else if visibleIndex < oldValue && animatingIndex == oldValue {
// if we start panning to the left, and the animating index is the old value
// let set the animating index to the last card.
animatingIndex = oldValue + 1
}
}
}

override init() {
super.init()
self.minimumInteritemSpacing = 0.0
self.minimumLineSpacing = 0.0
self.scrollDirection = .Horizontal
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// The width offset threshold percentage from 0 - 1
let thresholdOffsetPrecentage : CGFloat = 0.5

// This is the flick velocity threshold
let velocityThreshold : CGFloat = 0.4

override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {

let leftThreshold = CGFloat(collectionView!.bounds.size.width) * ((CGFloat(visibleIndex) - 0.5))
let rightThreshold = CGFloat(collectionView!.bounds.size.width) * ((CGFloat(visibleIndex) + 0.5))

let currentHorizontalOffset = collectionView!.contentOffset.x

// If you either traverse far enought in either direction,
// or flicked the scrollview over the horizontal velocity in either direction,
// adjust the visible index accordingly

if currentHorizontalOffset < leftThreshold || velocity.x < -velocityThreshold {
visibleIndex = max(0 , (visibleIndex - 1))
} else if currentHorizontalOffset > rightThreshold || velocity.x > velocityThreshold {
visibleIndex += 1
}

var _proposedContentOffset = proposedContentOffset
_proposedContentOffset.x = CGFloat(collectionView!.bounds.width) * CGFloat(visibleIndex)

return _proposedContentOffset
}
}

并在您的 View Controller 中定义委托(delegate)方法,以便在委托(delegate)告诉它需要新索引时插入新卡片

extension ViewController : CollectionViewFlowLayoutDelegate {
func flowLayout(flowLayout : CustomCollectionViewFlowLayout, insertIndex index : NSIndexPath) {
cards.append(Card())
collectionView.performBatchUpdates({
self.collectionView.insertItemsAtIndexPaths([index])
}) { (complete) in

}
}

下面是自定义的 Collection View ,它在相应地滚动时应用动画 :)

class CollectionView : UICollectionView {

override var contentOffset: CGPoint {
didSet {
if self.tracking {
// When you are tracking the CustomCollectionViewFlowLayout does not update it's visible index until you let go
// So you should be adjusting the second to last cell on the screen
self.adjustTransitionForOffset(NSIndexPath(forRow: self.numberOfItemsInSection(0) - 1, inSection: 0))
} else {

// Once the CollectionView is not tracking, the CustomCollectionViewFlowLayout calls
// targetContentOffsetForProposedContentOffset(_:withScrollingVelocity:), and updates the visible index
// by adding 1, thus we need to continue the trasition on the second the last cell
self.adjustTransitionForOffset(NSIndexPath(forRow: self.numberOfItemsInSection(0) - 2, inSection: 0))
}
}
}

/**
This method applies the transform accordingly to the cell at a specified index
- parameter atIndex: index of the cell to adjust
*/
func adjustTransitionForOffset(atIndex : NSIndexPath) {
if let lastCell = self.cellForItemAtIndexPath(atIndex) as? CollectionViewCell {
let progress = 1.0 - (lastCell.frame.minX - self.contentOffset.x) / lastCell.frame.width
lastCell.cardView.alpha = progress
lastCell.cardView.layer.transform = CATransform3DScale(CATransform3DIdentity, progress, progress, 0.0)
}
}
}

关于ios - 向左滑动创建一个新的 collectionViewCell,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36348464/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com