I guess the layoutSubviews
override shown in the question is on a custom subclass of UITableViewCell
.
The problem here is probably that myImageView
is not a direct subview of the cell.
It's important to understand that the cell's call to [super layoutSubviews]
only sets the frames of the cell's direct subviews. It doesn't walk all the way down the view hierarchy before returning. The cell normally has only one direct subview: its contentView
. So the call to [super layoutSubviews]
just sets the frame of the content view and then returns. The attempt to set self.myImageView.layer.cornerRadius
is happening too soon, because self.myImageView.frame
hasn't been updated yet.
Later, after the cell's layoutSubviews
method returns, UIKit will send the layoutSubviews
message to the content view, at which time the content view will set its own direct subview's frames. That is when myImageView
's frame will be set.
The easiest fix for this is just to make a UIImageView
subclass and override its layoutSubviews
to set its own corner radius.
@interface CircleImageView: UIImageView
@end
@implementation CircleImageView
- (void)layoutSubviews {
[super layoutSubviews];
self.layer.cornerRadius = self.bounds.size.width / 2;
}
@end
UPDATE
Matic Oblak asks (in a comment), “Is this what you wrote here testable”?
Yes, it's testable. Here's one way to test it: create a view hierarchy with a root view, a container view inside the root view, and a leaf view inside the container view:

Override layoutSubviews
in the root view and the container view to print the frames of the views:
// RootView.m
@implementation RootView {
IBOutlet UIView *_containerView;
IBOutlet UIView *_leafView;
}
- (void)layoutSubviews {
NSLog(@"%s before super: self.frame=%@ _containerView.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_containerView.frame), NSStringFromCGRect(_leafView.frame));
[super layoutSubviews];
NSLog(@"%s after super: self.frame=%@ _containerView.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_containerView.frame), NSStringFromCGRect(_leafView.frame));
}
@end
// ContainerView.m
@implementation ContainerView {
IBOutlet UIView *_leafView;
}
- (void)layoutSubviews {
NSLog(@"%s before super: self.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_leafView.frame));
[super layoutSubviews];
NSLog(@"%s after super: self.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_leafView.frame));
}
@end
For good measure, let's also see when the view controller's viewWillLayoutSubviews
and viewDidLayoutSubviews
are run:
// ViewController.m
@implementation ViewController
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
NSLog(@"%s", __FUNCTION__);
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSLog(@"%s", __FUNCTION__);
}
@end
Finally, run it on a device or simulation at a different size than in the storyboard, and look at the debug console:
-[ViewController viewWillLayoutSubviews]
-[RootView layoutSubviews] before super: self.frame={{0, 0}, {375, 667}} _containerView.frame={{20, 40}, {280, 420}} _leafView.frame={{20, 20}, {240, 380}}
-[RootView layoutSubviews] after super: self.frame={{0, 0}, {375, 667}} _containerView.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {240, 380}}
-[ViewController viewDidLayoutSubviews]
-[ContainerView layoutSubviews] before super: self.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {240, 380}}
-[ContainerView layoutSubviews] after super: self.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {295, 567}}
Notice that the root view's [super layoutSubviews]
changes the frame of the container view, but does not change the frame of the leaf view. Then, the container view's [super layoutSubviews]
changes the size of the leaf view.
Therefore, when layoutSubviews
has run in a view, you can assume that the frames of the view's direct subviews have been updated, but you must assume that the frames of any more deeply-nested views have not been updated.
Note also that viewDidLayoutSubviews
runs after the view controller's view has done layoutSubviews
, but before the more deeply-nested descendants have run layoutSubviews
. So in viewDidLayoutSubviews
, you must again assume that the frames of the more deeply-nested views have not been updated.
There is a way to force the frame of a more deeply-nested subview to be updated immediately: send layoutIfNeeded
to its superview. For example, I can modify -[RootView layoutSubviews]
to send layoutIfNeeded
to _leafView.superview
:
- (void)layoutSubviews {
NSLog(@"%s before super: self.frame=%@ _containerView.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_containerView.frame), NSStringFromCGRect(_leafView.frame));
[super layoutSubviews];
NSLog(@"%s after super: self.frame=%@ _containerView.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_containerView.frame), NSStringFromCGRect(_leafView.frame));
[_leafView.superview layoutIfNeeded];
NSLog(@"%s after _leafView.superview: self.frame=%@ _containerView.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_containerView.frame), NSStringFromCGRect(_leafView.frame));
}
(Note that in this example project, _leafView.superview
is _containerView
, but in general it might not be.) Here's the result:
-[ViewController viewWillLayoutSubviews]
-[RootView layoutSubviews] before super: self.frame={{0, 0}, {375, 667}} _containerView.frame={{20, 40}, {280, 420}} _leafView.frame={{20, 20}, {240, 380}}
-[RootView layoutSubviews] after super: self.frame={{0, 0}, {375, 667}} _containerView.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {240, 380}}
-[ContainerView layoutSubviews] before super: self.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {240, 380}}
-[ContainerView layoutSubviews] after super: self.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {295, 567}}
-[RootView layoutSubviews] after _leafView.superview: self.frame={{0, 0}, {375, 667}} _containerView.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {295, 567}}
-[ViewController viewDidLayoutSubviews]
So now I have forced UIKit to update the frame of _leafView
before -[RootView layoutSubviews]
returns.
As for “I assume many operations like setting a simple text on the label will call setNeedsLayout”: you can use the same technique I just demonstrated to figure out the answer to that question.