2017-02-14 33 views
2

Все еще очень быстрый Noob, я искал подходящий способ/лучшую практику для управления удалением строк в моем UITableView (который использует пользовательские UserCell s) на основе нажимая UIButton внутри UserCell, используя делегацию, которая, кажется, является самым чистым способом сделать это.Правильно делегировать действие кнопки из пользовательской ячейки, чтобы удалить строки в UITableView

Я последовал этому примеру: UITableViewCell Buttons with action

То, что я

UserCell класс

protocol UserCellDelegate { 

    func didPressButton(_ tag: Int) 
} 

class UserCell: UITableViewCell { 

    var delegate: UserCellDelegate? 
    let addButton: UIButton = { 

     let button = UIButton(type: .system) 

     button.setTitle("Add +", for: .normal) 
     button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside) 
     button.translatesAutoresizingMaskIntoConstraints = false 
     return button 
    }() 

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 
     super.init(style: .subtitle, reuseIdentifier: reuseIdentifier) 

     addSubview(addButton) 
     addButton.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -6).isActive = true 
     addButton.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true 
     addButton.heightAnchor.constraint(equalToConstant: self.frame.height/2).isActive = true 
     addButton.widthAnchor.constraint(equalToConstant: self.frame.width/6).isActive = true 
    } 

    func buttonPressed(_ sender: UIButton) { 

     delegate?.didPressButton(sender.tag) 
    } 
} 

TableViewController класс:

class AddFriendsScreenController: UITableViewController, UserCellDelegate { 

    let cellId = "cellId" 
    var users = [User]() 

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
     return users.count 
    } 

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 

     let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserCell 

     cell.delegate = self 
     cell.tag = indexPath.row 

     return cell 
    } 

    func didPressButton(_ tag: Int) { 

     let indexPath = IndexPath(row: tag, section: 0) 

     users.remove(at: tag) 
     tableView.deleteRows(at: [indexPath], with: .fade) 
    } 
} 

где User с в users добавляются с вызовом базы данных в контроллере представления.

Мои вопросы

  • Кнопка в каждой строке таблицы View кликабельна, но ничего не делает
  • кнопка кажется, интерактивными только при выполнении «длинное нажатие», то есть палец остается на нем в течение 0,5 с
  • Будет ли этот метод гарантировать, что indexPath будет обновлен и не выйдет из-под контроля? То есть если строка удалена с индексом 0, удалит ли «новую» строку с индексом 0 или удалит строку с индексом 1?

То, что я хочу

Будучи в состоянии нажать на кнопку в каждой строке таблицы, которая будет удалить его из Tableview.

Я, должно быть, получаю что-то нехорошее и очень ценю, если бы рыцарь Свифт мог просветить меня.

Большое спасибо заранее.

ответ

0

Есть по крайней мере 3 проблемы в вашем коде:

  • В UserCell вы должны позвонить:
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside) 

как только ваша ячейка была создана (скажем, от вашей реализации init(style:reuseIdentifier:)), так что self ссылается на фактический экземпляр UserCell.

  • В AddFriendsScreenController «s tableView(_:cellForRowAt:) вы устанавливаете тег самой клетки (cell.tag = indexPath.row), но в ваших UserCell» ы buttonPressed(_:) вы используете тег кнопки. Вы должны изменить эту функцию, чтобы быть:
func buttonPressed(_ sender: UIButton) { 

    //delegate?.didPressButton(sender.tag) 
    delegate?.didPressButton(self.tag) 
} 
  • Как вы уже догадались, и как за Prema Janoti's answer вы должны перезагрузить ваше представление таблицы, как только вы удалили строку как теги ваших клеток будут синхронизированы с их ссылаясь на indexPaths. В идеале вам следует избегать использования указательных путей для идентификации ячеек, но это другой предмет.

EDIT:
Простое решение, чтобы избежать теги быть синхронизированы с индексом трактов ассоциировать каждую ячейку с User объектом, который они, как предполагается, представляют собой:

  • Первая добавить user собственности на ваш UserCell класс:
class UserCell: UITableViewCell { 

    var user = User() // default with a dummy user 

    /* (...) */ 
} 
  • Установите это свойство для правильного User объекта изнутри tableView(_:cellForRowAt:):
