Как повторно получить прозрачный эффект маски UITableViewCell цвета во время увольнения ячейки на iOS 11, который ранее работал на iOS 10?
Предисловие
У меня есть рабочая пользовательская анимация отбрасывания UITableViewCell
на iOS 10. Способ ее реализации выглядит следующим образом:
- Пользователь нажимает кнопку отправки
- Пользовательский вид создается, позиционируется и вставляется с нулевым индексом подтекстов
UITableViewCell
(под ячейкой contentView
)
-
contentView
анимируется вне кадра, а пользовательский альфа-просмотр анимируется до 1.0
- Когда
contentView
полностью выходит за рамки, вызывается собственный UITableView
метод deleteRows(at: [indexPath], with: .top)
. Сбой UITableViewCell
и пользовательское представление маскируется за предыдущим UITableViewCell
по мере его сбрасывания.
- Ячейка (и, следовательно, все ее подпункты, включая мое пользовательское представление) удаляется.
Ниже приведена медленная анимация:
![Маска UITableViewCell, работающая на iOS 10]()
Примечание. tableView
имеет четкий цвет фона, позволяя отображать за ним пользовательский вид (синий). Каждая ячейка имеет containerView
, которая содержит все содержимое ячейки. containerView
и contentView
оба имеют четкие цвета фона. Все в порядке и денди.
Проблема
Миграция моего приложения на iOS 11, эта анимация больше не работает должным образом. Ниже приведена медленная анимация, которая больше не работает.
![Маска UITableViewCell, работающая на iOS 11]()
Как вы можете видеть, пользовательский вид накладывается поверх предыдущей ячейки при удалении соты без изменений в моем коде.
Исследование до сих пор
До сих пор я решил, что анатомия a UITableView
изменилась из этого:
UITableView
UITableViewWrapperView
cell
custom view
contentView
cell separator view
cell
cell
cell
UIView
UIImageView (scroll view indicator bottom)
UIImageView (scroll view indicator right)
Для этого: (UITableViewWrapperView
был удален)
UITableView
cell
custom view
contentView
cell separator view
cell
cell
cell
UIView
UIImageView (scroll view indicator bottom)
UIImageView (scroll view indicator right)
Одна вещь, которую я заметил об этом UITableWrapperView
, заключается в том, что его свойство isOpaque
имеет значение true, а свойство masksToBounds
- false, а UITableView
и все UITableViewCell
противоположны. Поскольку это представление было удалено в iOS 11, это может способствовать тому, что у меня есть ошибочный эффект. Я действительно не знаю.
Edit: Еще одна вещь, которую я обнаружил, заключалась в том, что UITableWrapperView
в рабочем примере вставляет тайну UIView
в нулевом индексе ее subviews (все UITableViewCell
s), для свойств которой isOpaque
установлено значение true и он имеет compositingFilter
. Это представление впоследствии удаляется после завершения анимации. Поскольку UITableWrapperView
удаляется в iOS 11, это представление также связывается с ассоциацией.
Вопрос
Прежде всего, кто-нибудь знает, почему это изменение в поведении происходит? Если нет, есть ли альтернативный способ добиться эффекта, который лучше работает в iOS 10? Я хочу clear UITableViewCell
, но у вас есть пользовательский вид, который отображается за каждой ячейкой при увольнении, который маскируется другим clear UITableViewCell
при увольнении, как показано в первом примере gif выше.
Ответы
Ответ 1
Перед удалением строки отправьте ячейку, которую вы удаляете, в обратную сторону подзонов представления таблицы.
tableView.sendSubview(toBack: cell)
tableView.deleteRows(at: [indexPath], with: .top)
Если у вас нет ячейки, вы можете получить ее с помощью указательного пути.
guard let cell = tableView.cell(for: indexPath) else { return }
// ...
Это должно работать с настройкой иерархии представлений.
![Просмотр настройки иерархии]()
CardTableViewController.swift
class CardTableViewController: UITableViewController, CardTableViewCellDelegate {
// MARK: Outlets
@IBOutlet var segmentedControl: UISegmentedControl!
// MARK: Properties
private var cards: [Card] = [
Card(text: "Etiam porta sem malesuada magna mollis euismod. Maecenas faucibus mollis interdum."),
Card(text: "Nulla vitae elit libero, a pharetra augue. Cras justo odio, dapibus ac facilisis in, egestas eget quam."),
Card(text: "Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Sed posuere consectetur est at lobortis.")
]
var selectedIndex: Int { return segmentedControl.selectedSegmentIndex }
var tableCards: [Card] {
return cards.filter { selectedIndex == 0 ? !$0.isDone : $0.isDone }
}
// MARK: UITableViewDataSource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableCards.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CardTableViewCell
cell.card = tableCards[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
// MARK: CardTableViewCellDelegate
func cardTableViewCell(_ cell: CardTableViewCell, didTouchUpInsideCheckmarkButton button: UIButton) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
cell.card?.isDone = button.isSelected
cell.card?.dayCount += button.isSelected ? 1 : -1
cell.customView?.dayCount = cell.card?.dayCount
UIView.animate(withDuration: 0.7, delay: 0.3, options: .curveEaseIn, animations: {
cell.checkmarkButton?.superview?.transform = .init(translationX: cell.frame.width, y: 0)
cell.customView?.alpha = 1
}) { _ in
self.tableView.sendSubview(toBack: cell)
self.tableView.deleteRows(at: [indexPath], with: .top)
}
}
// MARK: Actions
@IBAction func didChangeSegmentedControl(_ sender: UISegmentedControl) {
tableView.reloadData()
}
}
CardTableViewCell.swift
@objc protocol CardTableViewCellDelegate {
func cardTableViewCell(_ cell: CardTableViewCell, didTouchUpInsideCheckmarkButton button: UIButton)
}
@IBDesignable class CardTableViewCell: UITableViewCell {
// MARK: Outlets
@IBOutlet var checkmarkButton: UIButton?
@IBOutlet var customView: CardCustomView?
@IBOutlet var delegate: CardTableViewCellDelegate?
@IBOutlet var taskTextLabel: UILabel?
// MARK: Properties
var card: Card? {
didSet {
updateOutlets()
}
}
// MARK: Lifecycle
override func awakeFromNib() {
super.awakeFromNib()
checkmarkButton?.layer.borderColor = UIColor.black.cgColor
}
override func prepareForReuse() {
super.prepareForReuse()
card = nil
}
// MARK: Methods
private func updateOutlets() {
checkmarkButton?.isSelected = card?.isDone == true ? true : false
checkmarkButton?.superview?.transform = .identity
customView?.alpha = 0
customView?.dayCount = card?.dayCount
taskTextLabel?.text = card?.text
}
// MARK: Actions
@IBAction func didTouchUpInsideCheckButton(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
delegate?.cardTableViewCell(self, didTouchUpInsideCheckmarkButton: sender)
}
}
class CardCustomView: UIView {
// MARK: Outlets
@IBOutlet var countLabel: UILabel?
@IBOutlet var daysLabel: UILabel?
// MARK: Properties
var dayCount: Int? {
didSet {
updateOutlets()
}
}
override func awakeFromNib() {
super.awakeFromNib()
updateOutlets()
}
// MARK: Methods
private func updateOutlets() {
let count = dayCount.flatMap { $0 } ?? 0
countLabel?.text = "\(dayCount.flatMap { $0 } ?? 0)"
daysLabel?.text = "Day\(count == 1 ? "" : "s") in a row"
}
}
Card.swift
class Card: NSObject {
// MARK: Properties
var dayCount: Int = 0
var isDone: Bool = false
var text: String
// MARK: Lifecycle
init(text: String) {
self.text = text
}
}
Ответ 2
Прежде всего, я не считаю недостающую UITableViewWrapperView
проблемой.
Это родительское представление, и родители не могут скрывать представления пользователей.
Обратите внимание, что проблема в том, какая ячейка скрывает, какая. В первом случае строка A затеняет строку B, а во второй строке B скрывает строку A (в этом случае ваша пользовательская ячейка B имеет прозрачный фон).
Итак, похоже, что Apple завязывает отношения между ячейками. Чтобы исправить это, я попытался бы исправить эти отношения. Поэтому перед началом второй части анимации я бы попытался:
вернуть ячейку обратно
cell.superview.sendSubview(toBack: cell)
Это не должно ничего сломать (на старых версиях iOS) и должно решить проблему с iOS 11.
Ответ 3
Я много дней учился, но не смог.
iOS10
После deleteRows
, TableViewCell
имеет представление с свойством compositingFilter
, значение равно copy
, которое позволяет скрыть все представления позади этого.
Мы можем попытаться удалить subview TableViewCell
, сгенерированный в то время как deleteRows
. Тогда iOS 10 будет вести себя как iOS11.
Затем мы можем попробовать снова добавить представление (UIView()
) в TableViewCell
и установить compositingFilter
на copy
(String), он снова достигнет in iOS10
.
Но любой сбой действия в iOS11. Он создаст черный фон в iOS11.
Я считаю, что поведение слоя было изменено на iOS11.
Возможно, нам стоит подумать о том, чтобы временно отказаться от анимации up
в ios11
, когда фон TableViewCell
прозрачен.
Ответ 4
Я столкнулся с такой проблемой после просмотра приложения на iOS 11, проблема была в анимации при удалении строк из представления таблицы:
![введите описание изображения здесь]()
Понятно, что коллапсирующая анимация не подходит, потому что удаляется более одной строки.
Вот ответственный код для расширения/свертывания строк:
// inserting rows:
tableView.beginUpdates()
tableView.insertRows(at: [IndexPath(row: 4 + count, section: 0), IndexPath(row: 5 + count, section: 0), IndexPath(row: 6 + count, section: 0)], with: .automatic)
tableView.endUpdates()
// deleting rows:
tableView.beginUpdates()
tableView.deleteRows(at: [IndexPath(row: 4 + count, section: 0), IndexPath(row: 5 + count, section: 0), IndexPath(row: 6 + count, section: 0)], with: .automatic)
tableView.endUpdates()
, очевидно, существует также код для редактирования массива источников данных.
Причиной возникновения этой проблемы было - просто - назначенный UITableViewRowAnimation в automatic; Все, что я сделал, это изменить анимацию как fade
:
// inserting rows:
tableView.beginUpdates()
tableView.insertRows(at: [IndexPath(row: 4 + count, section: 0), IndexPath(row: 5 + count, section: 0), IndexPath(row: 6 + count, section: 0)], with: .fade)
tableView.endUpdates()
// deleting rows:
tableView.beginUpdates()
tableView.deleteRows(at: [IndexPath(row: 4 + count, section: 0), IndexPath(row: 5 + count, section: 0), IndexPath(row: 6 + count, section: 0)], with: .fade)
tableView.endUpdates()
И результат был исправлен как:
![введите описание изображения здесь]()
И
Возможно, вы захотите проверить следующий вопрос, который может быть связан с вашей проблемой:
Ответ 5
Я нашел решение для UITableView
с ячейками фиксированного размера
![представление решения в действии]()
В этом подходе я решил добавить дополнительное представление к представлению содержимого UITableViewCell
и анимировать его ограничение высоты до 0
рядом с анимацией удаления строки.
Чтобы все было просто, я покажу только ключевые моменты этого решения.
- Кнопка удаления нажатий пользователя.
- Ячейка перемещает внутренний просмотр содержимого вправо.
- Режим просмотра таблицы вызывает метод
deleteRows(at:with:)
.
- Ячейка оживляет высоту дополнительного представления до
0
.
Я не буду описывать всю установку автоматического макета здесь, потому что это затмит основную идею решения. Все строительные блоки довольно просты, но их несколько. Вы можете найти полный рабочий проект в этом репозитории GitHub.
К сожалению, это решение не очень хорошо работает с динамическими ячейками высоты из-за времени анимации. Я не уверен, что это решит вашу проблему, поскольку, насколько я вижу, вы используете динамические ячейки высоты.
Тем не менее, я надеюсь, что это подтолкнет вас в правильном направлении или поможет кому-то с аналогичной проблемой.