我试图将UIDynamics弹性添加到Swift中的水平UICollectionViewFlowLayout
。
我的视图控制器非常基本,只显示30个绿色的库存项目:
import Foundation
import UIKit
class MainViewController: UIViewController {
private var collectionView: UICollectionView?
private let cellIdentifier = "Cell"
init() {
super.init(nibName: nil, bundle: nil)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
let layout = SpringyCollectionViewLayout()
let collectionView = UICollectionView(frame: view.frame, collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.backgroundColor = UIColor.whiteColor()
collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: cellIdentifier)
collectionView.decelerationRate = 0.9
view.addSubview(collectionView)
self.collectionView = collectionView
view.backgroundColor = UIColor.whiteColor()
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}
extension MainViewController : UICollectionViewDataSource {
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 30
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: indexPath)
cell.clipsToBounds = true
cell.backgroundColor = UIColor.greenColor()
return cell
}
}
extension MainViewController : UICollectionViewDelegate {
}
我的布局子类如下所示:
import Foundation
import UIKit
let kLength = 0.3
let kDamping = 5.5
let kFrequence = 1.3
let kResistence = 1000
class SpringyCollectionViewLayout: UICollectionViewFlowLayout {
var dynamicAnimator: UIDynamicAnimator!
var visibleIndexPathsSet: NSMutableSet!
var latestDelta = CGFloat()
override init() {
super.init()
sectionInset = UIEdgeInsetsMake(0, 20, 0, 20)
itemSize = CGSizeMake(UIScreen.mainScreen().bounds.width * 0.5, UIScreen.mainScreen().bounds.height * 0.8)
minimumLineSpacing = 20
scrollDirection = .Horizontal
self.dynamicAnimator = UIDynamicAnimator(collectionViewLayout: self)
self.visibleIndexPathsSet = NSMutableSet()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareLayout() {
super.prepareLayout()
// Need to overflow our actual visible rect slightly to avoid flickering.
let visibleRect = CGRectInset(self.collectionView!.bounds, -100, -100)
let itemsInVisibleRectArray: NSArray = super.layoutAttributesForElementsInRect(visibleRect)!
let itemsIndexPathsInVisibleRectSet: NSSet = NSSet(array: itemsInVisibleRectArray.valueForKey("indexPath") as! [AnyObject])
// Step 1: Remove any behaviours that are no longer visible.
let noLongerVisibleBehaviours = (self.dynamicAnimator.behaviors as NSArray).filteredArrayUsingPredicate(NSPredicate(block: {behaviour, bindings in
let currentlyVisible: Bool = itemsIndexPathsInVisibleRectSet.member((behaviour as! UIAttachmentBehavior).items.first!
) != nil
return !currentlyVisible
}))
for (_, obj) in noLongerVisibleBehaviours.enumerate() {
self.dynamicAnimator.removeBehavior(obj as! UIDynamicBehavior)
self.visibleIndexPathsSet.removeObject((obj as! UIAttachmentBehavior).items.first!)
}
// Step 2: Add any newly visible behaviours.
// A "newly visible" item is one that is in the itemsInVisibleRect(Set|Array) but not in the visibleIndexPathsSet
let newlyVisibleItems = itemsInVisibleRectArray.filteredArrayUsingPredicate(NSPredicate(block: {item, bindings in
let currentlyVisible: Bool = self.visibleIndexPathsSet.member(item.indexPath) != nil
return !currentlyVisible
}))
let touchLocation: CGPoint = self.collectionView!.panGestureRecognizer.locationInView(self.collectionView)
for (_, item) in newlyVisibleItems.enumerate() {
let springBehaviour: UIAttachmentBehavior = UIAttachmentBehavior(item: item as! UIDynamicItem, attachedToAnchor: item.center)
springBehaviour.length = CGFloat(kLength)
springBehaviour.damping = CGFloat(kDamping)
springBehaviour.frequency = CGFloat(kFrequence)
// If our touchLocation is not (0,0), we'll need to adjust our item's center "in flight"
if (!CGPointEqualToPoint(CGPointZero, touchLocation)) {
let yDistanceFromTouch = fabsf(Float(touchLocation.y - springBehaviour.anchorPoint.y))
let xDistanceFromTouch = fabsf(Float(touchLocation.x - springBehaviour.anchorPoint.x))
let scrollResistance = (yDistanceFromTouch + xDistanceFromTouch) / Float(kResistence)
let item = springBehaviour.items.first as! UICollectionViewLayoutAttributes
var center = item.center
if self.latestDelta < 0 {
center.x += max(self.latestDelta, self.latestDelta * CGFloat(scrollResistance))
} else {
center.x += min(self.latestDelta, self.latestDelta * CGFloat(scrollResistance))
}
item.center = center
}
self.dynamicAnimator.addBehavior(springBehaviour)
self.visibleIndexPathsSet.addObject(item.indexPath)
}
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return self.dynamicAnimator.itemsInRect(rect) as? [UICollectionViewLayoutAttributes]
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
return self.dynamicAnimator.layoutAttributesForCellAtIndexPath(indexPath)
}
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
let scrollView = self.collectionView!
let delta: CGFloat = newBounds.origin.x - scrollView.bounds.origin.x
self.latestDelta = delta
let touchLocation: CGPoint = self.collectionView!.panGestureRecognizer.locationInView(self.collectionView)
for (_, springBehaviour) in self.dynamicAnimator.behaviors.enumerate() {
let springBehav = (springBehaviour as! UIAttachmentBehavior)
let yDistanceFromTouch = fabsf(Float(touchLocation.y - springBehav.anchorPoint.y))
let xDistanceFromTouch = fabsf(Float(touchLocation.x - springBehav.anchorPoint.x))
let scrollResistance = (yDistanceFromTouch + xDistanceFromTouch) / Float(kResistence)
let item = springBehav.items.first as! UICollectionViewLayoutAttributes
var center = item.center
if self.latestDelta < 0 {
center.x += max(self.latestDelta, self.latestDelta * CGFloat(scrollResistance))
} else {
center.x += min(self.latestDelta, self.latestDelta * CGFloat(scrollResistance))
}
item.center = center;
self.dynamicAnimator.updateItemUsingCurrentState(item)
}
return false
}
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
var offsetAdjustment = CGFloat(MAXFLOAT)
let center = proposedContentOffset.x + (CGRectGetWidth(self.collectionView!.bounds) / 2.0)
let proposedRect = CGRectMake(proposedContentOffset.x, 0.0, self.collectionView!.bounds.size.width, self.collectionView!.bounds.size.height)
let array:NSArray = self.layoutAttributesForElementsInRect(proposedRect)!
for layoutAttributes : AnyObject in array {
if let _layoutAttributes = layoutAttributes as? UICollectionViewLayoutAttributes {
if _layoutAttributes.representedElementCategory != UICollectionElementCategory.Cell {
continue
}
let itemVerticalCenter:CGFloat = layoutAttributes.center.x
let _center = fabsf(Float(itemVerticalCenter) - Float(center))
let _offsetAdjustment = fabsf(Float(offsetAdjustment))
if (_center < _offsetAdjustment) {
offsetAdjustment = (itemVerticalCenter - center)
}
}
}
return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y)
}
}
集合视图使用正确的contentSize
呈现,但单元格不存在:
如果我从layoutAttributesForElementsInRect
删除了返回itemsInRect
结果的UIDynamicAnimator
覆盖,则会显示单元格 - 尽管没有动态行为:
知道我错过了什么?我之前在Objective-C和iOS 8中做过同样的事情 - 当我比较它看起来相似的代码时。
我调试了prepareLayout
,它确实在可见的矩形中找到了单元格,并且行为确实被添加到动画师中。