Добавлен UITableViewCell GestureRecognizer Делегат не работает

Текущий прогресс

У меня есть таблица, где жест смахивания должен вызвать новый ViewController. Это на самом деле работает и может вызывать segue и загружать новый VC (весь приведенный ниже код должен быть полезен всем, кто просто хочет добавить жест).

проблема

Но я хочу передать новому ViewController значение индекса swiped-ячейки, и я не смог этого сделать или воспроизвести методы, описанные в руководствах, которые я нашел.

В моей таблице используется собственный класс ячеек, в который добавляется жест. Добавлен жест, я его протестировал, и он использует делегат для запуска функции в главном VC и запуска перехода.

Ошибка определенно, кажется, происходит в самом начале, когда я пытаюсь перехватить первоначального отправителя и передать его через делегата.

Код

Вот код моей пользовательской ячейки:

class CustomTableViewCell: UITableViewCell {

@IBOutlet var nameLabel: UILabel!
@IBOutlet var descLabel: UILabel!

var delegate: mainViewDelegate!

override func awakeFromNib() {
    super.awakeFromNib()
    // Initialization code

    //Create 'swipeLeft' variable, provide action (swipedLeft) and add to cell
    let swipeLeft: UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "swipedLeft")
    swipeLeft.direction = UISwipeGestureRecognizerDirection.Left
    self.addGestureRecognizer(swipeLeft)

}

override func setSelected(selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)
    // Configure the view for the selected state
}

func swipedLeft (sender: UISwipeGestureRecognizer) {
    println("swipe detected, cell function run")
    if(self.delegate != nil){
        self.delegate.cellSwipedLeft(sender)
    }
}   
}

Протокол:

protocol mainViewDelegate {
    func cellSwipedLeft (UISwipeGestureRecognizer)
}

Основной заголовок ViewController:

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, mainViewDelegate {

И я также добавил необходимую строку делегата в основной функции cellForRowAtIndexPath основного контроллера:

func tableView... 
...  (edited out to save space)
cell.delegate = self
}

Основная функция ВК:

func cellSwipedLeft (sender: UISwipeGestureRecognizer) {
    println("cellSwipedLeft main VC func ran")
    performSegueWithIdentifier("modalTo_HomeTaskAction", sender: nil)
}

Теперь все это работает, если я ничего не передаю в параметрах, но когда я добавляю UISwipeGestureRecognizer, он завершается с ошибкой потока 1: сигнал SIGABRT. Моя цель - успешно передать жест, затем я добавлю приведенный ниже код, чтобы получить индекс, и использую prepareForSegue, чтобы передать его моему ВК:

let gesture = sender as! UISwipeGestureRecognizer
let cell = gesture.view! as! CustomTableViewCell_F2
let indexPath = tableView.indexPathForCell(cell)

Таким образом, большой вопрос, почему передача UISwipeGestureRecognizer дает мне ошибку, которая начинается так:

2015-08-21 03:23:39.566 AppName[10170:945334] -[AppName.CustomTableViewCell swipedLeft]: unrecognized selector sent to instance 0x7fb149766560
2015-08-21 03:23:39.619 AppName[10170:945334] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-   [AppName.CustomTableViewCell swipedLeft]: unrecognized selector sent to instance 0x7fb149766560'
*** First throw call stack:
(
    0   CoreFoundation                          0x0000000106dfcc65 __exceptionPreprocess + 165
1   libobjc.A.dylib                     0x0000000108ba9bb7 

1 ответ

Решение

Дейв, вот более простой способ сделать это без протоколов, и вместо этого мы используем блоки. в вашем пользовательском UITableViewCell мы делаем это:

НАСТРОЙКА:

import Foundation
import UIKit

class EXTableViewCell: UITableViewCell {
    @IBOutlet var nameLabel: UILabel!
    @IBOutlet var descLabel: UILabel!
    var doWork: (() -> Void)?
    func swipedLeft (sender: UISwipeGestureRecognizer) {
        if let callback = self.doWork {
            println("swipe detected, cell function run")
            callback ()
        }
    }
    override func awakeFromNib() {
        super.awakeFromNib()
        let swipeLeft: UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "swipedLeft")
        swipeLeft.direction = UISwipeGestureRecognizerDirection.Left
        self.addGestureRecognizer(swipeLeft)
    }
    override func setSelected(selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }
}

Пользовательский ViewController:

import UIKit

class DetailViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
    }
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return  0 //replace with the correct info
    }
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 4 //replace with the correct info
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! FFViewCell
          cell.doWork = {
              () -> Void in
              self.doStuff(indexPath.row)
        }
          cell.labelMessage.text = items[indexPath.row] as String
          return cell
      }
      func doStuff(integer: NSInteger) {
          println("i got here \(integer)")
      }
}

Как это работает:

Видите ли, мы объявляем свойство блока, которое позволяет нам передавать пустой вызов "функции" (PER SE) любому "EXTableViewCell", который вы создаете в своем UIViewController.

Итак, в пользовательском UITableViewCell мы объявляем свойство void block:

var doWork: (() -> Void)?

Мы прикрепляем сенсорный обработчик к ячейке:

func swipedLeft (sender: UISwipeGestureRecognizer) {
        if let callback = self.doWork {
            println("swipe detected, cell function run")
            callback ()
        }
    }

