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來控制子視圖的展現.

相關文章
相關標籤/搜索