//cell.tag = indexPath.row 
cell.user = self.users[indexPath.row] 
  • Изменить подпись вашего метода протокола UserCellDelegate передать user имущества, хранящееся на клетку, а не его tag :
protocol UserCellDelegate { 

    //func didPressButton(_ tag: Int) 
    func didPressButtonFor(_ user: User) 

} 
  • Изменить UserCell «s buttonPressed(_:) действия соответственно:
func buttonPressed(_ sender: UIButton) { 

    //delegate?.didPressButton(sender.tag) 
    //delegate?.didPressButton(self.tag) 
    delegate?.didPressButtonFor(self.user) 
} 
  • Наконец, в вашем AddFriendsScreenController, определить правый ряд, чтобы удалить на основе User позиции в источнике данных:
//func didPressButton(_ tag: Int) { /* (...) */ } // Scrap this. 

func didPressButtonFor(_ user: User) { 

    if let index = users.index(where: { $0 === user }) { 

     let indexPath = IndexPath(row: index, section: 0) 

     users.remove(at: index) 
     tableView.deleteRows(at: [indexPath], with: .fade) 
    } 
} 

Примечание (optional binding) и тройной === (identity operator).

Этот недостаток этого подхода заключается в том, что он создаст плотную связь между классами User и UserCell. Лучшая практика диктует использование более сложного MVVM pattern, например, но это действительно другой вопрос ...

+0

спасибо, что указал это, я изменил соответствующим образом, и он отлично работает. Я изменил 'cell.tag' на' cell.addButton.tag' вместо того, что вы предложили, основываясь на том же принципе. Когда вы говорите, что следует избегать использования указательных путей для идентификации ячеек, какие проблемы могут возникнуть в результате потенциального создания и есть ли стандартный/умный подход к нему? Еще раз спасибо ! – Herakleis

+0

Я обновил свой ответ, чтобы предоставить альтернативу использованию 'indexPaths' - с оговоркой. Надеюсь, это поможет в качестве отправной точки! – Olivier

+0

Спасибо @Olivier, это выглядит просто. Вы проверите это! – Herakleis

0

попробовать это -

обновление didPressButton метод, как показано ниже -

func didPressButton(_ tag: Int) { 
    let indexPath = IndexPath(row: tag, section: 0) 
    users.remove(at: tag) 
    tableView.reloadData() 
} 
1

В Интернете существует много плохого/старого кода, даже на SO. На то, что вы опубликовали, написано «плохая практика».Итак, сначала несколько указателей:

  • Избегайте UITableViewController любой ценой. Иметь нормальный контроллер представления с табличным на нем
  • Делегатов должны всегда быть weak, если вы не 100% уверены, что вы делаете
  • быть более конкретными при именовании протоколов и методов протокола
  • держать все private, если это возможно, если нет, то используйте fileprivate. Используйте только остальные, если вы на 100% уверены, что это значение, которое вы хотите выставить.
  • Избегайте использования тегов любой ценой

Ниже приведен пример ответственного представления таблицы с одним типом клеток, который имеет кнопку, которая удаляет текущую ячейку при нажатии. Весь код может быть вставлен в ваш начальный файл ViewController при создании нового проекта. В раскадровке в виде таблицы добавляется ограничение слева, справа, сверху, снизу и выход на контроллер вида. Также ячейка добавляется в представление таблицы с помощью кнопки в ней, которая имеет выход в ячейку MyTableViewCell, а ее идентификатор установлен в «MyTableViewCell».

Остальное следует пояснить в комментариях.

class ViewController: UIViewController { 

    @IBOutlet private weak var tableView: UITableView? // By default use private and optional. Always. For all outlets. Only expose it if you really need it outside 

    fileprivate var myItems: [String]? // Use any objects you need. 


    override func viewDidLoad() { 
     super.viewDidLoad() 

     // Attach table viw to self 
     tableView?.delegate = self 
     tableView?.dataSource = self 

     // First refresh and reload the data 
     refreshFromData() // This is to ensure no defaults are visible in the beginning 
     reloadData() 
    } 

