UIButton

1. Background

One of the things we often encounter when working on button related requirements is the typography of images and text. Some of the things we often encounter during development are:

  • Picture on top and text on bottom
  • Left picture right text
  • Right picture left text
  • Below the picture on the text (this is less oh.

However, the default button design of the system is the left picture and the right text, so we need to adapt to this adjustment.

2. Solutions

Here we do a simple configuration of the button, and then get the location of the corresponding subview

    func setupBaseBtn() {
        btn.setTitleColor(.white, for: .normal)
        btn.setTitle(btnTitle, for: .normal)
        btn.setImage(btnImg, for: .normal)
        btn.backgroundColor = .lightGray
        btn.setBackgroundImage(UIImage(imageLiteralResourceName: "backColor.jpg"), for: .normal)
    }
    
    func setupBtnFrame() {
        letOfffsetY: CGFloat = 12.0letOffsetX: CGFloat = 10.0 // imgSize: (32, 32) // 36.0lettextWidth: CGFloat = CGFloat(btn.titleLabel! .font. PointSize) * (CGFloat)(btntitle.count) // 56.0letBtnHeight: CGFloat = (CGFloat)(offfsetY * 2.0) + (CGFloat)(btnimg.sie.height) // 88.0letbtnWidth: CGFloat = (CGFloat)(btnImg.size.width) + textWidth + 2 * offsetX btn.frame = CGRect(x: (self.view.bounds.width -btnwidth) / 2.0, y: 350, width: btnWidth, height: btnHeight) /* <OverrideBounds.OverrideBoundsButton: 0x7fda67c18590; baseClass = UIButton; frame = (163 350; 88 56); opaque = NO; layer = <CALayer: 0x600000ac5360>> <UIImageView: 0x7fda67e28c10; Frame = (9.5 12; 32 32); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x600000ac19c0>> <UIButtonLabel: 0x7fda67e11ca0; Frame = (41.5 17.5; 37 21.5); text ='download'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6000029a0550>>
         */
    }
Copy the code

Effect:

2.1 Normal mode that cannot be resolved

Why is it normal? Because most of the time this is fine for adjusting the position of a child of another view, whereas UIButton doesn’t. Here we make a small adjustment to the subview, and the size of the subview position does not change.

override func viewDidLayoutSubviews() {super. ViewDidLayoutSubviews () / / that the setting of the useless BTN. ImageView? . Frame = CGRect(x: 0, y: 12, width: 32, height: 32) 21.5) / * < UIImageView: 0 x7fceaf4154b0; Frame = (9.5 12; 32 32); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x6000018c3d60>> <UIButtonLabel: 0x7fceaf518070; Frame = (41.5 17.5; 37 21.5); text ='download'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003bcc7d0>>
         */
    }
Copy the code

2.2 Adjustment by Alignment

System provides contentVerticalAlignment and contentHorizontalAlignment parameters, we can set the content alignment.

    open var contentVerticalAlignment: UIControl.ContentVerticalAlignment // how to position content vertically inside control. default is center

    open var contentHorizontalAlignment: UIControl.ContentHorizontalAlignment // how to position content horizontally inside control. default is center
Copy the code

Enumeration of content alignment

    public enum ContentVerticalAlignment : Int {

        
        case center

        case top

        case bottom

        case fill
    }

    
    public enum ContentHorizontalAlignment : Int {

        
        case center

        case left

        case right

        caseThe fill @ the available (iOS 11.0. *)caseLeading @ the available (iOS 11.0. *)case trailing
    }
Copy the code

By default, the system sets the content to be displayed in the center, so we set the content to be aligned on the top and left

/ / content display is always left picture right words / / the default content transverse and longitudinal center display / / UIControl: setting contents alignment BTN. ContentVerticalAlignment =. Top; btn.contentHorizontalAlignment = .left; /* <UIImageView: 0x7f9bc3e18b30; frame = (0 0; 32 32); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x6000038ec000>> <UIButtonLabel: 0x7f9bc3d05e30; frame = (32 0; 37 21.5); text ='download'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600001ba7160>>
         */
Copy the code

Effect:

The important thing to note here is that the fill parameter is quite special, so it’s hard to guess what its implementation is.

// Label. X is the width of the image // Align the content horizontally to fill the content rectangle; Text can be wrapped and images can be stretched. BTN. ContentHorizontalAlignment =. The fill / / vertical alignment content to fill the rectangle; The image might get stretched. btn.contentVerticalAlignment = .fill /* <UIImageView: 0x7f85f0414c00; frame = (0 0; 51.316 56); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x600003094ac0>> <UIButtonLabel: 0x7f85f04046f0; frame = (32 0; 37 56); text ='download'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6000013f6170>>
        */
Copy the code

Effect:

Here we can see that these two enumeration methods can be used to adjust the alignment of the content but not to achieve our requirements of image and text position changes. So we need another solution.

2.3 Adjust by setting margins

