10

I am having a problem with setting the corner radius on the view because ever since the Xcode update to 8 the frames of the views are in most cases set to 1000x1000 instead of appropriate size.

There is a similar question here but I would like to add a bit more info in hope someone found the answer or a workaround.

In one of the instances I have a table view cell which has an image view created in storyboard. Its width and height are set as constant with constraint (to 95). The corner radius is set in the layoutSubviews which has been working for now:

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.myImageView.layer.cornerRadius = self.myImageView.frame.size.width/2.0f;
}

So I played around a bit with trial and error and the situation seems to perform a bit better if I try to trigger layout after the image is set (an image is retrieved from remote server) by calling:

self.myImageView.image = image;
[self.myImageView setNeedsLayout];
[self setNeedsLayout];
[self layoutIfNeeded];

Still this does not work in most cases. Now the funny part:

I have a case of this view controller on the tab bar where when I press the tab with the target view controller there will be a call to layout subviews immediately (from UIApplicationMain) where the size of the image view is 1000x1000 but once the image is set and I call the layout methods then the layout subviews is called again with correct image view size of 95x95 getting a correct result.

The second situation is pushing the view controller of the same type where now the layout is not being called at first, the only call is made after the image is set and layout is forced. The result is again having an image view of size 1000x1000, setting the corner radius to 500 and the image is not visible.

So is there a nice solution to fix these strange frame values? I have the same problem on multiple view controllers, table view cells. It all used to work until the update.

Has anyone any ideas what is the source of this problem even? Is it from the storyboard or some sort of settings migration or is this some layout bug introduced with this version of software?

I now also added the awake from nib:

- (void)awakeFromNib
{
    [super awakeFromNib];

    [self.myImageView setNeedsLayout];
    [self setNeedsLayout];
    [self layoutIfNeeded];
}

Which now forces the layout to happen at least 2 times in my case and the second time the frames are correct. Still this is far from the answer, this is awful and I would like to avid it since I would need to use it on all of the cells for each of the image views, buttons.

Community
  • 1
  • 1
Matic Oblak
  • 16,318
  • 3
  • 24
  • 43
  • I'm having a similar issue with an overridden `layoutSubviews` on a table cell never being called with the correct frame size the first time the cell is displayed. I suspect it's some kind of layout bug since there were a decent amount of changes to the layout systems. – NewShelbyWoo Sep 26 '16 at 22:54
  • @NewShelbyWoo I added an answer with workaround for this issue. Please let me know if this fixes your issue for now. Thank you. – Matic Oblak Sep 27 '16 at 07:41
  • I ended up fixing it by calling `myImageView.layoutIfNeeded` right before doing the manipulation on it in `layoutSubviews` – NewShelbyWoo Sep 27 '16 at 18:14
  • What class is that `layoutSubviews` method on? – rob mayoff Jan 24 '18 at 07:30
  • @robmayoff This post should now be deprecated as Apple has fixed this strange behaviour. `layoutSubviews` is a method directly on `UIView` and should generally not be called but is useful to override. What issue are you experiencing? – Matic Oblak Jan 24 '18 at 07:38
  • I asked the question because @AMAN77 has posted a bounty on your question. I guess he is running into the same problem. Anyway, I have already posted an answer based on some guesswork. – rob mayoff Jan 24 '18 at 07:41
  • Yeah So I had my corner radius set to the width/2, but it was more oblong than circle. I have to set it to something like width-8.0 / 2 to get more of a circle but this doesn't seem right. – AMAN77 Jan 24 '18 at 07:45
  • @MaticOblak can you provide with some screenshot so we could understand you problem visually? – arturdev Jan 29 '18 at 13:01
  • @arturdev As already mentioned from my perspective this question is deprecated. It was an issue specific to a version of iOS or the SDK. Although it is still a valid question as well as provided answers I assume that a member that put bounty on this thread is experiencing another issue with similar results. In any case if you are interested in digging this a bit deeper I suggest you to mention AMAN77 and ask him for details. As for what my results were like you can mock them by simply setting a corner radius of 500 on a 60x60 view to see what happens (expected result but not circle obviously) – Matic Oblak Jan 29 '18 at 13:12
  • set `self. myImageView .layer.masksToBounds = YES;` – Dhiru Jan 31 '18 at 06:51

7 Answers7

10

Can you just update your method to

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.myImageView.layer.cornerRadius = self.myImageView.frame.size.width/2.0f;
    self.myImageView.clipsToBounds = TRUE;
}

and give one try.

