iOS-UIButton、UITextField子控件Frame设置方法

UIButton

1. 背景介绍

在咱们在作按钮相关需求时,咱们常常会遇到一种状况就是:图片和文字的排版问题. 咱们在开发的时候常常会遇到的状况会有:bash

  • 上图片下文字
  • 左图片右文字
  • 右图片左文字
  • 下图片上文字(这个见得少哦.)

可是呢系统默认的按钮设计是左边图片右边文字,因此对此咱们须要对此进行适配调整了.ide

2. 解决方式

这里咱们对按钮进行简单的配置,而后获取到对应的子视图位置打下测试

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() {
        let offfsetY: CGFloat = 12.0
        let offsetX: CGFloat = 10.0
        // imgSize: (32, 32)
        // 36.0
        let textWidth: CGFloat = CGFloat(btn.titleLabel!.font.pointSize) * (CGFloat)(btnTitle.count)
        // 56.0
        let btnHeight: CGFloat = (CGFloat)(offfsetY * 2.0) + (CGFloat)(btnImg.size.height)
        // 88.0
        let btnWidth: 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 = '下载'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6000029a0550>>
         */
    }
复制代码

效果图:ui

2.1 没法解决的正常方式

为何说是正常方式呢,由于大部分状况下这对于调整其余视图的子视图来讲是能够实现其位置的调整,然而UIButton却不行.这里咱们对子视图进行微调,而后子视图位置大小却并不会进行变动.spa

override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        // 这样的设置无用
        btn.imageView?.frame = CGRect(x: 0, y: 12, width: 32, height: 32)
        btn.titleLabel?.frame = CGRect(x: 42, y: 17.5, width: 37, height: 21.5)
        /*
         <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 = '下载'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003bcc7d0>>
         */
    }
复制代码

2.2 经过对齐方式调整

系统提供contentVerticalAlignmentcontentHorizontalAlignment参数,咱们能够对内容设置对齐方式.设计

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
复制代码

内容对齐方式枚举3d

public enum ContentVerticalAlignment : Int {

        
        case center

        case top

        case bottom

        case fill
    }

    
    public enum ContentHorizontalAlignment : Int {

        
        case center

        case left

        case right

        case fill

        @available(iOS 11.0, *)
        case leading

        @available(iOS 11.0, *)
        case trailing
    }
复制代码

系统默认设置为内容均为居中显示,咱们这边设置内容居上居左对齐code

// 内容展现始终为左图片右文字
        // 内容默认横纵居中显示
        // UIControl: 设置内容对齐方式
        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 = '下载'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600001ba7160>>
         */
复制代码

效果图:orm

这里须要注意的是fill这个参数比较特别,很差猜想其具体实现.cdn

// 图片和文本高度撑满
        // 图片被拉伸,图片会变形
        // label.x为图片的宽
        // 水平对齐内容以填充内容矩形; 文本能够换行,图像能够拉伸。
        btn.contentHorizontalAlignment = .fill
        // 垂直对齐内容以填充内容矩形; 图像可能会被拉伸。
         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 = '下载'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6000013f6170>>
        */
复制代码

效果图:

这里咱们能够知道,这两个枚举方法这能是调整内容的对齐方式而没办法实现咱们的上述需求即图片和文本的位置变化. 因此咱们须要另外的解决方式.

2.3 经过设置边距调整

系统提供了contentEdgeInsetstitleEdgeInsetsimageEdgeInsets三个参数来让咱们对视图进行调整.

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
复制代码

这里有一篇文章写得很详细理解UIButton的imageEdgeInsets和titleEdgeInsets能够参考下,咱们这里就简单介绍下不展开讨论.

这里咱们简单的调整下imageEdgeInsetstitleEdgeInsets会发现子视图的位置更改了.

btn.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10)
        btn.titleEdgeInsets = UIEdgeInsets(top: 10, left: 0, bottom: 0, right: 0)
        /*
        // 新位置
         <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 = '下载'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6000025b2fd0>>
         
         // 原位置
         <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 = '下载'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600003bcc7d0>>
         */
复制代码

看到结果后咱们会发现,这里的数值变化不是那么直接,对于某些数值的微调来讲是一个很好的选择也能够实现咱们上述的需求,但会显得不那么直接,会稍微麻烦.下面咱们介绍另外一种方法来实现.

