向左滑动以创建新的collectionViewCell

时间:2016-04-01 05:08:59

标签: ios uicollectionview uicollectionviewcell uiswipegesturerecognizer

我尝试使用更顺畅的方式为collectionViewCells添加新的myCollectionView(一次只显示一个单元格)。我想让它像用户向左滑动一样,如果用户在最后一个单元格上myCollectionView,则在用户滑动时插入一个新单元格,以便用户向左滑动"细胞。而且我也只允许用户一次滚动一个单元格。

修改 所以我认为用文字描述它有点难,所以这里有一个显示我的意思的gif

enter image description here

所以在过去的几周里,我一直试图以多种不同的方式实现这一点,而我最成功的方法是使用scrollViewWillEndDragging委托方法并实现它像这样:

 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不会居中在新创建的单元格上。

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

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

我还使用不同的方式来实现此行为,例如使用myCollectionView,以及其他各种方法,但无济于事。任何人都可以指出我正确的方向,因为我有点失落。

如果您想查看互动,可以使用以下链接下载我的项目: My Example Project

如果您有兴趣,请使用以下旧方法:

要做到这一点,我尝试了两种方法,

第一个是:

scrollViewDidScroll

使用此方法的问题是:

  1. 有时我会因: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() } }
  2. 而崩溃
  3. 当添加新的NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0.时,它有时会显示用户从前一个单元格写入的输入(这可能有时与单元格的出列和重复使用有关,尽管如此,我和# 39;我不确定,如果有人回答,我将不胜感激。)
  4. 插入不顺畅,我希望用户能够在最后一个单元格中向左滑动,并且&#34;进入&#34;一个新的细胞。如果我当前在最后一个单元格中,向左滑动会自动将我放在新单元格中,因为现在当我向左滑动时,新单元格创建并不是以新创建的单元格为中心
  5. 我使用的第二种方法是:

    collectionCell

    虽然滑动手势很少响应,但如果我使用轻击手势,let swipeLeftGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "swipedLeftOnCell:") swipeLeftGestureRecognizer.direction = .Left myCollectionView.addGestureRecognizer(swipeLeftGestureRecognizer) swipeLeftGestureRecognizer.delegate = self 总是响应,这非常奇怪(我再次知道这是一个问题)

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

2 个答案:

答案 0 :(得分:1)

我希望这会有所帮助:)

<强>更新

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

New Updated Gist

Old Gist

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

class Card {
    var someCardData : String?
}

接下来,创建一个集合视图单元格,其中有一个卡片视图,我们将把变换应用到

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
     }()
}

接下来使用集合视图设置视图控制器。正如您将看到的那样,我将在最后定义一个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 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
    }
}

在视图控制器中定义委托方法,以便在委托告诉它需要新索引时插入新卡

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

    }
}

以下是自定义Collection视图,在相应滚动时应用动画:)

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)
        }
    }
}

答案 1 :(得分:0)

我想你在

中错过了一点

let newIndexPath = NSIndexPath(forItem: addedCards.count, inSection: 0)

indexPath应该是

NSIndexPath(forItem : addedCards.count - 1, inSection : 0)不是addedCards.count

这就是你收到错误的原因

NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0