1

There are a couple of questions asking this on SO but none have asked that properly.

I am using a custom progress bar that is a UIView based class with this implementation.

@implementation MCProgressBarView {
    UIImageView * _backgroundImageView;
    UIImageView * _foregroundImageView;
    CGFloat minimumForegroundWidth;
    CGFloat availableWidth;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
  self = [super initWithCoder:aDecoder];
  // [self bounds] is 1000x1000 here... what? 
  if (self) [self initialize];
  return self;
}


- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) [self initialize];
    return self;
}

- (void) initialize {
  UIImage * backgroundImage = [[[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"progress-bg" ofType:@"png"]]
                               resizableImageWithCapInsets:UIEdgeInsetsMake(0.0f, 10.0f, 0.0f, 10.0f)];

  UIImage * foregroundImage = [[[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"progress-bg" ofType:@"png"]]
                               resizableImageWithCapInsets:UIEdgeInsetsMake(0.0f, 10.0f, 0.0f, 10.0f)];

  _backgroundImageView = [[UIImageView alloc] initWithFrame:self.bounds];
  _backgroundImageView.image = backgroundImage;
  [self addSubview:_backgroundImageView];

  _foregroundImageView = [[UIImageView alloc] initWithFrame:self.bounds];
  _foregroundImageView.image = foregroundImage;
  [self addSubview:_foregroundImageView];

  UIEdgeInsets insets = foregroundImage.capInsets;
  minimumForegroundWidth = insets.left + insets.right;

  availableWidth = self.bounds.size.width - minimumForegroundWidth;

  [self adjustProgress:0.0f];
}

I have created a UIView on interface builder to be 200x20 points and assigned that view to be of this custom class MCProgressBarView I am using. When the app runs, initWithCoder runs and creates a progress view with a size of 1000x1000 points, disregarding the size that the view has on IB.

How do I force initWithCoder to run with the proper size assigned on IB?

NOTE: I don't want to hard wire that to 200x20 and I don't want to set this by code at run time. Is there a way to make this work?

Léo Natan
  • 56,823
  • 9
  • 150
  • 195
Duck
  • 34,902
  • 47
  • 248
  • 470

1 Answers1

2

TL;DR: Move your frame/bounds logic to viewDidLayoutSubviews.

initWithCoder: is not safe for performing frame logic. You should use viewDidLayoutSubviews for that. Apple uses 1000x1000 bounds starting with iOS 10. It is unclear if bug or intentional, but it seems to have a net positive outcome - people come here and ask about it. ;-)

In fact, initWithCoder: has never been safe for such logic. In the past, you'd have a view whose bounds would be those of iPhone 5's screen because that was what you used in IB, but then the view would grow to iPhone 6 Plus size or iPad size, and it would cause visual issues.

Now, Apple just sets the bounds to 1000x1000, which is incorrect for all cases. The bounds are corrected once a layout pass is performed on the view.

Paulw11
  • 108,386
  • 14
  • 159
  • 186
Léo Natan
  • 56,823
  • 9
  • 150
  • 195
  • 1
    There is no viewDidLayoutSubviews on a UIView based class. If I use the viewController to set the view size correctly then this whole thing is poorly conceived by Apple as a lot of things on ios. – Duck Oct 22 '16 at 19:38
  • @SpaceDog Use `layoutSubviews`. – Léo Natan Oct 22 '16 at 19:39
  • Ok. I have implemented a `layoutSubviews` on the class. When that method runs, the view is with the correct size without I having to do anything but when it displays on the application it is 1000x1000. – Duck Oct 22 '16 at 19:41
  • Please post screenshots. Something sounds strange. – Léo Natan Oct 22 '16 at 19:42