Implementando Swipe no UITableView com MGSwipeTableCell

Implementando Swipe no UITableView com MGSwipeTableCell

Um recurso muito interessante que vemos em muitos apps disponíveis na Apple Store é a possibilidade de deslizar a célula do tableView para a direita ou para a esquerda para habilitar as opções de ações para um determinado item.

Iremos aprimorar o nosso app Lista de Pendências para possibilitar editar e excluir uma pendência ao deslizarmos a mesma para a esquerda e a opção de mudar o status ao deslizar para a direita. O app Lista de Pendências pode ser encontrado neste post.

O recurso de adicionar botões de ação ao UITableViewCell é um pouco limitado no iOS então iremos utilizar o pod MGSwipeTableCell para nos auxiliar na implementação desta funcionalidade.

Instalando o MGSwipeTableCell

A biblioteca MGSwipeTableCell está disponível para ser instalada via CocoaPods com o comando pod 'MGSwipeTableCell' conforme exemplo abaixo.

# Uncomment this line to define a global platform for your project
platform :ios, '9.0'

target 'MGSwipeTableCellExample' do
  # Comment this line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for MGSwipeTableCellExample
  pod 'DZNEmptyDataSet'
  pod 'MGSwipeTableCell'
end

Para mais informações sobre o CocoaPods visite este post.

O MGSwipeTableCell disponibiliza as seguintes opções de transição ao deslizar a célula:

  • Border transition

    border

  • Clip transition

    clip

  • 3D transition

    3d

  • Static transition

    static

  • Drag transition

    drag

Alterando o projeto Lista de pendências

Começaremos alterando o fonte ToDoItemTableViewCell.swift para que nossa UITableViewCell herde do MGSwipeTableCell. Altere o fonte conforme o exemplo abaixo.

import MGSwipeTableCell
class ToDoItemTableViewCell: MGSwipeTableCell  {

Agora iremos fazer algumas alterações na classe ViewController para implementar o protocolo MGSwipeTableCellDelegate.

O protocolo MGSwipeTableCellDelegate possui os seguintes métodos:

  • swipeTableCell(_:canSwipe:)
  • swipeTableCell(_:didChangeSwipeState:gestureIsActive)
  • swipeTableCell(_:tappedButtonAtIndex:direction:fromExpansion:)
  • swipeTableCell(_:swipeButtonsForDirection:swipeSettings:expansionSettings:)
  • swipeTableCell(_:shouldHideSwipeOnTap:)
  • swipeTableCellWillBeginSwiping(_:)
  • swipeTableCellWillEndSwiping(_:)

Iremos implementar dois deles no nosso projeto.

No método swipeTableCell(_:swipeButtonsForDirection:swipeSettings:expansionSettings:) iremos criar os botões da nossa célula. Utilizaremos a classe MGSwipeButton para criar os botões.
Com o parâmetro direction podemos separar os botões que serão criados ao deslizar para a direita e para a esquerda. Do lado esquerdo teremos um botão para marcar a pendência como concluída. Caso o usuário deslize o botão até o meio o mesmo será ativado. Informamos isto com a propriedade enableSwipeBounces do parâmetro swipeSettings igual a true. Também precisamos informar o índice do botão que será ativado. No nosso caso temos apenas um botão e o índice será zero.
Do lado direito criaremos dois botões. Um para editar e um para excluir. O botão para excluir será ativado ao deslizar a célula até o meio da célula.
O método espera um array com o botões criados como retorno.

clipboard-checked clipboard-checked@2x clipboard-checked@3x clipboard-edit clipboard-edit@2x clipboard-edit@3x clipboard-remove clipboard-remove@2x clipboard-remove@3x

Imagens de Yannick Lung

// MARK: -
extension ViewController: MGSwipeTableCellDelegate {
  // MARK: - MGSwipeTableCellDelegate
  func swipeTableCell(cell: MGSwipeTableCell!, swipeButtonsForDirection direction: MGSwipeDirection, swipeSettings: MGSwipeSettings!, expansionSettings: MGSwipeExpansionSettings!) -> [AnyObject]! {
    switch direction {
      case .LeftToRight:
        // configure left buttons
        let checkButton = MGSwipeButton(title: "", icon: UIImage(named:"clipboard-checked"), backgroundColor: UIColor.greenColor())
        checkButton.tintColor = UIColor.blackColor()

        swipeSettings.transition = .Border
        swipeSettings.enableSwipeBounces = true

        expansionSettings.buttonIndex = 0

        return [ checkButton ]
      case .RightToLeft:
        // configure right buttons
        let editButton = MGSwipeButton(title: "", icon: UIImage(named: "clipboard-edit"), backgroundColor: UIColor.blueColor())
        editButton.tintColor = UIColor.whiteColor()
        let deleteButton = MGSwipeButton(title: "", icon: UIImage(named: "clipboard-remove"), backgroundColor: UIColor.redColor())
        deleteButton.tintColor = UIColor.whiteColor()

        swipeSettings.transition = .Border
        swipeSettings.enableSwipeBounces = true

        expansionSettings.buttonIndex = 0

        return [ deleteButton, editButton ]
    }
  }
}

O outro método que iremos utilizar é o swipeTableCell(_:tappedButtonAtIndex:direction:fromExpansion:). Nele iremos definir as ações que serão realizadas ao ativar os botões das células. Primeiramente verificamos a direção do deslizamento e depois o índice do botão para saber qual ação será executada.
Renomearemos o método updateItem(_:) para checkItem(_:) e criaremos os métodos deleteItem(_:) e editItemAction(_:).

func swipeTableCell(cell: MGSwipeTableCell!, tappedButtonAtIndex index: Int, direction: MGSwipeDirection, fromExpansion: Bool) -> Bool {
  if let indexPath = tableView.indexPathForCell(cell) {
    switch direction {
      case .LeftToRight:
        checkItem(indexPath)
      case .RightToLeft:
        if index == 0 {
          deleteItem(indexPath)
        } else if index == 1 {
          editItemAction(indexPath)
        } else {
          return false
        }
    }
  }

  return true
}

Os métodos auxiliares ficaram:

func editItemAction(indexPath: NSIndexPath) {
  let oldItem = selectItem(indexPath)
  let alert = UIAlertController(title: NSLocalizedString("ToDoList.Edit.title", value: "Editing To Do Item", comment: ""),
                                message: nil,
                                preferredStyle: .Alert)
  alert.addTextFieldWithConfigurationHandler { $0.text = oldItem.item }

  alert.addAction(UIAlertAction(title: NSLocalizedString("ToDoList.Add.Cancel", value: "Cancel", comment: ""), style: .Cancel, handler: nil))

  alert.addAction(UIAlertAction(title: NSLocalizedString("ToDoList.Add.OK", value: "Ok", comment: ""), style: .Default) { [unowned self, alert] _ in
    let newItem = ToDoItem(item: alert.textFields![0].text!, finalized: oldItem.finalized)
    self.editItem(oldItem, newItem: newItem)
  })

  alert.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItem

  presentViewController(alert, animated: true, completion: nil)
}
func editItem(olditem: ToDoItem, newItem: ToDoItem) {
  if let index = toDoList.indexOf(olditem) {
    toDoList[index] = newItem

    saveToDoList()
  }
}
func deleteItem(indexPath: NSIndexPath) {
  let item = selectItem(indexPath)

  if let index = toDoList.indexOf(item) {
    toDoList.removeAtIndex(index)

    saveToDoList()
  }
}
func checkItem(indexPath: NSIndexPath) {
  let item = selectItem(indexPath)

  if let index = toDoList.indexOf(item) {
    item.finalized = !item.finalized

    toDoList[index] = item

    saveToDoList()
  }
}

Também precisaremos alterar o método tableView(_:cellForRowAtIndexPath:) do protocolo UITableViewDataSource para definirmos a classe ViewController como delegate da célula ToDoItemTableViewCell.

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCellWithIdentifier(cellName) as! ToDoItemTableViewCell
  let item = selectItem(indexPath)

