Search code examples
iosswiftuiimageviewuikitcgaffinetransform

UIImageView wrong rotation using CGAffineTransform


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

When I tap on a section I want it accessory (chevron.right, made as UIImageView) be rotated clockwise by 90 degrees with a smooth animation and when I click again on the same section - rotate back, counterclockwise by 90 degrees.

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:

   override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
      if indexPath.row == 0 {
        sections[indexPath.section].isOpened = !sections[indexPath.section].isOpened
        tableView.reloadSections([indexPath.section], with: .none)
      }
   }

In the same method I try to rotate the chevron:

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    if indexPath.row == 0 {
        sections[indexPath.section].isOpened = !sections[indexPath.section].isOpened
        tableView.reloadSections([indexPath.section], with: .none)
        
        guard let cell = tableView.cellForRow(at: indexPath) as? MainSortTableViewCell else { return }

        if sections[indexPath.section].isOpened {
            UIView.animate(withDuration: 1.0) {
                cell.chevronImage.transform = CGAffineTransform(rotationAngle: .pi/2)
            }
        } else {
            UIView.animate(withDuration: 1.0) {
                cell.chevronImage.transform = CGAffineTransform(rotationAngle: -.pi/2)
            }
        }
     }

But with this approach I face a couple of problems:

  1. While first tap it animates chevron down on 90 degrees as I need, but when I tap again - it rotates initial chevron (face to the right) which causes a result as chevron looks up instead (-pi/2). How to fix the behaviour and rotate chevron from its new position (from when its face is down back to initial right)?
  2. When I try to tap after first rotation the animation will be never triggered again. The transition is immediate. How to fix that?

Here is a GIF with the problem behaviour: CLICK


Solution

  • You misunderstood what the transform property means. transform represents the transformation of the view from its original position (facing right, in this case), and so setting it to CGAffineTransform(rotationAngle: -.pi / 2) makes the view transform in such a way that it rotates -.pi / 2 radians from its original position, i.e. facing up.

    To achieve the desired rotation, you can either compose this -.pi/2 rotation with the current transform of the view, or just set transform to .identity (no transform at all, from its original position):

    cell.chevronImage.transform = .identity
    // or
    cell.chevronImage.transform = cell.chevronImage.transform.rotated(by: -.pi / 2)
    

    I suspect that you haven't added this rotation code in cellForRowAtIndexPath. Please do that, just without the animation. Otherwise you would run into problems like the chevron resetting after scrolling the table view (see also). However, this would mean that it would be rotated too early, when you call reloadSections. The easiest way to solve this is to move reloadSections after the animation:

    UIView.animate(withDuration: 1.0) {
        if sections[indexPath.section].isOpened {
            cell.chevronImage.transform = CGAffineTransform(rotationAngle: .pi/2)
        } else {
            cell.chevronImage.transform = .identity
        }
    } completion: {_ in
        self.tableView.reloadSections([indexPath.section], with: .none)
    }