UITextField 커스텀 클래스

- 자간, 줄간격

- 언더라인 (컬러, 두깨)

- Padding 조절

- tintClearImage Button

import UIKit

let LEFT_IMAGE_WIDTH: CGFloat = 16.0
let LEFT_IMAGE_HEIGHT: CGFloat = 16.0
let LEFT_TEXT_PADDING: CGFloat = 15.0
let RIGHT_BUTTON_PADDING: CGFloat = 10.0  // |5-button-5|

enum ViewType {
    case left, right, textpadding
}


@IBDesignable
class IBTextField: UITextField {
    
    var tintedClearImage: UIImage? = nil
    private var underLineborder: CALayer? = nil
    //private var leftPadingView: UIView? = nil
    //private var leftImageView: UIImageView? = nil
    
    @IBInspectable public var _fontStyleNumber: Int = 2{
        didSet {
            configure()
        }
    }
    
    @IBInspectable public var _fontSize: CGFloat = 14.0{
        didSet {
            
            configure()
        }
    }
    
    @IBInspectable public var _hintfontSize: CGFloat = 10{
        didSet {
            configure()
        }
    }
    
    @IBInspectable public var _hintText: String = ""{
        didSet {
            configure()
        }
    }
    
    /// Hint 텍스트 컬러
    @IBInspectable public var _hintColor: UIColor = .white{
        didSet {
            configure()
        }
    }
    
    /// 글자간격
    @IBInspectable open var letterSpacing:CGFloat = 0.0 {
        didSet {
            configure()
        }
        
    }
    
    /// 줄간격 default : 21
    @IBInspectable open var linesspacing:CGFloat = -1.0 {
        didSet {
            configure()
        }
        
    }
    
    /// 우측 Close 디폴트 버튼 컬러
    @IBInspectable public var _closeTintColor: UIColor = .white{
        didSet {
            configure()
            
        }
    }
    
    /// 테두리 두께
    @IBInspectable public var _borderWidth: CGFloat = 0.0{
        didSet {
            self.layer.borderWidth = _borderWidth
            
        }
    }
    
    /// 테두리 컬러
    @IBInspectable public var _borderColor: UIColor = UIColor.white{
        didSet {
            self.layer.borderColor = self._borderColor.cgColor
            
        }
    }
 
    /// 하단 BorderLine 사용 유무
    @IBInspectable public var isUnderLine: Bool = false{
        didSet {
            makeUnderLine()
        }
    }
    
    /// 하단 BorderLine 컬러
    @IBInspectable public var underLineColor: UIColor = .red{
        didSet {
            makeUnderLine()
        }
    }
    
    /// 하단 BorderLine 두께
    @IBInspectable public var underLineHeight: CGFloat = 2.0 {
        didSet {
            makeUnderLine()
        }
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
   
    override func layoutSubviews() {
        super.layoutSubviews()
        configure()
        makeUnderLine()
        rightCloseImageTintColor()
    }
    
    func configure() {
        
        if _fontStyleNumber >= 0 || _fontStyleNumber <= 5{
            if let fontStyle = FontStyleNumber.init(rawValue: _fontStyleNumber) {
                
                // placeholder (Hint Text) Font
                let hintSytlefont = UIFont.notoSansFont(forFont: fontStyle.fontName, size: _hintfontSize)
                
                self.attributedPlaceholder = NSAttributedString(string: _hintText, attributes: [
                    .foregroundColor: _hintColor.withAlphaComponent(0.4),
                    .font: hintSytlefont
                ])
                
                // Lable Text Font
                self.font = UIFont.notoSansFont(forFont: fontStyle.fontName, size: _fontSize)
            }
        }
        
        makeUnderLine()
        setNeedsDisplay()
        invalidateIntrinsicContentSize()
    }
    
    func makeUnderLine(){
        if self.isUnderLine == false { return }
        
        let frame = self.frame
        
        if underLineborder == nil {
            underLineborder = CALayer()
            self.layer.addSublayer(underLineborder!)
        }
        
        underLineborder!.frame = CGRect(x: 0, y: frame.size.height-underLineHeight, width: frame.width, height: underLineHeight)
        underLineborder!.backgroundColor = underLineColor.cgColor
        
        setNeedsDisplay()
        invalidateIntrinsicContentSize()
    }
    
    // 디폴트 우측 x버튼 컬러 변경 (기본적으로 투명도가 적용되있음)
    private func rightCloseImageTintColor() {
        for view in subviews {
            if view is UIButton {
                let button = view as! UIButton
                if #available(iOS 13.0, *) {
                    // 컬러 뚜렷하게 표시 됨.
                    if let image = button.image(for: .normal)?.withTintColor(self._closeTintColor, renderingMode: .alwaysOriginal) {
                        button.setImage(image, for: .normal)
                        button.setImage(image, for: .highlighted)
                    }
                } else {
                    // Fallback on earlier versions
                    // 컬러 투명적용되서 색이 뚜렷하게 표시 안됨.
                    if let image = button.image(for: .normal) {
                        tintedClearImage = self.tintImage(image: image, color: UIColor.white)
                        button.setImage(self.tintedClearImage, for: .normal)
                        button.setImage(self.tintedClearImage, for: .highlighted)
                    }
                }
            }
        }
    }
    
