无法将 UIImageView 旋转动画和 tableView 部分重新加载结合起来

发布于 2025-01-20 11:51:39 字数 3472 浏览 3 评论 0原文

我有4个部分,每个部分都有2个嵌套行。我通过敲击每个部分打开行。

这是我的初始数据的样子。 It has title, subtitle and options (which is what nested rows should display):

   private var sections = [
        SortingSection(title: "По имени", subtitle: "Российский рубль", options: ["По возрастанию (А→Я)", "По убыванию (Я→А)"]),
        SortingSection(title: "По короткому имени", subtitle: "RUB", options: ["По возрастанию (А→Я)", "По убыванию (Я→А)"]),
        SortingSection(title: "По значению", subtitle: "86,22", options: ["По возрастанию (1→2)", "По убыванию (2→1)"]),
        SortingSection(title: "Своя", subtitle: "в любом порядке", options: ["Включить"])
   ]

When I tap on a section I want it accessory (<代码> chevron.right ,以uiimageView制作)在同步中旋转 随着嵌套行的扩展,当我再次单击相同的行为以关闭时。

我有一个称为iSoped的变量(bool,默认情况下为false),我从false到true更改为didSelectRowat中的每条点击。 Based on that a show all nested cells and rotate the UIImageView:

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    if indexPath.row == 0 {
        sections[indexPath.section].isOpened.toggle()
        
        guard let cell = tableView.cellForRow(at: indexPath) as? MainSortTableViewCell else { return }
        
        UIView.animate(withDuration: 0.3) {
            if self.sections[indexPath.section].isOpened {
                cell.chevronImage.transform = CGAffineTransform(rotationAngle: .pi/2)
            } else {
                cell.chevronImage.transform = .identity
            }
        } completion: { _ in
            tableView.reloadSections([indexPath.section], with: .none)
        }
     }

As you can see above I reload tableView section to show\hide nested rows in a completion block after animation.我不能在\ else语句中使用reloadSections,因为chevron Animation 被跳过

Also my numberOrRowsInSection method:

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let section = sections[section]
    if section.isOpened {
        return section.options.count + 1
    } else {
        return 1
    }
}
  1. Here is how it looks now: CLICK< /p>

  2. Here is what I want (any iPhone native apps):< a href =“ https://cln.sh/z0lm6e” rel =“ nofollow noreferrer”>单击

I tried to add and delete rows instead of reloading the whole section, but always end up with error:

UIView.animate(withDuration: 0.3) {
    if self.sections[indexPath.section].isOpened {
       cell.chevronImage.transform = CGAffineTransform(rotationAngle: .pi/2)
                
         for i in 0..<self.sections[indexPath.section].options.count {
                    tableView.insertRows(at: [IndexPath(row: 1+i, section: indexPath.section)], with: .none)
         }
      } else {
         cell.chevronImage.transform = .identity
                
       for i in 0..<self.sections[indexPath.section].options.count {
        tableView.deleteRows(at: [IndexPath(row: i-1, section: indexPath.section)], with: .none)
                }
            }
        }

How can I change my code to solve the task and animate chevron at the same time nested rows expand or close?

I have 4 sections, each section have 2 nested rows. I open the rows by tapping on each section.

Here is how my initial data looks like. It has title, subtitle and options (which is what nested rows should display):

   private var sections = [
        SortingSection(title: "По имени", subtitle: "Российский рубль", options: ["По возрастанию (А→Я)", "По убыванию (Я→А)"]),
        SortingSection(title: "По короткому имени", subtitle: "RUB", options: ["По возрастанию (А→Я)", "По убыванию (Я→А)"]),
        SortingSection(title: "По значению", subtitle: "86,22", options: ["По возрастанию (1→2)", "По убыванию (2→1)"]),
        SortingSection(title: "Своя", subtitle: "в любом порядке", options: ["Включить"])
   ]

When I tap on a section I want it accessory (chevron.right, made as UIImageView) be rotated in sync with expanding of nested rows and when I click again the same behaviour for closing.

I have a variable called isOpened (bool, false by default), which I change from false to true and back each tap in didSelectRowAt. Based on that a show all nested cells and rotate the UIImageView:

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    if indexPath.row == 0 {
        sections[indexPath.section].isOpened.toggle()
        
        guard let cell = tableView.cellForRow(at: indexPath) as? MainSortTableViewCell else { return }
        
        UIView.animate(withDuration: 0.3) {
            if self.sections[indexPath.section].isOpened {
                cell.chevronImage.transform = CGAffineTransform(rotationAngle: .pi/2)
            } else {
                cell.chevronImage.transform = .identity
            }
        } completion: { _ in
            tableView.reloadSections([indexPath.section], with: .none)
        }
     }