  cell.fillCell(item)

  cell.delegate = self

  return cell
}

Utilizando o MGSwipeTableCell sem protocolo

Alternativamente é possível utilizar o MGSwipeTableCell sem implementar o protocolo MGSwipeTableCellDelegate. Para isto basta definir os botões com as suas respectivas closures diretamente no método tableView(_:cellForRowAtIndexPath:) do protocolo UITableViewDataSource.

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCellWithIdentifier(cellName) as! ToDoItemTableViewCell
  let item = selectItem(indexPath)

  cell.fillCell(item)

  cell.delegate = self

  //configure left buttons
  let checkButton = MGSwipeButton(title: "", icon: UIImage(named:"clipboard-checked"), backgroundColor: UIColor.greenColor()) { [ weak self ] (sender: MGSwipeTableCell!) -> Bool in
    if let strongSelf = self,
        let indexPath = tableView.indexPathForCell(sender) {
      strongSelf.checkItem(indexPath)

      return true
    }

    return false
  }
  checkButton.tintColor = UIColor.blackColor()

  cell.leftButtons = [ checkButton ]
  cell.leftSwipeSettings.transition = .Border

  //configure right buttons
  let editButton = MGSwipeButton(title: "", icon: UIImage(named: "clipboard-edit"), backgroundColor: UIColor.blueColor()) { [ weak self ] (sender: MGSwipeTableCell!) -> Bool in
    if let strongSelf = self,
        let indexPath = tableView.indexPathForCell(sender) {
      strongSelf.editItemAction(indexPath)

      return true
    }

    return false
  }
  editButton.tintColor = UIColor.whiteColor()

  let deleteButton = MGSwipeButton(title: "", icon: UIImage(named: "clipboard-remove"), backgroundColor: UIColor.redColor()) { [ weak self ] (sender: MGSwipeTableCell!) -> Bool in
    if let strongSelf = self,
        let indexPath = tableView.indexPathForCell(sender) {
      strongSelf.deleteItem(indexPath)

      return true
    }

    return false
  }
  deleteButton.tintColor = UIColor.whiteColor()

  cell.rightButtons = [ deleteButton, editButton ]
  cell.rightSwipeSettings.transition = .Border

  return cell
}

Conclusão

Veja como ficou a nossa Lista de Pendências.

Lista de Pendências - Check Lista de Pendências - Edit|Delete Lista de Pendências - Delete Lista de Pendências

Utilizando o delegate melhoramos o uso de memória uma vez que os botões só serão criados conforme necessidade.

Espero que tenha gostado do tutorial. Caso tenha alguma dúvida crítica ou sugestão de novos pods a serem mostrados aqui no blog deixe a sua mensagem abaixo. Se gostou compartilhe.
Caso queira entrar em contato pode me chamar no Twitter @mateusfsilva.

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s