0

An app I'm working on has a customized UIButton class that is supposed to create a rounded button of a size depending on its title.

From when I've updated to iOS 13, something strange is happening:

the first time the view containing these buttons appears, everything works well. Then I segue to another view controller, then go back to the view with the buttons and here I get an infinite loop on the -layoutsubviews method.

This is the customized class:

#import "RoundedButton.h"

@interface RoundedButton()
@property (nonatomic, retain) NSString *text;
@end

@implementation RoundedButton

-(void)awakeFromNib{
    [super awakeFromNib];
    _text = self.currentTitle;
    [super setTitle:@"" forState:UIControlStateNormal];
}

-(void)layoutSubviews{
    [super layoutSubviews];
    NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
    style.alignment = NSTextAlignmentCenter;

    NSDictionary <NSString *, id>*attr = @{  NSFontAttributeName:self.titleLabel.font, NSParagraphStyleAttributeName:style };
    CGRect r = self.bounds;
    r.size = [_text sizeWithAttributes:attr];
    r.size.width += 20;
    r.size.height += 20;

    r.size.width = MAX( r.size.width, 200 );
    r.size.height = MAX( r.size.height, 40 );

    [self setBounds:r];
}

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    if ( ![self.backgroundColor isEqual:[UIColor clearColor]] ){
        CGContextRef context = UIGraphicsGetCurrentContext();

        CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
        UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:_cornerRounded];

        CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
        [bezierPath fill];


        //CGContextSetBlendMode(context, kCGBlendModeCopy);
        //CGContextFillRect(context, self.bounds);
        CGContextSetBlendMode(context, kCGBlendModeClear);

        NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
        style.alignment = NSTextAlignmentCenter;

        NSDictionary <NSString *, id>*attr = @{  NSFontAttributeName:self.titleLabel.font, NSParagraphStyleAttributeName:style };

        CGSize szText = [_text sizeWithAttributes:attr];

        CGRect r = self.bounds;
        r.origin.y += (r.size.height - szText.height)/2;

        [_text drawInRect:r withAttributes:attr];
    }
}

-(void)setEnabled:(BOOL)enabled{
    [super setEnabled:enabled];
    if ( !enabled ){
        self.alpha = 0.8;
    }
    else{
        self.alpha = 1.0;
    }
}

-(void)setTitle:(NSString *)title forState:(UIControlState)state{
    _text = title;
    [self setNeedsDisplay];
}


-(void)setAttributedTitle:(NSAttributedString *)title forState:(UIControlState)state{
    _text = title.string;
    [self setNeedsDisplay];
}

@end
Aleph72
  • 877
  • 1
  • 13
  • 40
  • You set bounds of the button at the end of `layoutSubviews`. Setting size of a UIView calls `layoutSubviews` again. – denysowova Feb 11 '20 at 11:44
  • Someone else made this code, but I didn't notice any problem until updated my iPad to iOS 13.3.1. Where do you think I should put the code that is now in the -layoutSubviews method? – Aleph72 Feb 11 '20 at 11:52
  • As I understand, the code recalculates the size of the button based on the text. I think you can move this code to `setText` method. So when the `text` property is set, the size of the button is recalculated. – denysowova Feb 11 '20 at 12:02
  • Just tried, the code gets called, but it doesn't work. The buttons are simple circles with just a little part of its titles showing. – Aleph72 Feb 11 '20 at 12:07
  • Does `drawRect:` method get called? – denysowova Feb 11 '20 at 12:30
  • Yes, just after the setText – Aleph72 Feb 11 '20 at 12:35
  • You can compare `r` and `bounds` and call `setBounds:` if they are different – Cy-4AH Feb 11 '20 at 12:42
  • Where? When "r" is created is equal to "bounds". The thing that puzzles me is that all this code works just fine the first time the view controller calls it. Then if I segue to another view controller and come back, it loops... – Aleph72 Feb 11 '20 at 12:48
  • *"Someone else made this code..."* -- so, do you **know** this is what you want to be using? Maybe if you describe (and show images) of what you are trying to achieve... – DonMag Feb 11 '20 at 16:50
  • The code is for changing some buttons depending on their title. I just want this to work. As I said, it was working before the update to iOS 13, and now it doesn't. I'm trying to fix this. – Aleph72 Feb 11 '20 at 16:52
  • @Aleph72 - I haven't been able to replicate your infinite loop problem. Can you show a little more of what you're doing? Are there multiple buttons? Are they arranged using constraints / auto-layout? If so, is the problem possibly due to conflicts with related constraints? – DonMag Feb 12 '20 at 15:40
  • The only constraint the two buttons have are for centering in the X axis and distance from the bottom. By the way I've solved the problem using auto layout. I'm going to publish the code as a reference. – Aleph72 Feb 13 '20 at 07:34

1 Answers1

0

At the end I've solved the problem using auto layout.

Instead of using the setBounds method, I'm using the calculated size to set the height and width of the buttons with constraints.

This is the new code:

#import "RoundedButton.h"

@interface RoundedButton()
@property (nonatomic, retain) NSString *text;
@end

@implementation RoundedButton

-(void)awakeFromNib{
    [super awakeFromNib];
    _text = self.currentTitle;
    [super setTitle:@"" forState:UIControlStateNormal];

    [self setConstraint];
}

- (void)setConstraint {
    NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
    style.alignment = NSTextAlignmentCenter;

    NSDictionary <NSString *, id>*attr = @{  NSFontAttributeName:self.titleLabel.font, NSParagraphStyleAttributeName:style };
    CGRect r = self.bounds;
    r.size = [_text sizeWithAttributes:attr];
    r.size.width += 20;
    r.size.height += 20;

    r.size.width = MAX( r.size.width, 200 );
    r.size.height = MAX( r.size.height, 40 );

    [self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:r.size.width]];

    [self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:r.size.height]];
}

-(void)layoutSubviews{
    [super layoutSubviews];
}

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    if ( ![self.backgroundColor isEqual:[UIColor clearColor]] ){
        CGContextRef context = UIGraphicsGetCurrentContext();

        CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
        UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:_cornerRounded];

        CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
        [bezierPath fill];


        //CGContextSetBlendMode(context, kCGBlendModeCopy);
        //CGContextFillRect(context, self.bounds);
        CGContextSetBlendMode(context, kCGBlendModeClear);

        NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
        style.alignment = NSTextAlignmentCenter;

        NSDictionary <NSString *, id>*attr = @{  NSFontAttributeName:self.titleLabel.font, NSParagraphStyleAttributeName:style };

        CGSize szText = [_text sizeWithAttributes:attr];

        CGRect r = self.bounds;
        r.origin.y += (r.size.height - szText.height)/2;

        [_text drawInRect:r withAttributes:attr];
    }
}

-(void)setEnabled:(BOOL)enabled{
    [super setEnabled:enabled];
    if ( !enabled ){
        self.alpha = 0.8;
    }
    else{
        self.alpha = 1.0;
    }
}

-(void)setTitle:(NSString *)title forState:(UIControlState)state{
    _text = title;
    [self setConstraint];
}


-(void)setAttributedTitle:(NSAttributedString *)title forState:(UIControlState)state{
    _text = title.string;
    [self setConstraint];
}

@end
Aleph72
  • 877
  • 1
  • 13
  • 40