As you can see above I reload tableView section to show\hide nested rows in a completion block after animation. I can't use reloadSections in an if\else statement because then chevron animation gets skipped.

Also my numberOrRowsInSection method:

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let section = sections[section]
    if section.isOpened {
        return section.options.count + 1
    } else {
        return 1
    }
}
  1. Here is how it looks now: CLICK

  2. Here is what I want (any iPhone native apps):CLICK

I tried to add and delete rows instead of reloading the whole section, but always end up with error:

UIView.animate(withDuration: 0.3) {
    if self.sections[indexPath.section].isOpened {
       cell.chevronImage.transform = CGAffineTransform(rotationAngle: .pi/2)
                
         for i in 0..<self.sections[indexPath.section].options.count {
                    tableView.insertRows(at: [IndexPath(row: 1+i, section: indexPath.section)], with: .none)
         }
      } else {
         cell.chevronImage.transform = .identity
                
       for i in 0..<self.sections[indexPath.section].options.count {
        tableView.deleteRows(at: [IndexPath(row: i-1, section: indexPath.section)], with: .none)
                }
            }
        }

How can I change my code to solve the task and animate chevron at the same time nested rows expand or close?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

情独悲 2025-01-27 11:51:39

正如您所看到的,如果您想要为单元格中的元素设置动画,则不能在重新加载单元格的同时执行此操作。

因此,为了获得您想要的效果,一种方法是将数据分成“部分对”。

因此,而不是这样:

在此处输入图像描述

您将看到:

在此处输入图像描述

当点击“标题”部分时,您可以为设置图像视图旋转动画重新加载下一部分时该单元格

这需要更多的数据管理——但实际上,并没有那么多。

例如,如果数据结构是:

struct SortingSection {
    var title: String = ""
    var subtitle: String = ""
    var options: [String] = []
    var isOpened: Bool = false
}

numberOfSections 中,我们可以返回 sections.count * 2

然后,在 numberOfRowsInSection 中,我们将得到“virtualSection”数字来获取数据数组的索引 - 类似这样:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let virtualSection: Int = section / 2
    let secItem = sections[virtualSection]
    if section % 2 == 0 {
        return 1
    }
    if secItem.isOpened {
        return secItem.options.count
    }
    return 0
}

类似地,在 cellForRowAt 中:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let virtualSection: Int = indexPath.section / 2
    let secItem = sections[virtualSection]
    if indexPath.section % 2 == 0 {
        // return a "header row cell"
    }
    // return a "option row cell"
}

最后,在 didSelectRowAt 中:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    let virtualSection: Int = indexPath.section / 2
    // if it's a "header row"
    if indexPath.section % 2 == 0 {
        sections[virtualSection].isOpened.toggle()
        guard let c = tableView.cellForRow(at: indexPath) as? ExpandCell else { return }
        UIView.animate(withDuration: 0.3) {
            if self.sections[virtualSection].isOpened {
                c.chevronImageView.transform = CGAffineTransform(rotationAngle: .pi/2)
            } else {
                c.chevronImageView.transform = .identity
            }
            // reload the NEXT section
            tableView.reloadSections([indexPath.section + 1], with: .automatic)
        }
    }

}

这是一个完整实施来尝试。一切都是通过代码完成的(没有 @IBOutlet 连接),因此创建一个新的 UITableViewController 并将其自定义类分配给 ExpandSectionTableViewController

struct SortingSection {
    var title: String = ""
    var subtitle: String = ""
    var options: [String] = []
    var isOpened: Bool = false
}
class ExpandCell: UITableViewCell {
    let titleLabel = UILabel()
    let subtitleLabel = UILabel()
    let chevronImageView = UIImageView()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(titleLabel)
        
        subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(subtitleLabel)
        
        chevronImageView.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(chevronImageView)
        
        let g = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: g.topAnchor),
            titleLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            
            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4.0),
            subtitleLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            
            chevronImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            chevronImageView.widthAnchor.constraint(equalToConstant: 40.0),
            chevronImageView.heightAnchor.constraint(equalTo: chevronImageView.widthAnchor),
            chevronImageView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            
            subtitleLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor),
            
        ])
    
        subtitleLabel.font = .systemFont(ofSize: 12.0, weight: .regular)
        subtitleLabel.textColor = .gray
        
        chevronImageView.contentMode = .center
        let cfg = UIImage.SymbolConfiguration(pointSize: 24.0, weight: .regular)
        if let img = UIImage(systemName: "chevron.right", withConfiguration: cfg) {
            chevronImageView.image = img
        }
        
    }
}
class SubCell: UITableViewCell {
    