Затем мы вызываем этот обработчик внутри или в основном UIViewController и устанавливаем это свойство при настройке нашей ячейки табличного представления:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! EXTableViewCell
        cell.doWork = {
            () -> Void in
            self.doStuff()
        }
        return cell
    }

В частности:

cell.doWork = {
            () -> Void in
            self.doStuff()
        }

Затем мы, очевидно, настроили функцию "doStuff", чтобы делать то, что мы хотим, чтобы она выполнялась в нашем UIViewController:

func doStuff() {
        println("i got here")
    }

Никаких протоколов, никаких беспорядков, никаких проблем с делегированием, все функции на основе блоков. Я не тестировал этот код с реальным UIViewController, однако в Objective-C это работает безупречно, и перед публикацией этого кода я убедился, что он компилируется.

В качестве краткого примечания к удивительности блоков, почти все, что кажется очень сложным с делегированием и протоколами, может быть выполнено с блоками, сложная часть - это использование блоков и понимание их универсальности. Вероятно, самой приятной частью является тот факт, что вы можете использовать "свойства блока", как обычное свойство, но с дополнительным преимуществом присоединения события-обработчика к объекту, которому принадлежит свойство блока. В любом случае, вам может понадобиться еще одна вещь, но это очень просто:

Возможно, вам потребуется запустить ячейку пользовательского табличного представления следующим образом, сделав ее делегатом UIGestureRecognizer следующим образом:

class EXTableViewCell: UITableViewCell, UIGestureRecognizerDelegate {

и вам может потребоваться объявить ваш распознаватель жестов в классе ячейки пользовательского табличного представления, чтобы он выглядел примерно так:

swipeLeft.delegate = self
swipeLeft.cancelsTouchesInView = false

Кроме того, если у вас возникли проблемы с выполнением этого, то дайте мне знать, я посмотрю, не смогу ли я просто реализовать полную реализацию.

Рабочий пример, проверен и готов к работе:

Пользовательский tableViewCell:

import Foundation
import UIKit

class FFViewCell: UITableViewCell, UIGestureRecognizerDelegate {

    var labelMessage = UILabel()

    var doWork: (() -> Void)?

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        let swipeLeft: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "this")

        swipeLeft.delegate = self
        swipeLeft.cancelsTouchesInView = false

        self.addGestureRecognizer(swipeLeft)

        labelMessage.setTranslatesAutoresizingMaskIntoConstraints(false)
        contentView.addSubview(labelMessage)
        var viewsDict =  ["labelMessage" : labelMessage]

        contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[labelMessage]|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDict))
        contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-20-[labelMessage]", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDict))

    }
    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    func this () {
        if let callback = self.doWork {
            println("swipe detected, cell function run")
            callback ()
        }
    }

}

AppDelegate:

import UIKit

@UIApplicationMain

class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var rootViewController: UINavigationController?
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        window = UIWindow(frame: UIScreen.mainScreen().bounds)

        rootViewController = UINavigationController(rootViewController: ViewController())
        if let window = window {
            window.backgroundColor = UIColor.whiteColor()

            window.rootViewController = rootViewController

            window.makeKeyAndVisible()
        }
        return true
    }
    func applicationWillResignActive(application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
    }

    func applicationDidEnterBackground(application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }

    func applicationWillEnterForeground(application: UIApplication) {
        // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
    }

    func applicationDidBecomeActive(application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }

    func applicationWillTerminate(application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }


}

ViewController:

import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    var tableView : UITableView?
    var items = ["asdf","asdf","asdf","asdf","asdf"]

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }
    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView = UITableView(frame: CGRectMake(0, 0, 414, 736), style: UITableViewStyle.Plain)
        tableView!.delegate = self
        tableView!.dataSource = self
        tableView!.registerClass(FFViewCell.self, forCellReuseIdentifier: "Cell")
        self.view .addSubview(tableView!)
    }
    override func loadView() {
        var stuf = UIView()
        stuf.frame = CGRectMake(0, 0, 414, 736)
        stuf.backgroundColor = UIColor .redColor()
        self.view = stuf
    }
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count;
    }
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    func tableView(tableView:UITableView, heightForRowAtIndexPath indexPath:NSIndexPath)->CGFloat
    {
        return 44
    }   
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! FFViewCell
        cell.doWork = {
            () -> Void in
            self.doStuff()
        }
        cell.labelMessage.text = items[indexPath.row] as String
        return cell
    }
    func doStuff() {
        println("i got here")
    }
}

Вот код "жеста смахивания", Дейв:

import Foundation
import UIKit

class FFViewCell: UITableViewCell, UIGestureRecognizerDelegate {

    var labelMessage = UILabel()

    var doWork: (() -> Void)?

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        let swipeLeft: UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "this")
        swipeLeft.direction = UISwipeGestureRecognizerDirection.Left
        swipeLeft.delegate = self
        swipeLeft.cancelsTouchesInView = false
        self.addGestureRecognizer(swipeLeft)

        labelMessage.setTranslatesAutoresizingMaskIntoConstraints(false)
        contentView.addSubview(labelMessage)
        var viewsDict =  ["labelMessage" : labelMessage]

        contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[labelMessage]|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDict))
        contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-20-[labelMessage]", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDict))

    }
    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    func this () {
        if let callback = self.doWork {
            println("swipe detected, cell function run")
            callback ()
        }
    }

}
Другие вопросы по тегам