Explain the basic method of changing UIButton internal controls in iOS App development

  • 2020-05-24 06:13:35
  • OfStack

By default, UIButton has an UIImageView and UILabel controls, which can be accessed with the following properties:


@property(nonatomic,readonly,retain) UIImageView *imageView;
@property(nonatomic,readonly,retain) UILabel     *titleLabel;

UIButton displays text only because of its internal titleLabel, that is, UIButton's setTitle:forState: method, which sets the string to be displayed on titleLabel

The image of UIButton's setImage:forState: method Settings is displayed on the internal imageView.

Note:
1. To set the text or color of the button, use the following method


- (void)setTitle:(NSString *)title forState:(UIControlState)state;
- (void)setTitleColor:(UIColor *)color forState:(UIControlState)state;

warnning: cannot get titleLabel directly to set text and text color. For example, the following is wrong:

button.titleLabel.text = @"12323";
button.titleLabel.textColor = [UIColor redColor];

2. To set the small picture inside the button, the following method must be used


- (void)setImage:(UIImage *)image forState:(UIControlState)state;

warnning: you cannot get the imageView Settings directly. For example, the following is wrong:

button.imageView.image = [UIImage imageNamed:@"abc.png"];

Well, with that in mind, let's move on to the topic of this article

Change UIButton internal controls
When UIButton is set to title and image, UIButton is composed of 1 UIButtonLabel + 1 UIImageView. But their default format is fixed, with 1 UIImage on the left and 1 UIButtonLabel on the right. Now if we want UIImage to appear above this button, UIButtonLabel to appear below this button.

We can completely customize 1 control to implement the above, or we can change its internal child controls based on UIButton. The second method is used here.

1. First of all, if you want to change the position of the child control, the first thing that might come to mind is to get this button and then access its imageView and titleLabel properties.

We can first print 1 on this button and take a look at the internal structure:


NSLog(@"%@", self.button.subviews);

The result is an empty array! What's going on here?

In fact, the child controls inside UIButton are lazily loaded, which means that if the child controls are not used, they will not be loaded.

So let's reassign the frame values of these two controls, so that we can not only use the two controls to load them, but also see if we can directly change the frame of these two controls to achieve the purpose of rearranging the two controls.

However, if you do this, you will find that the actual button display has not changed, indicating that directly changing the frame of UIButton internal controls will not achieve the purpose of rearranging.

Then print out the child control inside the button:


NSLog(@"%@", self.button.subviews);

You will find that the subviews array is now 1 UIImageView + 1 UIButtonLabel, and now they have a value (lazy loading because of the previous use of these two controls).

However, if you look closely, you will find that the frame of these two controls is obviously the frame we just assigned, but why is it not displayed according to this frame?

Because the printed frame is just what we set, and UIButton will recalcate the size according to its UIImageView and UIButtonLabel contents when it is displayed, even if we change the frame of the child control, we cannot really change the position and size of the child control.

2. The second idea is to inherit UIButton and change it based on the original button.

For example, we create a subclass of UIButton, CYLButton, and implement the following methods in the implementation file of CYLButton:


- (CGRect)titleRectForContentRect: (CGRect)contentRect // control label Where is the display and the size
{
    return CGRectMake(0, contentRect.size.width, contentRect.size.width, contentRect.size.height - contentRect.size.width);
} - (CGRect)imageRectForContentRect: (CGRect)contentRect // control image Where is the display and the size
{
    return CGRectMake(0, 0, contentRect.size.width, contentRect.size.width);
} // contentRect1 As to represent the UIButton the bounds.size // We can do it here initWithFrame: Set in method UIButton Properties of the internal control
- (instancetype)initWithFrame: (CGRect)frame
{
      if (self = [super initWithFrame: frame]) {
        self.titleLabel.backgroundColor = [UIColor blueColor];
          self.titleLabel.textAlignment = NSTextAlignmentCenter;
          self.imageView.backgroundColor = [UIColor yellowColor];
      }
      return self;
}

And you can see that this is going to work for us. But there's a downside, and if we set something in one method and we want to use it in the other method, it's not very convenient.

3. A better approach is to override the layoutSubviews method, which makes it easy to adjust child controls.


- (void)layoutSubviews
{
    [super layoutSubviews];       CGFloat imageW = self.bounds.size.width;
      CGFloat imageH = imageW;
      self.imageView.frame = CGRectMake(0, 0, imageW, imageH);       CGFloat titleY = imageH;
      CGFloat titleW = imageW;
      CGFloat titleH = self.bounds.size.height - titleY;
    self.titleLabel.frame = CGRectMake(0, titleY, titleW, titleH);
}

This might seem odd, because what we just changed outside of this class was imageView and titleLabel frame, but it didn't work, and in the layoutSubviews method it did, so why did it work?

Because we just changed the frame of the child control on the outside, but when we execute the layoutSubviews method on the inside, we will set their frame again to the size corresponding to image and title. Now we can modify their frame directly in layoutSubviews, overwriting the previous step of setting their frame to the default size, so it is now possible to succeed, and because variables can be Shared in one method.

It is also important to note that if you inherit from UIButton (e.g. CYLButton), when you have a data model and want to assign values to child controls in CYLButton's setter method, you cannot do this directly:


self.imageView = ...
self.text = ...

Since self(CYLButton) is inherited from UIButton, both image and title are stateful, thus:

- (void)setTitle:(NSString *)title forState:(UIControlState)state;
- (void)setTitleColor:(UIColor *)color forState:(UIControlState)state;
0
So whether or not you can modify it directly depends on whether the property is in any state. If you have a split state, you cannot modify it directly.


Related articles: