阅读 657

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

UIButton

1. 背景介绍

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

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

但是呢系统默认的按钮设计是左边图片右边文字,所以对此我们需要对此进行适配调整了.

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

效果图:

2.1 无法解决的正常方式

为什么说是正常方式呢,因为大部分情况下这对于调整其他视图的子视图来说是可以实现其位置的调整,然而UIButton却不行.这里我们对子视图进行微调,然后子视图位置大小却并不会进行变更.

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

内容对齐方式枚举

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

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

        // 内容展示始终为左图片右文字
        // 内容默认横纵居中显示
        // 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>>
         */
复制代码

效果图:

这里需要注意的是fill这个参数比较特别,不好猜测其具体实现.

        // 图片和文本高度撑满
        // 图片被拉伸,图片会变形
        // 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来控制子视图的展示.

关注下面的标签,发现更多相似文章
评论