动画部分 UITableViewCell
Animate Parts Of UITableViewCell
我想创建一个可展开的 table 视图单元格,点击单元格的特定部分我想设置动画并展开单元格。
我可以使用默认动画在点击时展开和折叠,这基本上会淡化视图更改。但我想在指示器图像上设置旋转动画并展开单元格。
这是当前的动画效果。
这是我的 table 视图代码
class ExpandableTableView: UITableView {
var status:[Bool] = [Bool].init(repeating: false, count: 10)
override init(frame: CGRect, style: UITableView.Style) {
super.init(frame: frame, style: style)
self.registerCell()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.registerCell()
}
func registerCell() {
self.register(UINib(nibName: "DemoTableViewCell", bundle: .main), forCellReuseIdentifier: "DemoTableViewCell")
self.rowHeight = UITableView.automaticDimension
self.delegate = self
self.dataSource = self
}
}
extension ExpandableTableView: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return status.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "DemoTableViewCell") as? DemoTableViewCell else {
return UITableViewCell()
}
cell.setHeader("Header \(indexPath.row) : Status \(self.status[indexPath.row])")
cell.setStatus(isExpanded: self.status[indexPath.row])
return cell
}
}
extension ExpandableTableView: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.status[indexPath.row] = !self.status[indexPath.row]
tableView.reloadRows(at: [indexPath], with: .automatic)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
这是 UITableViewCell 的代码
class DemoTableViewCell: UITableViewCell {
@IBOutlet private var lblHeader:UILabel!
@IBOutlet weak var imgIndicator: UIImageView!
@IBOutlet private var containerStackView:UIStackView!
func setHeader(_ header:String) {
self.lblHeader.text = header
}
func setStatus(isExpanded:Bool) {
self.containerStackView.isHidden = !isExpanded
if (isExpanded){
self.imgIndicator.transform = CGAffineTransform(rotationAngle: -.pi/2)
}else {
self.imgIndicator.transform = CGAffineTransform(rotationAngle: 0)
}
self.containerStackView.layoutIfNeeded()
}
}
任何帮助将不胜感激。
谢谢
编辑
代码:Here is the code 用于快速结帐。
我不确定您是否尝试将转换代码放入动画块中。
这通常适合我。
例如:
// Set the duration you wish
UIView.animate(withDuration: 2, animations: { [weak self] in
self?.imgIndicator.transform =
CGAffineTransform(rotationAngle: .pi/2)
})
给出:
更新 1 使用自动布局的 UITableView 单元示例
自定义table查看单元格
fileprivate class CustomCell: UITableViewCell
{
var imgIndicator: UIImageView!
static let tableViewCellIdentifier = "cell"
override init(style: UITableViewCell.CellStyle,
reuseIdentifier: String?)
{
super.init(style: style,
reuseIdentifier: reuseIdentifier)
configureIndicator()
}
required init?(coder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
private func configureIndicator()
{
let config = UIImage.SymbolConfiguration(textStyle: .largeTitle)
let image = UIImage(systemName: "chevron.right.circle",
withConfiguration: config)
imgIndicator = UIImageView(image: image)
imgIndicator.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(imgIndicator)
// auto-layout
imgIndicator.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
.isActive = true
imgIndicator.topAnchor.constraint(equalTo: contentView.topAnchor)
.isActive = true
imgIndicator.widthAnchor.constraint(equalTo: imgIndicator.heightAnchor,
multiplier: 1).isActive = true
imgIndicator.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
.isActive = true
}
// Simplified just to do the animation
// Customize with your logic
func setStatus(isExpanded: Bool)
{
if isExpanded
{
imgIndicator.transform = CGAffineTransform(rotationAngle: .pi/2)
}
}
}
基本视图控制器配置
class TableViewAnimationVC: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
// This is just view set up, you can ignore this
title = "Animation rotation"
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationBar.backgroundColor = .white
navigationController?.navigationBar.tintColor = .white
view.backgroundColor = .white
configureTableView()
}
private func configureTableView()
{
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(CustomCell.self,
forCellReuseIdentifier: CustomCell.tableViewCellIdentifier)
tableView.dataSource = self
tableView.delegate = self
// remove additional rows
tableView.tableFooterView = UIView()
view.addSubview(tableView)
// Auto layout
tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor)
.isActive = true
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
.isActive = true
tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor)
.isActive = true
tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
.isActive = true
}
}
表格视图数据源
extension TableViewAnimationVC: UITableViewDataSource
{
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int
{
// random number
return 10
}
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell =
tableView.dequeueReusableCell(withIdentifier: CustomCell.tableViewCellIdentifier)
as! CustomCell
cell.textLabel?.text = "Tap to rotate"
return cell
}
}
TableView 委托
extension TableViewAnimationVC: UITableViewDelegate
{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
// Retrieved the actual cell that was tapped
let cell = tableView.cellForRow(at: indexPath) as! CustomCell
// Perform animation in block
UIView.animate(withDuration: 2.0) {
cell.setStatus(isExpanded: true)
} completion: { (success) in
if success
{
// do your processing after the animation has completed
}
}
}
}
最终结果:
使用 OP 代码示例更新 2
问题似乎与使用用于重新加载 table 视图和执行其他 UIView 动画的相同动画块有关。
我看到的选项是将这两个动画分开。
一种选择是完成旋转动画,然后重新加载您的 table视图行以进行展开 - 您可以对此进行试验。
我会告诉你第二个选项,它似乎是一个更好的用户体验。高层次:
- 找出当前应为哪个单元格设置动画
- 使用动画重新加载 table 视图行
- 在单独的 UIView 动画块中,执行您的其他动画
以下是我为实现上述目标所做的更改:
首先,在您的单元格中 class
class DemoTableViewCell: UITableViewCell {
@IBOutlet private var lblHeader:UILabel!
@IBOutlet weak var imgIndicator: UIImageView!
@IBOutlet private var containerStackView:UIStackView!
// No change
func setHeader(_ header:String) {
self.lblHeader.text = header
}
// Contract / expand the table view cell only
func setStatus(isExpanded:Bool) {
self.containerStackView.isHidden = !isExpanded
self.containerStackView.layoutIfNeeded()
}
// Animate the indicator separately
func animateIndicator()
{
var endAngle: CGFloat = .pi/2.0
var startAngle = CGFloat.zero
if self.containerStackView.isHidden
{
startAngle = .pi/2
endAngle = .zero
}
imgIndicator.transform
= CGAffineTransform(rotationAngle: startAngle)
UIView.animate(withDuration: 0.5) { [weak self] in
self?.imgIndicator.transform
= CGAffineTransform(rotationAngle: endAngle)
}
completion: { (success) in
// do anything
}
}
}
在ExpandableTableView
中我做了一些修改:
// Unchanged, done by you
var status:[Bool] = [Bool].init(repeating: false, count: 10)
// Stores the current cell's index path which should be animated
// This is for my convenience as I couldn't grasp your complete logic
// you can edit based on your logic
var shouldAnimate: [IndexPath] = []
UITableViewDelegate 的变化
extension ExpandableTableView: UITableViewDelegate {
func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
self.status[indexPath.row] = !self.status[indexPath.row]
// Added by me to keep track of which cell to animate
shouldAnimate.append(indexPath)
tableView.reloadRows(at: [indexPath], with: .fade)
}
// Perform your UIView animations in here
func tableView(_ tableView: UITableView,
willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath)
{
if let cell = cell as? DemoTableViewCell,
shouldAnimate.firstIndex(of: indexPath) != nil
{
cell.animateIndicator()
}
}
// Unchanged
func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
结果接近我相信你希望达到的结果:
调整时间以尝试使展开和旋转动画更好地同步。
我想创建一个可展开的 table 视图单元格,点击单元格的特定部分我想设置动画并展开单元格。
我可以使用默认动画在点击时展开和折叠,这基本上会淡化视图更改。但我想在指示器图像上设置旋转动画并展开单元格。
这是当前的动画效果。
这是我的 table 视图代码
class ExpandableTableView: UITableView {
var status:[Bool] = [Bool].init(repeating: false, count: 10)
override init(frame: CGRect, style: UITableView.Style) {
super.init(frame: frame, style: style)
self.registerCell()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.registerCell()
}
func registerCell() {
self.register(UINib(nibName: "DemoTableViewCell", bundle: .main), forCellReuseIdentifier: "DemoTableViewCell")
self.rowHeight = UITableView.automaticDimension
self.delegate = self
self.dataSource = self
}
}
extension ExpandableTableView: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return status.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "DemoTableViewCell") as? DemoTableViewCell else {
return UITableViewCell()
}
cell.setHeader("Header \(indexPath.row) : Status \(self.status[indexPath.row])")
cell.setStatus(isExpanded: self.status[indexPath.row])
return cell
}
}
extension ExpandableTableView: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.status[indexPath.row] = !self.status[indexPath.row]
tableView.reloadRows(at: [indexPath], with: .automatic)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
这是 UITableViewCell 的代码
class DemoTableViewCell: UITableViewCell {
@IBOutlet private var lblHeader:UILabel!
@IBOutlet weak var imgIndicator: UIImageView!
@IBOutlet private var containerStackView:UIStackView!
func setHeader(_ header:String) {
self.lblHeader.text = header
}
func setStatus(isExpanded:Bool) {
self.containerStackView.isHidden = !isExpanded
if (isExpanded){
self.imgIndicator.transform = CGAffineTransform(rotationAngle: -.pi/2)
}else {
self.imgIndicator.transform = CGAffineTransform(rotationAngle: 0)
}
self.containerStackView.layoutIfNeeded()
}
}
任何帮助将不胜感激。
谢谢
编辑
代码:Here is the code 用于快速结帐。
我不确定您是否尝试将转换代码放入动画块中。
这通常适合我。
例如:
// Set the duration you wish
UIView.animate(withDuration: 2, animations: { [weak self] in
self?.imgIndicator.transform =
CGAffineTransform(rotationAngle: .pi/2)
})
给出:
更新 1 使用自动布局的 UITableView 单元示例
自定义table查看单元格
fileprivate class CustomCell: UITableViewCell
{
var imgIndicator: UIImageView!
static let tableViewCellIdentifier = "cell"
override init(style: UITableViewCell.CellStyle,
reuseIdentifier: String?)
{
super.init(style: style,
reuseIdentifier: reuseIdentifier)
configureIndicator()
}
required init?(coder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
private func configureIndicator()
{
let config = UIImage.SymbolConfiguration(textStyle: .largeTitle)
let image = UIImage(systemName: "chevron.right.circle",
withConfiguration: config)
imgIndicator = UIImageView(image: image)
imgIndicator.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(imgIndicator)
// auto-layout
imgIndicator.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
.isActive = true
imgIndicator.topAnchor.constraint(equalTo: contentView.topAnchor)
.isActive = true
imgIndicator.widthAnchor.constraint(equalTo: imgIndicator.heightAnchor,
multiplier: 1).isActive = true
imgIndicator.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
.isActive = true
}
// Simplified just to do the animation
// Customize with your logic
func setStatus(isExpanded: Bool)
{
if isExpanded
{
imgIndicator.transform = CGAffineTransform(rotationAngle: .pi/2)
}
}
}
基本视图控制器配置
class TableViewAnimationVC: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
// This is just view set up, you can ignore this
title = "Animation rotation"
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationBar.backgroundColor = .white
navigationController?.navigationBar.tintColor = .white
view.backgroundColor = .white
configureTableView()
}
private func configureTableView()
{
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(CustomCell.self,
forCellReuseIdentifier: CustomCell.tableViewCellIdentifier)
tableView.dataSource = self
tableView.delegate = self
// remove additional rows
tableView.tableFooterView = UIView()
view.addSubview(tableView)
// Auto layout
tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor)
.isActive = true
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
.isActive = true
tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor)
.isActive = true
tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
.isActive = true
}
}
表格视图数据源
extension TableViewAnimationVC: UITableViewDataSource
{
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int
{
// random number
return 10
}
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell =
tableView.dequeueReusableCell(withIdentifier: CustomCell.tableViewCellIdentifier)
as! CustomCell
cell.textLabel?.text = "Tap to rotate"
return cell
}
}
TableView 委托
extension TableViewAnimationVC: UITableViewDelegate
{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
// Retrieved the actual cell that was tapped
let cell = tableView.cellForRow(at: indexPath) as! CustomCell
// Perform animation in block
UIView.animate(withDuration: 2.0) {
cell.setStatus(isExpanded: true)
} completion: { (success) in
if success
{
// do your processing after the animation has completed
}
}
}
}
最终结果:
使用 OP 代码示例更新 2
问题似乎与使用用于重新加载 table 视图和执行其他 UIView 动画的相同动画块有关。
我看到的选项是将这两个动画分开。
一种选择是完成旋转动画,然后重新加载您的 table视图行以进行展开 - 您可以对此进行试验。
我会告诉你第二个选项,它似乎是一个更好的用户体验。高层次:
- 找出当前应为哪个单元格设置动画
- 使用动画重新加载 table 视图行
- 在单独的 UIView 动画块中,执行您的其他动画
以下是我为实现上述目标所做的更改:
首先,在您的单元格中 class
class DemoTableViewCell: UITableViewCell {
@IBOutlet private var lblHeader:UILabel!
@IBOutlet weak var imgIndicator: UIImageView!
@IBOutlet private var containerStackView:UIStackView!
// No change
func setHeader(_ header:String) {
self.lblHeader.text = header
}
// Contract / expand the table view cell only
func setStatus(isExpanded:Bool) {
self.containerStackView.isHidden = !isExpanded
self.containerStackView.layoutIfNeeded()
}
// Animate the indicator separately
func animateIndicator()
{
var endAngle: CGFloat = .pi/2.0
var startAngle = CGFloat.zero
if self.containerStackView.isHidden
{
startAngle = .pi/2
endAngle = .zero
}
imgIndicator.transform
= CGAffineTransform(rotationAngle: startAngle)
UIView.animate(withDuration: 0.5) { [weak self] in
self?.imgIndicator.transform
= CGAffineTransform(rotationAngle: endAngle)
}
completion: { (success) in
// do anything
}
}
}
在ExpandableTableView
中我做了一些修改:
// Unchanged, done by you
var status:[Bool] = [Bool].init(repeating: false, count: 10)
// Stores the current cell's index path which should be animated
// This is for my convenience as I couldn't grasp your complete logic
// you can edit based on your logic
var shouldAnimate: [IndexPath] = []
UITableViewDelegate 的变化
extension ExpandableTableView: UITableViewDelegate {
func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
self.status[indexPath.row] = !self.status[indexPath.row]
// Added by me to keep track of which cell to animate
shouldAnimate.append(indexPath)
tableView.reloadRows(at: [indexPath], with: .fade)
}
// Perform your UIView animations in here
func tableView(_ tableView: UITableView,
willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath)
{
if let cell = cell as? DemoTableViewCell,
shouldAnimate.firstIndex(of: indexPath) != nil
{
cell.animateIndicator()
}
}
// Unchanged
func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
结果接近我相信你希望达到的结果:
调整时间以尝试使展开和旋转动画更好地同步。