    private func tintImage(image: UIImage, color: UIColor) -> UIImage {
        let size = image.size
        
        UIGraphicsBeginImageContextWithOptions(size, false, image.scale)
        let context = UIGraphicsGetCurrentContext()
        image.draw(at: .zero, blendMode: CGBlendMode.normal, alpha: 1.0)
        
        context?.setFillColor(color.cgColor)
        context?.setBlendMode(CGBlendMode.sourceIn)
        context?.setAlpha(1.0)
        
        let rect = CGRect(x: CGPoint.zero.x, y: CGPoint.zero.y, width: image.size.width, height: image.size.height)
        UIGraphicsGetCurrentContext()?.fill(rect)
        let tintedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return tintedImage ?? UIImage()
    }
    
    // 왼쪽 이미지 아이콘 추가
    func setMakeLeftImageView(image named: String) {
        
        if let leftImage = UIImage(named: named) {
            
            let leftImageView  = UIImageView(frame:CGRect(x: 0.0, y: 0.0, width: LEFT_IMAGE_WIDTH, height: LEFT_IMAGE_HEIGHT))
            
            leftImageView.contentMode = .center
            leftImageView.image = leftImage
            setView(.left, with: leftImageView)
        }
        
        setNeedsDisplay()
        invalidateIntrinsicContentSize()
    }
    
    // 오른쪽 커스텀 Close 버튼 추가
    func setMakeRightButtonView(rightButton: UIButton) {
        
        
        let rightPadingView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: rightButton.frame.width, height: self.frame.height))
        
        rightPadingView.tag = 1000
        rightButton.tag = 2000
        rightButton.center = CGPoint(
            x: rightPadingView.bounds.size.width / 2,
            y: rightPadingView.bounds.size.height / 2
        )
        rightPadingView.addSubview(rightButton)
        
        setView(.right, with: rightPadingView)
        
        setNeedsDisplay()
        invalidateIntrinsicContentSize()
    }
    
    func setView(_ type: ViewType, with view: UIView) {
        if type == ViewType.left || type == ViewType.textpadding {
            leftView = view
            leftViewMode = .always
        } else if type == .right {
            rightView = view
            rightViewMode = .always
        }
    }
    
    /* 왼쪽 아이콘 사용 안 할 경우 Text가 붙어서 입력되기 때문에 Padding 적용
       - Text 입력시 왼쪽 Padding 적용
     */
    func setMakeTextPadding(leftPadding: CGFloat = LEFT_TEXT_PADDING) {
        let leftPadingView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: leftPadding, height: self.frame.height))
        leftPadingView.contentMode = .center
        
        setView(.textpadding, with: leftPadingView)
        setNeedsDisplay()
        invalidateIntrinsicContentSize()
    }
    
    // right 버튼 숨김/노출 설정
    func rightButtonVisible(bHidden: Bool) {
        guard let rightView = getView(viewTag: 1000) as? UIView else {
            return
        }
        
        guard let rightButton = rightView.getView(viewTag: 2000) as? UIButton else {
            return
        }
        
        rightButton.isHidden = bHidden
    }
    
    
    // 글자간격 및 줄간격 조정
    func updateWithSpacing() {
        
        let attributedString = self.attributedText == nil ? NSMutableAttributedString(string: self.text ?? "") : NSMutableAttributedString(attributedString: attributedText!)
        
        // 자간 간격 설정
        attributedString.addAttribute(NSAttributedString.Key.kern, value: self.letterSpacing, range: NSRange(location: 0, length: attributedString.length))
        
        // 줄간격 (default -  21)
        if linesspacing > 0 {
            let paragraphStyle = NSMutableParagraphStyle()
            paragraphStyle.minimumLineHeight = linesspacing
            paragraphStyle.maximumLineHeight = linesspacing
            paragraphStyle.alignment = self.textAlignment
            
            attributedString.addAttribute(NSAttributedString.Key.font, value: self.font!, range: NSRange(location: 0, length: attributedString.length))
            
            attributedString.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: attributedString.length))
        }
        
        self.attributedText = attributedString
    }
}

 

