gestureRecognizerShouldBegin:发送到实例

时间:2015-08-31 14:28:01

标签: ios swift uigesturerecognizer

我正在使用基于来自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

1 个答案:

答案 0 :(得分:0)

我刚才解决了这个问题。:

  

struct Bundle

     

func setupGestureRecognizer()

     

func gestureRecognizerShouldBegin

     

func handleGesture(手势:UILongPressGestureRecognizer) - &gt;空隙

应该全部放在ViewController中而不是布局文件中。