    let titleLabel = UILabel()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(titleLabel)
        
        let g = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: g.topAnchor),
            titleLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            titleLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor),
        ])
        
        titleLabel.font = .italicSystemFont(ofSize: 15.0)
        
    }
}

class ExpandSectionTableViewController: UITableViewController {
    
    var sections: [SortingSection] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let optCounts: [Int] = [
            2, 3, 2, 5, 4, 2, 2, 3, 3, 4, 2, 1, 2, 3, 4, 3, 2
        ]
        for (i, val) in optCounts.enumerated() {
            var opts: [String] = []
            for n in 1...val {
                opts.append("Section \(i) - Option \(n)")
            }
            sections.append(SortingSection(title: "Title \(i)", subtitle: "Subtitle \(i)", options: opts, isOpened: false))
        }
        
        tableView.register(ExpandCell.self, forCellReuseIdentifier: "expCell")
        tableView.register(SubCell.self, forCellReuseIdentifier: "subCell")
    }
    override func numberOfSections(in tableView: UITableView) -> Int {
        return sections.count * 2
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        let virtualSection: Int = section / 2
        let secItem = sections[virtualSection]
        if section % 2 == 0 {
            return 1
        }
        if secItem.isOpened {
            return secItem.options.count
        }
        return 0

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

        let virtualSection: Int = indexPath.section / 2
        let secItem = sections[virtualSection]
        if indexPath.section % 2 == 0 {
            let c = tableView.dequeueReusableCell(withIdentifier: "expCell", for: indexPath) as! ExpandCell
            c.titleLabel.text = secItem.title
            c.subtitleLabel.text = secItem.subtitle
            c.chevronImageView.transform = secItem.isOpened ? CGAffineTransform(rotationAngle: .pi/2) : .identity
            c.selectionStyle = .none
            return c
        }
        let c = tableView.dequeueReusableCell(withIdentifier: "subCell", for: indexPath) as! SubCell
        c.titleLabel.text = secItem.options[indexPath.row]
        return c

    }
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        let virtualSection: Int = indexPath.section / 2
        // if it's a "header row"
        if indexPath.section % 2 == 0 {
            sections[virtualSection].isOpened.toggle()
            guard let c = tableView.cellForRow(at: indexPath) as? ExpandCell else { return }
            UIView.animate(withDuration: 0.3) {
                if self.sections[virtualSection].isOpened {
                    c.chevronImageView.transform = CGAffineTransform(rotationAngle: .pi/2)
                } else {
                    c.chevronImageView.transform = .identity
                }
                // reload the NEXT section
                tableView.reloadSections([indexPath.section + 1], with: .automatic)
            }
        }

    }
    
}

As you've seen, if you want to animate an element in a cell you cannot do so at the same time as reloading the cell.

So, to get the effect you want, one approach will be to split your data into "section pairs."

So, instead of this:

enter image description here

you'll have this:

enter image description here

When tapping on a "header" section, you can animate the image view rotation for that cell while reloading the next section.

It takes a little more management of the data -- but, really, not that much.

For example, if the data structure is:

struct SortingSection {
    var title: String = ""
    var subtitle: String = ""
    var options: [String] = []
    var isOpened: Bool = false
}

in numberOfSections we can return sections.count * 2

Then, in numberOfRowsInSection, we'll get the "virtualSection" number to get the index into our data array - something like this:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let virtualSection: Int = section / 2
    let secItem = sections[virtualSection]
    if section % 2 == 0 {
        return 1
    }
    if secItem.isOpened {
        return secItem.options.count
    }
    return 0
}

similarly, in cellForRowAt:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let virtualSection: Int = indexPath.section / 2
    let secItem = sections[virtualSection]
    if indexPath.section % 2 == 0 {
        // return a "header row cell"
    }
    // return a "option row cell"
}

