我正在使用基于来自nghialv(https://github.com/nghialv/Sapporo)的Sapporo的日历来实现集合视图,以实现完全不同的目的。我想定义将在10秒帧内在六个不同通道中发送的脉冲。因此,我有一个Seconds vs. Channels的PulseCollectionView。
我希望使用本教程长按后实现单元格拖动:http://karmadust.com/drag-and-rearrange-uicollectionviews-through-layouts/。
我的问题是,当我长按一个单元格时,我收到错误:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSConcreteMapTable gestureRecognizerShouldBegin:]: unrecognized selector sent to instance 0x7b6bea30'
我试图通过调试器找到错误的行,但似乎我找不到它。它甚至在到达gestureRecognizerShouldBegin
之前就中断了这是我的布局文件:
import UIKit
import Sapporo
let SecondsMaximum : CGFloat = 10
let ChannelsMaximum : CGFloat = 6
let HorizontalSpacing : CGFloat = 10
let VerticalSpacing : CGFloat = 4
//let WidthPerSecond : CGFloat = 100
let SecondHeaderHeight : CGFloat = 40
let ChannelHeaderWidth : CGFloat = 100
class PulseLayout: SALayout, UIGestureRecognizerDelegate {
override func collectionViewContentSize() -> CGSize {
let contentHeight = collectionView!.bounds.size.height
let contentWidth = collectionView!.bounds.size.width
return CGSizeMake(contentWidth, contentHeight)
}
override func awakeFromNib() {
super.awakeFromNib()
self.setupGestureRecognizer()
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()
// Cells
let visibleIndexPaths = indexPathsOfItemsInRect(rect)
layoutAttributes += visibleIndexPaths.map {
self.layoutAttributesForItemAtIndexPath($0)
}
// Supplementary views
let secondHeaderViewIndexPaths = indexPathsOfSecondHeaderViewsInRect(rect)
layoutAttributes += secondHeaderViewIndexPaths.map {
self.layoutAttributesForSupplementaryViewOfKind(PulseHeaderType.Second.rawValue, atIndexPath: $0)
}
let channelHeaderViewIndexPaths = indexPathsOfChannelHeaderViewsInRect(rect)
layoutAttributes += channelHeaderViewIndexPaths.map {
self.layoutAttributesForSupplementaryViewOfKind(PulseHeaderType.Channel.rawValue, atIndexPath: $0)
}
// Decoration Views
let verticalGridViewIndexPaths = indexPathsOfVerticalGridLineViewsInRect(rect)
layoutAttributes += verticalGridViewIndexPaths.map {
self.layoutAttributesForDecorationViewOfKind(GridLineType.Vertical.rawValue, atIndexPath: $0)
}
let horizontalGridViewIndexPaths = indexPathsOfHorizontalGridLineViewsInRect(rect)
layoutAttributes += horizontalGridViewIndexPaths.map {
self.layoutAttributesForDecorationViewOfKind(GridLineType.Horizontal.rawValue, atIndexPath: $0)
}
return layoutAttributes
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
var attributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
if let event = (getCellModel(indexPath) as? PulseEventCellModel)?.event {
attributes.frame = frameForEvent(event)
}
return attributes
}
override func layoutAttributesForSupplementaryViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
var attributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: elementKind, withIndexPath: indexPath)
let totalHeight = collectionViewContentSize().height
let totalWidth = collectionViewContentSize().width
if elementKind == PulseHeaderType.Second.rawValue {
let availableWidth = totalWidth - ChannelHeaderWidth
let widthPerSecond = availableWidth / SecondsMaximum
attributes.frame = CGRectMake(ChannelHeaderWidth + widthPerSecond * CGFloat(indexPath.item), 0, widthPerSecond, SecondHeaderHeight)
attributes.zIndex = -10
} else if elementKind == PulseHeaderType.Channel.rawValue {
let availableHeight = totalHeight - SecondHeaderHeight
let heightPerChannel = availableHeight / ChannelsMaximum
attributes.frame = CGRectMake(0, SecondHeaderHeight + (heightPerChannel * CGFloat(indexPath.item)), ChannelHeaderWidth, heightPerChannel)
attributes.zIndex = -10
}
return attributes
}
override func layoutAttributesForDecorationViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
var attributes = UICollectionViewLayoutAttributes(forDecorationViewOfKind: elementKind, withIndexPath: indexPath)
let totalHeight = collectionViewContentSize().height
let totalWidth = collectionViewContentSize().width
if elementKind == GridLineType.Vertical.rawValue {
let availableWidth = totalWidth - ChannelHeaderWidth
let widthPerSecond = availableWidth / SecondsMaximum
attributes.frame = CGRectMake(ChannelHeaderWidth + widthPerSecond * CGFloat(indexPath.item), 0.0, 1, totalHeight)
attributes.zIndex = -11
} else if elementKind == GridLineType.Horizontal.rawValue {
let availableHeight = totalHeight - SecondHeaderHeight
let heightPerChannel = availableHeight / ChannelsMaximum
attributes.frame = CGRectMake(0.0, SecondHeaderHeight + (heightPerChannel * CGFloat(indexPath.item)), totalWidth, 1)
attributes.zIndex = -11
}
return attributes
}
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
return true
}
struct Bundle {
var offset : CGPoint = CGPointZero
var sourceCell : UICollectionViewCell
var representationImageView : UIView
var currentIndexPath : NSIndexPath
var canvas : UIView
}
var bundle : Bundle?
var canvas : UIView? {
didSet {
if canvas != nil {
// self.calculateBorders()
}
}
}
func setupGestureRecognizer() {
if let collectionView = self.collectionView {
let longPressGestureRecogniser = UILongPressGestureRecognizer(target: self, action: "handleGesture:")
longPressGestureRecogniser.minimumPressDuration = 0.2
longPressGestureRecogniser.delegate = self
collectionView.addGestureRecognizer(longPressGestureRecogniser)
if self.canvas == nil {
self.canvas = self.collectionView!.superview
}
}
}
func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if let ca = self.collectionView!.superview {
if let cv = self.collectionView {
let pointPressedInCanvas = gestureRecognizer.locationInView(ca)
for cell in cv.visibleCells() as! [PulseEventCell] {
let cellInCanvasFrame = ca.convertRect(cell.frame, fromView: cv)
if CGRectContainsPoint(cellInCanvasFrame, pointPressedInCanvas ) {
let representationImage = cell.snapshotViewAfterScreenUpdates(true)
representationImage.frame = cellInCanvasFrame
let offset = CGPointMake(pointPressedInCanvas.x - cellInCanvasFrame.origin.x, pointPressedInCanvas.y - cellInCanvasFrame.origin.y)
let indexPath : NSIndexPath = cv.indexPathForCell(cell as UICollectionViewCell)!
self.bundle = Bundle(offset: offset, sourceCell: cell, representationImageView:representationImage, currentIndexPath: indexPath, canvas: ca)
break
}
}
}
}
return (self.bundle != nil)
}
func handleGesture(gesture: UILongPressGestureRecognizer) -> Void {
if let bundle = self.bundle {
let dragPointOnCanvas = gesture.locationInView(self.collectionView!.superview)
if gesture.state == UIGestureRecognizerState.Began {
bundle.sourceCell.hidden = true
self.canvas?.addSubview(bundle.representationImageView)
UIView.animateWithDuration(0.5, animations: { () -> Void in
bundle.representationImageView.alpha = 0.8
});
}
if gesture.state == UIGestureRecognizerState.Changed {
// Update the representation image
var imageViewFrame = bundle.representationImageView.frame
var point = CGPointZero
point.x = dragPointOnCanvas.x - bundle.offset.x
point.y = dragPointOnCanvas.y - bundle.offset.y
imageViewFrame.origin = point
bundle.representationImageView.frame = imageViewFrame
let dragPointOnCollectionView = gesture.locationInView(self.collectionView)
if let indexPath : NSIndexPath = self.collectionView?.indexPathForItemAtPoint(dragPointOnCollectionView) {
// self.checkForDraggingAtTheEdgeAndAnimatePaging(gesture)
if indexPath.isEqual(bundle.currentIndexPath) == false {
// If we have a collection view controller that implements the delegate we call the method first
/*if let delegate = self.collectionView!.delegate as UICollectionViewDelegate? {
delegate.moveDataItem(bundle.currentIndexPath, toIndexPath: indexPath)
}*/
self.collectionView!.moveItemAtIndexPath(bundle.currentIndexPath, toIndexPath: indexPath)
self.bundle!.currentIndexPath = indexPath
}
}
}
if gesture.state == UIGestureRecognizerState.Ended {
bundle.sourceCell.hidden = false
bundle.representationImageView.removeFromSuperview()
if let delegate = self.collectionView!.delegate as UICollectionViewDelegate? { // if we have a proper data source then we can reload and have the data displayed correctly
self.collectionView!.reloadData()
}
self.bundle = nil
}
}
}
}
extension PulseLayout {
func indexPathsOfEventsBetweenMinSecondIndex(minChannelIndex: Int, maxChannelIndex: Int, minStartSecond: Int, maxStartSecond: Int) -> [NSIndexPath] {
var indexPaths = [NSIndexPath]()
if let cellmodels = getCellModels(0) as? [PulseEventCellModel] {
for i in 0..<cellmodels.count {
let event = cellmodels[i].event
if event.channel >= minChannelIndex && event.channel <= maxChannelIndex && event.startSecond >= minStartSecond && event.startSecond <= maxStartSecond {
let indexPath = NSIndexPath(forItem: i, inSection: 0)
indexPaths.append(indexPath)
}
}
}
return indexPaths
}
func indexPathsOfItemsInRect(rect: CGRect) -> [NSIndexPath] {
let minVisibleChannel = channelIndexFromYCoordinate(CGRectGetMinY(rect))
let maxVisibleChannel = channelIndexFromYCoordinate(CGRectGetMaxY(rect))
let minVisibleSecond = secondIndexFromXCoordinate(CGRectGetMinX(rect))
let maxVisibleSecond = secondIndexFromXCoordinate(CGRectGetMaxX(rect))
return indexPathsOfEventsBetweenMinSecondIndex(minVisibleChannel, maxChannelIndex: maxVisibleChannel, minStartSecond: minVisibleSecond, maxStartSecond: maxVisibleSecond)
}
func channelIndexFromYCoordinate(yPosition: CGFloat) -> Int {
let contentHeight = collectionViewContentSize().height - SecondHeaderHeight
let HeightPerChannel = contentHeight / ChannelsMaximum
let channelIndex = max(0, Int((yPosition - SecondHeaderHeight) / HeightPerChannel))
return channelIndex
}
func secondIndexFromXCoordinate(xPosition: CGFloat) -> Int {
let contentWidth = collectionViewContentSize().width - ChannelHeaderWidth
let WidthPerSecond = contentWidth / SecondsMaximum
let secondIndex = max(0, Int((xPosition - ChannelHeaderWidth) / WidthPerSecond))
return secondIndex
}
func indexPathsOfSecondHeaderViewsInRect(rect: CGRect) -> [NSIndexPath] {
if CGRectGetMinY(rect) > SecondHeaderHeight {
return []
}
let minSecondIndex = secondIndexFromXCoordinate(CGRectGetMinX(rect))
let maxSecondIndex = secondIndexFromXCoordinate(CGRectGetMaxX(rect))
return (minSecondIndex...maxSecondIndex).map { index -> NSIndexPath in
NSIndexPath(forItem: index, inSection: 0)
}
}
func indexPathsOfChannelHeaderViewsInRect(rect: CGRect) -> [NSIndexPath] {
if CGRectGetMinX(rect) > ChannelHeaderWidth {
return []
}
let minChannelIndex = channelIndexFromYCoordinate(CGRectGetMinY(rect))
let maxChannelIndex = channelIndexFromYCoordinate(CGRectGetMaxY(rect))
return (minChannelIndex...maxChannelIndex).map { index -> NSIndexPath in
NSIndexPath(forItem: index, inSection: 0)
}
}
func indexPathsOfVerticalGridLineViewsInRect(rect: CGRect) -> [NSIndexPath] {
if CGRectGetMaxX(rect) < SecondHeaderHeight {
return []
}
let minSecondIndex = secondIndexFromXCoordinate(CGRectGetMinX(rect))
let maxSecondIndex = secondIndexFromXCoordinate(CGRectGetMaxX(rect))
return (minSecondIndex...maxSecondIndex).map { index -> NSIndexPath in
NSIndexPath(forItem: index, inSection: 0)
}
}
func indexPathsOfHorizontalGridLineViewsInRect(rect: CGRect) -> [NSIndexPath] {
if CGRectGetMaxY(rect) < ChannelHeaderWidth {
return []
}
let minChannelIndex = channelIndexFromYCoordinate(CGRectGetMinY(rect))
let maxChannelIndex = channelIndexFromYCoordinate(CGRectGetMaxY(rect))
return (minChannelIndex...maxChannelIndex).map { index -> NSIndexPath in
NSIndexPath(forItem: index, inSection: 0)
}
}
func frameForEvent(event: PulseEvent) -> CGRect {
let totalHeight = collectionViewContentSize().height - SecondHeaderHeight
let HeightPerChannel = totalHeight / ChannelsMaximum
let contentWidth = collectionViewContentSize().width - ChannelHeaderWidth
let WidthPerSecond = contentWidth / SecondsMaximum
var frame = CGRectZero
frame.origin.x = ChannelHeaderWidth + WidthPerSecond * CGFloat(event.startSecond)
frame.origin.y = SecondHeaderHeight + HeightPerChannel * CGFloat(event.channel)
frame.size.height = HeightPerChannel
frame.size.width = CGFloat(event.durationInSeconds) * WidthPerSecond
frame = CGRectInset(frame, 2.0, HeightPerChannel/8.0)
return frame
}
}
这是我的ViewController:
import UIKit
import Sapporo
enum PulseHeaderType: String {
case Second = "SecondHeaderView"
case Channel = "ChannelHeaderView"
}
enum GridLineType: String {
case Vertical = "VerticalGridLineView"
case Horizontal = "HorizontalGridLineView"
}
class PulseViewController: UIViewController {
@IBOutlet var collectionView: UICollectionView!
lazy var sapporo: Sapporo = { [unowned self] in
return Sapporo(collectionView: self.collectionView)
}()
override func viewDidLoad() {
super.viewDidLoad()
sapporo.delegate = self
sapporo.registerNibForClass(PulseEventCell)
sapporo.registerNibForSupplementaryClass(PulseHeaderView.self, kind: PulseHeaderType.Second.rawValue)
sapporo.registerNibForSupplementaryClass(PulseHeaderView.self, kind: PulseHeaderType.Channel.rawValue)
let layout = PulseLayout()
layout.registerClass(GridLineView.self, forDecorationViewOfKind: GridLineType.Vertical.rawValue)
layout.registerClass(GridLineView.self, forDecorationViewOfKind: GridLineType.Horizontal.rawValue)
sapporo.setLayout(layout)
let randomEvent = { () -> PulseEvent in
let randomID = arc4random_uniform(10000)
let title = "Event \(randomID)"
let randomChannel = Int(arc4random_uniform(6))
let randomStartSecond = Int(arc4random_uniform(8))
let randomDuration = Int(arc4random_uniform(2) + 1)
return PulseEvent(title: title, channel: randomChannel, startSecond: randomStartSecond, durationInSeconds: randomDuration)
}
let cellmodels = (0...20).map { _ -> PulseEventCellModel in
let event = randomEvent()
return PulseEventCellModel(event: event) { _ in
println("Selected event: \(event.title)")
}
}
sapporo[0].append(cellmodels)
sapporo.bump()
}
}
extension PulseViewController: SapporoDelegate {
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
if kind == PulseHeaderType.Second.rawValue {
let view = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: PulseHeaderView.reuseIdentifier, forIndexPath: indexPath) as! PulseHeaderView
view.titleLabel.text = "\(indexPath.item)"
return view
}
let view = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: PulseHeaderView.reuseIdentifier, forIndexPath: indexPath) as! PulseHeaderView
view.titleLabel.text = "Channel \(indexPath.item + 1)"
return view
}
}
你能帮我看看我错过的东西吗?
问候,
C
答案 0 :(得分:0)
我刚才解决了这个问题。:
struct Bundle
func setupGestureRecognizer()
func gestureRecognizerShouldBegin
func handleGesture(手势:UILongPressGestureRecognizer) - &gt;空隙
应该全部放在ViewController中而不是布局文件中。