    private func reloadData() { 

     myItems = nil 

     // Simulate a data fetch 
     let queue = DispatchQueue(label: "test") // Just for the async example 

     queue.async { 
      let items: [String] = (1...100).flatMap { "Item: \($0)" } // Just generate some string 
      Thread.sleep(forTimeInterval: 3.0) // Wait 3 seconds 
      DispatchQueue.main.async { // Go back to main thread 
       self.myItems = items // Assign data source to self 
       self.refreshFromData() // Now refresh the table view 
      } 
     } 
    } 

    private func refreshFromData() { 
     tableView?.reloadData() 
     tableView?.isHidden = myItems == nil 
     // Add other stuff that need updating here if needed 
    } 

    /// Will remove an item from the data source and update the array 
    /// 
    /// - Parameter item: The item to remove 
    fileprivate func removeItem(item: String) { 

     if let index = myItems?.index(of: item) { // Get the index of the object 

      tableView?.beginUpdates() // Begin updates so the table view saves the current state 
      myItems = myItems?.filter { $0 != item } // Update our data source first 
      tableView?.deleteRows(at: [IndexPath(row: index, section: 0)], with: .fade) // Do the table view cell modifications 
      tableView?.endUpdates() // Commit the modifications 
     } 

    } 

} 

// MARK: - UITableViewDelegate, UITableViewDataSource 

extension ViewController: UITableViewDelegate, UITableViewDataSource { 

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
     return myItems?.count ?? 0 
    } 

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
     if let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell", for: indexPath) as? MyTableViewCell { 
      cell.item = myItems?[indexPath.row] 
      cell.delegate = self 
      return cell 
     } else { 
      return UITableViewCell() 
     } 
    } 

} 

// MARK: - MyTableViewCellDelegate 

extension ViewController: MyTableViewCellDelegate { 

    func myTableViewCell(pressedMainButton sender: MyTableViewCell) { 

     guard let item = sender.item else { 
      return 
     } 

     // Delete the item if main button is pressed 
     removeItem(item: item) 

    } 

} 



protocol MyTableViewCellDelegate: class { // We need ": class" so the delegate can be marked as weak 

    /// Called on main button pressed 
    /// 
    /// - Parameter sender: The sender cell 
    func myTableViewCell(pressedMainButton sender: MyTableViewCell) 

} 

class MyTableViewCell: UITableViewCell { 

    @IBOutlet private weak var button: UIButton? 

    weak var delegate: MyTableViewCellDelegate? // Must be weak or we can have a retain cycle and create a memory leak 

    var item: String? { 
     didSet { 
      button?.setTitle(item, for: .normal) 
     } 
    } 

    @IBAction private func buttonPressed(_ sender: Any) { 

     delegate?.myTableViewCell(pressedMainButton: self) 

    } 
} 

В вашем случае String должен быть заменен User. Рядом с этим вы будете иметь несколько изменений, таких как didSet в ячейке (например, button?.setTitle(item.name, for: .normal)), а метод фильтра должен использовать === или сравнить некоторые id или что-то в этом роде.

+0

Благодаря @Matic, он даст вам возможность изучить ваш пример. Любую причину «UITableViewController» можно избежать любой ценой? Огромное спасибо. – Herakleis

+0

@Herakleis Основная причина в том, что этот контроллер заменяет свой вид (controller.view) из традиционного UIView на свой подкласс UITableView, который представляет собой прокрутку. Это означает, что любое представление, которое вы добавляете в контроллер. Просмотр в качестве подвью, будет прокручиваться. Таким образом, все наложения - это боль, которую нужно добавить. Кроме того, он отключает весь элемент управления, где находится табличный вид, поэтому очень сложно разместить содержимое в некоторых других представлениях (верхние и нижние колонтитулы, боковые панели). С другой стороны, практически нет никакой пользы в использовании его по сравнению с табличным представлением, добавленным в качестве подсмотра контроллера нормального представления. –

+0

Проверьте, как я просто скрываю представление таблицы, прежде чем мы получим какие-либо данные. Мы могли бы добавить простой индикатор активности, который будет показан в том же случае и скрыт, когда данные будут получены. Мы могли бы сделать то же самое с некоторым наложением, если данные не получены (пустой массив) ... Скрытие вида таблицы в вашем случае означало бы скрыть все его подсмотры, наложения, которые вы добавили ... –

 Смежные вопросы

  • Нет связанных вопросов^_^