and finally, in didSelectRowAt:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    let virtualSection: Int = indexPath.section / 2
    // if it's a "header row"
    if indexPath.section % 2 == 0 {
        sections[virtualSection].isOpened.toggle()
        guard let c = tableView.cellForRow(at: indexPath) as? ExpandCell else { return }
        UIView.animate(withDuration: 0.3) {
            if self.sections[virtualSection].isOpened {
                c.chevronImageView.transform = CGAffineTransform(rotationAngle: .pi/2)
            } else {
                c.chevronImageView.transform = .identity
            }
            // reload the NEXT section
            tableView.reloadSections([indexPath.section + 1], with: .automatic)
        }
    }

}

Here's a complete implementation to try out. Everything is done via code (no @IBOutlet connections), so create a new UITableViewController and assign its custom class to ExpandSectionTableViewController:

struct SortingSection {
    var title: String = ""
    var subtitle: String = ""
    var options: [String] = []
    var isOpened: Bool = false
}
class ExpandCell: UITableViewCell {
    let titleLabel = UILabel()
    let subtitleLabel = UILabel()
    let chevronImageView = UIImageView()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(titleLabel)
        
        subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(subtitleLabel)
        
        chevronImageView.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(chevronImageView)
        
        let g = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: g.topAnchor),
            titleLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            
            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4.0),
            subtitleLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            
            chevronImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
            chevronImageView.widthAnchor.constraint(equalToConstant: 40.0),
            chevronImageView.heightAnchor.constraint(equalTo: chevronImageView.widthAnchor),
            chevronImageView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            
            subtitleLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor),
            
        ])
    
        subtitleLabel.font = .systemFont(ofSize: 12.0, weight: .regular)
        subtitleLabel.textColor = .gray
        
        chevronImageView.contentMode = .center
        let cfg = UIImage.SymbolConfiguration(pointSize: 24.0, weight: .regular)
        if let img = UIImage(systemName: "chevron.right", withConfiguration: cfg) {
            chevronImageView.image = img
        }
        
    }
}
class SubCell: UITableViewCell {
    
    let titleLabel = UILabel()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(titleLabel)
        
        let g = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            titleLabel.topAnchor.constraint(equalTo: g.topAnchor),
            titleLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            titleLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor),
        ])
        
        titleLabel.font = .italicSystemFont(ofSize: 15.0)
        
    }
}

class ExpandSectionTableViewController: UITableViewController {
    
    var sections: [SortingSection] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let optCounts: [Int] = [
            2, 3, 2, 5, 4, 2, 2, 3, 3, 4, 2, 1, 2, 3, 4, 3, 2
        ]
        for (i, val) in optCounts.enumerated() {
            var opts: [String] = []
            for n in 1...val {
                opts.append("Section \(i) - Option \(n)")
            }
            sections.append(SortingSection(title: "Title \(i)", subtitle: "Subtitle \(i)", options: opts, isOpened: false))
        }
        
        tableView.register(ExpandCell.self, forCellReuseIdentifier: "expCell")
        tableView.register(SubCell.self, forCellReuseIdentifier: "subCell")
    }
    override func numberOfSections(in tableView: UITableView) -> Int {
        return sections.count * 2
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        let virtualSection: Int = section / 2
        let secItem = sections[virtualSection]
        if section % 2 == 0 {
            return 1
        }
        if secItem.isOpened {
            return secItem.options.count
        }
        return 0

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

        let virtualSection: Int = indexPath.section / 2
        let secItem = sections[virtualSection]
        if indexPath.section % 2 == 0 {
            let c = tableView.dequeueReusableCell(withIdentifier: "expCell", for: indexPath) as! ExpandCell
            c.titleLabel.text = secItem.title
            c.subtitleLabel.text = secItem.subtitle
            c.chevronImageView.transform = secItem.isOpened ? CGAffineTransform(rotationAngle: .pi/2) : .identity
            c.selectionStyle = .none
            return c
        }
        let c = tableView.dequeueReusableCell(withIdentifier: "subCell", for: indexPath) as! SubCell
        c.titleLabel.text = secItem.options[indexPath.row]
        return c

    }
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        let virtualSection: Int = indexPath.section / 2
        // if it's a "header row"
        if indexPath.section % 2 == 0 {
            sections[virtualSection].isOpened.toggle()
            guard let c = tableView.cellForRow(at: indexPath) as? ExpandCell else { return }
            UIView.animate(withDuration: 0.3) {
                if self.sections[virtualSection].isOpened {
                    c.chevronImageView.transform = CGAffineTransform(rotationAngle: .pi/2)
                } else {
                    c.chevronImageView.transform = .identity
                }
                // reload the NEXT section
                tableView.reloadSections([indexPath.section + 1], with: .automatic)
            }
        }

    }
    
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文