Coz in one of my case, I missed setting clipsToBounds = TRUE; and it was not making it circle.

Pushkraj Lanjekar
  • 2,254
  • 1
  • 21
  • 34
  • 1
    The clip subviews is enabled and it works. And yes, on the image view you need to enable it or no clipping is done at all. But this is not my case, in my case the view appears correctly or it does not appear at all. And I do know why it does not which is because the corner radius is to large. And the reason for it to be so large is because the frame of the image view is too large for which the cause is totally unknown. – Matic Oblak Sep 26 '16 at 12:55
  • Ok, I missed it. But I faced same issue and resolved using clipsToBounds so shared answer. – Pushkraj Lanjekar Sep 26 '16 at 13:03
7

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:

view hierarchy

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.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Is this what you wrote here testable or do you have any documentation about that? I am not sure who triggers the layout flag on the superview but I would expect none. A view needs layout as soon as any of its subviews needs layout. The flag is there so the layout does not occur too many times. I assume many operations like setting a simple text on the label will call setNeedsLayout and at this point it either does this to its superview as well or a getter flag checks all subviews to see if any needs layout... In both cases I expect layout will be called in same runloop for both views... – Matic Oblak Jan 31 '18 at 11:25
  • ...but in what you are describing only a label which you set the text on would actually layout which would mean that for instance an icon positioned next to it would not move (which it should and does in auto layout). So by this logic I find it hard to believe what you wrote is true. Am I missing something? – Matic Oblak Jan 31 '18 at 11:28
1

Try to override drawRect: method in UIImageView subclass and then set the corner radius.

- (void)drawRect:(CGRect)rect {
self.layer.cornerRadius = self.frame.size.width/2.0f;
[self setClipsToBounds:YES];
}
SThakur
  • 262
  • 4
  • 16
0

I have created a demo application to see what is the minimum requirement for this issue to happen and it is pretty low:

  • Create a new single view application
  • Add a table view on the main view controller
  • Add a cell to the table view and override its class as a new table view cell subclass
  • Add an image view to the cell with fixed width and height constraints (adding leading and top constraints as well)
  • In the cell layoutSubviews set the image view corner radius to half of the image view width (the result will always be 500)

The image view is not displayed due to the corner radius being 500.

So I was searching for a minimal fix and from previous testing it seems that the layout is missing the first call so I added it by overriding the awake from nib:

- (void)awakeFromNib
{
    [super awakeFromNib];
    [self setNeedsLayout];
    [self layoutIfNeeded];
}

So in the project I am working on currently I created a new subclass of a table view cell and I have overridden the awake from nib. Then I made all other cells as a subclass of this one. It is not a solution but it is a workaround and since I have 57 table view cell subclasses this procedure was quite quick to implement.

Matic Oblak
  • 16,318
  • 3
  • 24
  • 43
0

Just adding on @pushkraj answer for round view your UIView width and height should be same.

Then You can do.

- (void)layoutSubviews
{
   [super layoutSubviews];

   view.layer.cornerRadius = view.frame.size.height/2
   view.clipsToBounds = true
}
Gajendra K Chauhan
  • 3,387
  • 7
  • 40
  • 55
Mohammed Hussain
  • 176
  • 2
  • 13
-1

You can do like this

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.myImageView.layer.cornerRadius = self.myImageView.frame.size.width/2.0f;
    self. myImageView.layer.masksToBounds = YES;
}

this will work for you . see why masking layers to bounds is required link here

Dhiru
  • 3,040
  • 3
  • 25
  • 69
  • This looks like a duplicate answer from @Pushkraj which used clip subviews property. Is there a reason you could explain why this is better? And after you have done so could you care to explain how this fixes the fact that corner radius was working and clipping was enabled but the problem was in view size which was 1000x1000 regardless of constraints which should make it 95x95. – Matic Oblak Jan 31 '18 at 11:19
-3

To make any view circular you have to update the corner radius and corner radius is a property of a layer of any view(viewInfo).

From apple documentation

Setting the radius to a value greater than 0.0 causes the layer to begin drawing rounded corners on its background. By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to true causes the content to be clipped to the rounded corners.

The default value of this property is 0.0.

More about corner radius

Here is code snippet compatible with swift 4

@IBOutlet weak var viewInfo: UIView!

func setCornerRadius(){
    self.viewInfo.layer.cornerRadius = self.viewInfo.frame.size.height/2
} 
Kamar Shad
  • 6,089
  • 1
  • 29
  • 56
Deepak Tagadiya
  • 2,187
  • 15
  • 28