Swift: Custom TableView Swipe Actions (2021, Xcode 12, Swift 5) - iOS Development
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let model = models[indexPath.row]
let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { [weak self] _, _, success in
self?.models.remove(at: indexPath.row)
self?.tableView.deleteRows(at: [indexPath], with: .automatic)
success(true)
}
deleteAction.image = UIImage(systemName: "xmark")
let favoriteAction = UIContextualAction(style: .normal, title: "Favorite") { [weak self] _, _, success in
self?.models[indexPath.row].isFavorite.toggle()
self?.tableView.reloadRows(at: [indexPath], with: .automatic)
success(true)
}
favoriteAction.backgroundColor = model.isFavorite ? .systemBlue : .systemPink
favoriteAction.image = UIImage(systemName: model.isFavorite ? "hand.thumbsdown.circle" : "hand.thumbsup.circle")
return UISwipeActionsConfiguration(actions: [deleteAction, favoriteAction])
}
UISwipeActionsConfiguration
은 UIContextualAction
의 집합으로 구성UIContextualAction
은 액션 스타일, 타이틀, 이미지, 컬러, 핸들러 등을 지원import UIKit
final class SwipeActionTableViewController: UIViewController {
private lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.register(SwipeActionTableViewCell.self, forCellReuseIdentifier: SwipeActionTableViewCell.identifier)
tableView.delegate = self
tableView.dataSource = self
return tableView
}()
private var models: [PokemonModel] = PokemonModel.stubs
override func viewDidLoad() {
super.viewDidLoad()
setUI()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.frame = view.bounds
}
private func setUI() {
view.backgroundColor = .systemBackground
view.addSubview(tableView)
}
}
extension SwipeActionTableViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let model = models[indexPath.row]
let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { [weak self] _, _, success in
self?.models.remove(at: indexPath.row)
self?.tableView.deleteRows(at: [indexPath], with: .automatic)
success(true)
}
deleteAction.image = UIImage(systemName: "xmark")
let favoriteAction = UIContextualAction(style: .normal, title: "Favorite") { [weak self] _, _, success in
self?.models[indexPath.row].isFavorite.toggle()
self?.tableView.reloadRows(at: [indexPath], with: .automatic)
success(true)
}
favoriteAction.backgroundColor = model.isFavorite ? .systemBlue : .systemPink
favoriteAction.image = UIImage(systemName: model.isFavorite ? "hand.thumbsdown.circle" : "hand.thumbsup.circle")
return UISwipeActionsConfiguration(actions: [deleteAction, favoriteAction])
}
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let model = models[indexPath.row]
let muteAction = UIContextualAction(style: .normal, title: "Mute") { [weak self] _, _, success in
self?.models[indexPath.row].isMuted.toggle()
self?.tableView.reloadRows(at: [indexPath], with: .automatic)
success(true)
}
muteAction.backgroundColor = model.isMuted ? .systemOrange : .systemGreen
muteAction.image = UIImage(systemName: model.isMuted ? "speaker.circle" : "speaker.slash.circle")
return UISwipeActionsConfiguration(actions: [muteAction])
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
extension SwipeActionTableViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: SwipeActionTableViewCell.identifier, for: indexPath) as? SwipeActionTableViewCell else { fatalError() }
cell.configure(with: models[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return view.frame.height / 8
}
}
import UIKit
final class SwipeActionTableViewCell: UITableViewCell {
static let identifier = "SwipeActionTableViewCell"
private let pokemonImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
return imageView
}()
private let pokemonNameLabel: UILabel = {
let label = UILabel()
label.font = .preferredFont(forTextStyle: .headline)
label.textAlignment = .left
return label
}()
private let favoriteImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
return imageView
}()
private let muteImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
return imageView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
let size = contentView.frame.size.height
pokemonImageView.frame = CGRect(origin: .zero, size: .init(width: size, height: size))
pokemonImageView.layer.cornerRadius = size / 2
let otherSize = size / 2
favoriteImageView.frame = CGRect(x: size + 10, y: (contentView.frame.height - otherSize) / 2, width: otherSize, height: otherSize)
muteImageView.frame = CGRect(x: size + otherSize + 20, y: (contentView.frame.height - otherSize) / 2, width: otherSize, height: otherSize)
pokemonNameLabel.frame = CGRect(x: size + otherSize + otherSize + 30, y: 0, width: contentView.frame.size.width - (size + otherSize + otherSize) - 30, height: size)
}
private func setUI() {
contentView.addSubview(pokemonImageView)
contentView.addSubview(pokemonNameLabel)
contentView.addSubview(favoriteImageView)
contentView.addSubview(muteImageView)
}
func configure(with model: PokemonModel) {
favoriteImageView.image = UIImage(systemName: model.isFavorite ? "hand.thumbsdown.circle" : "hand.thumbsup.circle")?.withTintColor(model.isFavorite ? .systemBlue : .systemPink, renderingMode: .alwaysOriginal)
muteImageView.image = UIImage(systemName: model.isMuted ? "speaker.circle" : "speaker.slash.circle")?.withTintColor(model.isMuted ? .systemOrange : .systemGreen, renderingMode: .alwaysOriginal)
pokemonNameLabel.text = model.name
guard let url = URL(string: model.imageURLString) else { return }
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
guard
let data = data,
let response = response as? HTTPURLResponse,
response.statusCode >= 200 && response.statusCode < 400,
error == nil else { return }
DispatchQueue.main.async { [weak self] in
self?.pokemonImageView.image = UIImage(data: data)
}
}
.resume()
}
}
configure
함수 단에서 들어오는 모델의 isMuted
, isFavorite
등 불리언 값에 따라 현재 셀의 이미지를 변경