iOS 8 UITableView rotation bug

2019-05-24 11:23发布

问题:

So after compiling an app on XCode 6, I noticed a strange bug that happens only when running on iOS 8: The UITableView takes the wrong inner dimensions after updating its frame.

Now I'll try to explain the exact situation: We have a UITableView rotated on its side, which basically makes a horizontal UITableView. It happens through tableView.transform = CGAffineTransformMakeRotation(-M_PI / 2);. Now after setting the transform, and then settings its frame - everything is fine. But of course the system in most cases sends the parent another frame change because it needs to set the parent to the real sizes and not the XIB sizes or any initialization size. In that moment - when I relayout the subviews, including the table view - everything goes wrong. Actually the frame of the table view is simply set to the bounds of the containing view, but then the inner scrollview (In iOS 8 the UITableView has another UIScrollView inside it, called UITableViewWrapperView. As UITableView is a UIScrollView by itself, I can't figure out why they needed another one...) takes a "height" which equals the parent width. And "height" is actually the width property, only rotated.

Now we can easily estimate the they have a bug with relating the width of the inner UIScrollView to the actual width of the parent UITableView, which could possibly be by reading the .frame.size.width instead of the .bounds.size.width. But the strange thing is that when investigating the frame of the subviews of the UITableView- it seems that they are all good! So it must be a rendering problem somewhere.

So we are left with a horizontal table which has a blank gap on top, because the "height" of the cells is 320 instead of 568, while the "width" of the cells is fine, set to 320.

I'll be very happy to hear from other people experiencing this problem (Or from Apple), but I have finally found a solution and posting it here with the question, for future reference for me and for others.

回答1:

So the change that made it behave, was instead of doing this:

- (void)layoutSubviews
{
    tableView.frame = self.bounds;
}

I have reset the transform, set the frame to the bounds which the UITableView would expect locally after the transform, and then set the transform and set the correct frame. This is a bit confusing, but here it goes:

- (void)layoutSubviews
{
    if (UIDevice.currentDevice.systemVersion.floatValue >= 8.f)
    {
        // iOS 8 layout bug! Table's "height" taken from "width" after changing frame. But then if we cancel transform, set width/height to the final width/height, and rotate it and set to the virtual width/height - it works!

        CGRect rotatedFrame = self.bounds,
        unrotatedFrame = rotatedFrame;
        unrotatedFrame.size.width = rotatedFrame.size.height;
        unrotatedFrame.size.height = rotatedFrame.size.width;

        tableView.transform = CGAffineTransformIdentity;
        tableView.frame = unrotatedFrame;
        tableView.transform = CGAffineTransformMakeRotation(-M_PI / 2);
        tableView.frame = rotatedFrame;
    }
    else
    {
        tableView.frame = self.bounds;
    }
}


回答2:

This appears to be a new problem with iOS8. When you want to rotate an object it no longer appears to rotate around the upper left corner of the object's frame.

Apple docs for iOS8 state that "an object is rotated about it's center point". So when a vertical UITableView is rotated 90 degrees, it may disappear from view because the center point may be off the visible area. In order to make the table appear as if it was rotated about the upper left corner of the table, you must now also translate the frame by an amount equal to the difference between the frame width and frame height.

It's important to note you need to concatenate the transforms in order to get the desired result, like the following:

First create a 90 degree rotation transform:

    CGAffineTransform xform_rotate = CGAffineTransformMakeRotation(-M_PI * 0.5);

Then create a translation amount variable equal to the difference between table width and height:

float translateAmount = (camThumbsTableView.frame.size.height/2)-(camThumbsTableView.frame.size.width/2);

Then concatenate the original rotation transform with the translation:

    CGAffineTransform xform_total = CGAffineTransformTranslate(xform_rotate, translateAmount, translateAmount);

When done, you can now transform your tableView as follows:

    self.camThumbsTableView.transform = xform_total;

This will have the effect of both rotating and translating your tableView such that it now appears to have been rotated around the upper left corner of the tableView instead of about the center point.