The system provides contentEdgeInsets, titleEdgeInsets, and imageEdgeInsets to allow you to adjust your view.

    open var contentEdgeInsets: UIEdgeInsets // default is UIEdgeInsetsZero. On tvOS 10 or later, default is nonzero except for custom buttons.

    open var titleEdgeInsets: UIEdgeInsets // default is UIEdgeInsetsZero

    open var imageEdgeInsets: UIEdgeInsets // default is UIEdgeInsetsZero
Copy the code

There’s an article that goes into a lot of detail to understand UIButton’s image edge geinsets and title edge geinsets, but we’re just going to go through it and not get into it.

Here we simply adjust the imageEdgeInsets and titleEdgeInsets to see that the position of the subview has changed.

btn.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10) btn.titleEdgeInsets = UIEdgeInsets(top: <UIImageView: 0x7ff2b6c12dc0; Frame = (4.5 12; 32 32); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x60000069e740>> <UIButtonLabel: 0x7ff2b6e1aaf0; Frame = (41.5 22.5; 37 21.5); text ='download'; opaque = NO; userInteractionEnabled = NO; Layer = <_UILabelLayer: 0x6000025B2fd0 >> // Original position <UIImageView: 0x7FceAF4154B0; Frame = (9.5 12; 32 32); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x6000018c3d60>> <UIButtonLabel: 0x7fceaf518070; Frame = (41.5 17.5; 37 21.5); text ='download'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003bcc7d0>>
         */
Copy the code

When we look at the results, we see that the change in value here is not so straightforward, it’s a good choice for some fine-tuning of the value and it can fulfill our requirements above, but it’s not so straightforward, it’s a little bit troublesome. Here’s another way to do it.

2.4 Adjust by rewriting the system approach

BackgroundRect, contentRect, titleRect, and imageRect are provided to override the subview position size.

Func backgroundRect(backgroundRect)forBounds: CGRect) -> CGRect
Returns the rectangle in whichThe receiver draws its background. // Set content location size to contentRect(forBounds: CGRect) -> CGRect
Returns the rectangle in whichThe receiver draws its entire content. // Set text titleRect(forContentRect: CGRect) -> CGRect
Returns the rectangle in whichThe receiver draws its title. // Set image content size func imageRect(forContentRect: CGRect) -> CGRect
Returns the rectangle in which the receiver draws its image.
Copy the code

Code and effect diagram display:

btn.imageRect = CGRect(x: 12, y: 15, width: 36, height: 36) btn.titleRect = CGRect(x: 45, y: 20, width: 40, height: 24) /* <UIImageView: 0x7fb28a415670; frame = (12 15; 36 36); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x6000010046a0>> <UIButtonLabel: 0x7fb28a705f80; frame = (45 20; 40 and 24); text ='download'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003347070>>
         */
        
        btn.backgroundRect = CGRect(x: 10, y: 10, width: 40, height: 40)
        /*
         <UIImageView: 0x7fc31e629740; frame = (10 10; 40 40); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x600003e6cae0>>
         */
Copy the code

Effect:

imageRect and titleRect are incompatible with contentRect and cannot be set together. imageRect,titleRect is restricted by contentRect from setting content views:

// imageRect,titleRect is limited by contentRect // btn.contentRect = CGRect(x: 0, y: 20, width: 60, height: 30) /* <UIImageView: 0x7fc731e05550; frame = (4.5 20; 32 30); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x6000000195c0>>
         <UIButtonLabel: 0x7fc731c1dd60; Frame = 24.5 (37; 18.5, 21.5); text ='download'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60000237aa30>>
         */
Copy the code

Effect:

3. Content recommendation

Understand UIButton’s imageEdgeInsets and titleEdgeInsets

IOS Button imageEdgeInsets and titleEdgeInsets

There are two articles on imageEdgeInsets and titleEdgeInsets.

UITextField

UITextField is similar to UIButton Settings for controls. Here we introduce the overrides provided by the system for the location size of child controls.

// drawing and positioning overrides // boarder... Nothing found =. = open func borderRect(forBounds Bounds: CGRect) -> CGRect // _UITextFieldContentView effectforBounds Bounds: CGRect) -> CGRect // UITextFieldLabel view effect Open func placeholderRect(forBounds Bounds: CGRect) -> CGRect // UIFieldEditor view effect open func editingRect(forBounds Bounds: CGRect) -> CGRect // Effects of the clearBtn view Open func clearButtonRect(forBounds Bounds: CGRect) -> CGRect // Effects of leftView open func leftViewRect(forBounds Bounds: CGRect) -> CGRect // rightView effect open func rightViewRect(forBounds bounds: CGRect) -> CGRect
Copy the code