2.4 经过重写系统方法来调整

系统提供了backgroundRectcontentRecttitleRectimageRect来重写子视图位置大小.

// 设置背景图片位置大小
func backgroundRect(forBounds: CGRect) -> CGRect
Returns the rectangle in which the receiver draws its background.

// 设置内容位置大小
func contentRect(forBounds: CGRect) -> CGRect
Returns the rectangle in which the receiver draws its entire content.

// 设置文本内容大小
func titleRect(forContentRect: CGRect) -> CGRect
Returns the rectangle in which the receiver draws its title.

// 设置图片内容大小
func imageRect(forContentRect: CGRect) -> CGRect
Returns the rectangle in which the receiver draws its image.
复制代码

代码及效果图展现:

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 24); text = '下载'; 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>>
         */
复制代码

效果图:

这里须要注意的是:imageRecttitleRectcontentRect会有冲突,是不能一块儿设置的. imageRect,titleRect不设置的话,将被contentRect所限制 设置内容视图:

// imageRect,titleRect不设置的话,将被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 = (37 24.5; 18.5 21.5); text = '下载'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60000237aa30>>
         */
复制代码

效果图:

3.内容推荐

理解UIButton的imageEdgeInsets和titleEdgeInsets

iOS button的imageEdgeInsets和titleEdgeInsets原理

这两篇文章有对imageEdgeInsetstitleEdgeInsets进行详细的讲解,有兴趣的小伙伴能够前往查看阅读.

UITextField

UITextField呢跟UIButton对于控件的设置是有些相似的,这里咱们介绍下系统提供给咱们关于子控件的位置大小的重写方法.

// drawing and positioning overrides
    // boarder...没发现什么内容=。=
    open func borderRect(forBounds bounds: CGRect) -> CGRect
    
    // _UITextFieldContentView视图的影响
    open func textRect(forBounds bounds: CGRect) -> CGRect
    
    // UITextFieldLabel视图的影响
    open func placeholderRect(forBounds bounds: CGRect) -> CGRect

    // UIFieldEditor视图的影响
    open func editingRect(forBounds bounds: CGRect) -> CGRect

    // clearBtn视图的影响
    open func clearButtonRect(forBounds bounds: CGRect) -> CGRect
   
    // leftView视图的影响
    open func leftViewRect(forBounds bounds: CGRect) -> CGRect

    // rightView视图的影响
    open func rightViewRect(forBounds bounds: CGRect) -> CGRect
复制代码

默认视图位置

tf.borderStyle = .roundedRect
        tf.text = "测试文本测试文本"
        tf.placeholder = "placeholder文本"
        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 19); opaque = NO; layer = <CALayer: 0x600001accd00>>
         placeholder:
         <UITextFieldLabel: 0x7feff98061b0; frame = (39 4; 127 20.5); text = 'placeholder文本'; 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>>
         */
复制代码

效果图:

这里须要注意

  • rightViewclearButton是不能同时存在的.这里是分别去掉其中一项后跑了两次获得的结果.
  • 只有文本时视图有_UITextFieldContentView没有UITextFieldLabelUIFieldEditor控件
  • 当文本框正在编辑时有UIFieldEditor_UITextFieldContentView控件,而且_UITextFieldContentViewUIFieldEditor的子控件
  • 当文本有占位文本即没有文本时UITextFieldLabelUIFieldEditor_UITextFieldContentView会同时存在,而且UITextFieldLabel在他们中的最底层

自定义视图位置

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 = 'placeholder文本'; 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>>
         */
复制代码

须要注意的是_UITextFieldContentView只是会根据textRect设置的值来变化,并不会直接拿过来用,这里能够看到,只有xwidth是一致.textRect值的设定会有必定的考究,搞不清楚原理...还有borderRect实在不清楚有何用处... 有知道的同窗能够一块儿赐教下😀

总结

经过对于UIButtonUITextField的子控件位置设置来看,苹果的优雅设计大概的思路是经过insets来对子视图进行微调,经过重写方法来对子控件的位置进行大的调整.经过mode来控制子视图的展现.

相关文章
相关标签/搜索