[사용방법]

IBDesignable 클래스라서 스토리보드상에서 설정하고 사용하는게 편하고 사용방법은 일반적인 UITextField 와 동일하다.

class ChatViewController: UIViewController {
	@IBOutlet weak var chattingInputTextField: ITextField! {
    	didSet {
        	//우측 아이콘 표시
            rightImageTextField.borderStyle = .none
            rightImageTextField.keyboardType = .numberPad
            rightImageTextField.returnKeyType = .next
            rightImageTextField.setMakeLeftImageView(image: "phone_reg")
            
            
            /*좌측 버튼 표시
              사용 안할 경우 디폴트 x버튼 표시됨.*/
            
            //let rightButton = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 30))
            let rightButton = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
            
            // 1. 이미지 버튼
            rightButton.contentMode = .center
            rightButton.setImage(UIImage(named: "close"), for: .normal)
            
            // 2. 텍스트 버튼
            //rightButton.titleLabel?.textColor = UIColor.gray
            //rightButton.titleLabel?.font = UIFont.notoSansFont(forFont: .NotoSansCJKkrRegular, size: 9)
            //rightButton.setTitle("재전송", for: .normal)
            
            rightImageTextField.setMakeRightButtonView(rightButton: rightButton)
        
            //right button Click 이벤트 연결
            rightButton.addTarget(self, action: #selector(buttonPressed(_:)), for: .touchUpInside)
            
            //TextField 입력 이벤트 연결
            rightImageTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        chattingInputTextField.delegate = self
        chattingInputTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
        
        setBecomeFirstResponder()
    }
    
    /// 채팅 입력창 키보드 바로 표시
    func setBecomeFirstResponder() {
        // 채팅입력창 포커스 활성화
        self.chattingInputTextField.becomeFirstResponder()
    }
}

extension ChatViewController : UITextFieldDelegate {
    
    @objc func textFieldDidChange(_ textField: UITextField) {
    }
    
    /// 키보드 리턴키 델리게이트 처리
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        print("textFieldShouldReturn: \((textField.text) ?? "Empty")")
        // 최종 입력된 정보가 있으면 전송 후 텍스트 필드 초기화
        sendChattingMessage(message: textField.text)
        
        return true
    }
    
    func textFieldShouldClear(_ textField: UITextField) -> Bool {
        print("textFieldShouldClear: \((textField.text) ?? "Empty")")
        return true
    }
    
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        
        print("shouldChangeCharactersIn: \((textField.text) ?? "Empty")")
        
        let currentText = textField.text ?? ""
        guard let stringRange = Range(range, in: currentText) else { return false }
        
        let updatedText = currentText.replacingCharacters(in: stringRange, with: string)
        self.chattingSendButton.isEnabled = updatedText.count >= 1 ? true:false
        
        return true
    }
}

 

+ Recent posts