Default view location

        tf.borderStyle = .roundedRect
        tf.text = "Test text Test text"
        tf.placeholder = "Placeholder text"
        tf.clearButtonMode = .always
        tf.leftViewMode = .always
        tf.rightViewMode = .always
        tf.leftView = UIImageView(image: UIImage(imageLiteralResourceName: "download.png"))
        let rightIV = UIImageView(image: UIImage(imageLiteralResourceName: "right.png"))
        rightIV.frame = CGRect(x: 0, y: 0, width: 32, height: 32)
        tf.rightView = rightIV
        
        /*
         text:
         <_UITextFieldContentView: 0x7f8ab75189f0; frame = (39 -3; 122 37); opaque = NO; userInteractionEnabled = NO; layer = <__UITextTiledLayer: 0x600003accba0>>
         leftView:
         <UIImageView: 0x7feff760b350; frame = (0 -1; 32 32); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x600001acc440>>
         editText:
         <UIFieldEditor: 0x7feff9031400; frame = (39 2; 127 26); text = 'Jk'; clipsToBounds = YES; opaque = NO; gestureRecognizers = <NSArray: 0x6000014d1bc0>; layer = <CALayer: 0x600001a95e60>; contentOffset: {0, 0}; contentSize: {127, 26}; adjustedContentInset: {0, 0, 0, 0}> <_UITextFieldContentView: 0x7feff743dcc0; frame = (0 0; 127 26); opaque = NO; userInteractionEnabled = NO; layer = <__UITextTiledLayer: 0x600003e93b40>> clearBtn: <UIButton: 0x7feff760f750; frame = (176 6; 19); opaque = NO; layer = <CALayer: 0x600001accd00>> placeholder: <UITextFieldLabel: 0x7feff98061b0; frame = (39 4; 127, 20.5); text ='the placeholder text'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003987160>>
         <UIFieldEditor: 0x7feff9031400; frame = (39 2; 127 26); text = ' '; clipsToBounds = YES; opaque = NO; gestureRecognizers = <NSArray: 0x6000014d1bc0>; layer = <CALayer: 0x600001a95e60>; contentOffset: {0, 0}; contentSize: {127, 26}; adjustedContentInset: {0, 0, 0, 0}>
         <_UITextFieldContentView: 0x7feff743dcc0; frame = (0 0; 127 26); opaque = NO; userInteractionEnabled = NO; layer = <__UITextTiledLayer: 0x600003e93b40>>
         rightView:
         <UIImageView: 0x7fa2efe404d0; frame = (168 -1; 32 32); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x600000906ac0>>
         */
Copy the code

Effect:

Notice here

  • rightViewwithclearButtonCan’t exist at the same time. Here is the result of two runs after removing one term.
  • The view has text only_UITextFieldContentViewThere is noUITextFieldLabelandUIFieldEditorcontrols
  • While the text box is being editedUIFieldEditorand_UITextFieldContentViewControl, and_UITextFieldContentViewisUIFieldEditorThe child controls of
  • When the text has placeholder text that is, no textUITextFieldLabel,UIFieldEditor,_UITextFieldContentViewWill exist at the same time andUITextFieldLabelAt the bottom of them all

Customize view location

        tf.leftViewRect = CGRect(x: 5, y: 5, width: 32, height: 32)
        tf.rightViewRect = CGRect(x: 160, y: 0, width: 35, height: 35)
        tf.clearButtonRect = CGRect(x: 180, y: 10, width: 24, height: 24)
        tf.textRect = CGRect(x: 50, y: 5, width: 100, height: 90)
        tf.placeholderRect = CGRect(x: 40, y: 6, width: 120, height: 24)
        tf.editingRect = CGRect(x: 42, y: 4, width: 128, height: 30)
        /*
         leftView:
         <UIImageView: 0x7fe891e03b30; frame = (5 5; 32 32); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x60000275f5c0>>
         */
        
        /*
         placeholder:
         <UITextFieldLabel: 0x7f9ecf438140; frame = (40 6; 120 24); text = 'the placeholder text'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003957070>>
         edit:
         <UIFieldEditor: 0x7f9ecf865600; frame = (42 4; 128 30); text = ' '; clipsToBounds = YES; opaque = NO; gestureRecognizers = <NSArray: 0x600001424de0>; layer = <CALayer: 0x600001a2e000>; contentOffset: {0, 0}; contentSize: {128, 30}; adjustedContentInset: {0, 0, 0, 0}> <_UITextFieldContentView: 0x7f9ecf701900; frame = (0 0; 128 30); opaque = NO; userInteractionEnabled = NO; layer = <__UITextTiledLayer: 0x600003e76ca0>> */ /* clearButton: <UIButton: 0x7fdf23d28170; frame = (180 10; 24 24); opaque = NO; layer = <CALayer: 0x60000043dfc0>> */ /* rightView: <UIImageView: 0x7f9a88f223a0; frame = (160 0; 35 35); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x600003138fa0>> */ /* <_UITextFieldContentView: 0x7fdbf8715ae0; frame = (50 0; 100, 101); opaque = NO; userInteractionEnabled = NO; layer = <__UITextTiledLayer: 0x600001fd5e00>> */Copy the code

_UITextFieldContentView (x); _UITextFieldContentView (width); _UITextFieldContentView (x); BorderRect isn’t really sure what it’s for… Have the classmate that knows can give instruction together 😀

conclusion

From the perspective of the position Settings for UIButton and UITextField child controls, Apple’s elegant design is basically to use insets to fine-tune the child view and override methods to make large adjustments to the position of the child controls. Use mode to control the display of subviews.