Search code examples
swiftuikituitextfieldsubclass

Subclassing UITextField with other UI elements / Swift 5



Hello everyone!) Need some help!)

I have custom text field with input limit which was in my view controller. If you look below, you will see that my text field has: UIView (underlayer with some borders), two UILabels (name label and counter label), and UITextField inside of UIView. Now I want to make UITextField subclass and configure my text field there with whole UI-es. MARK: - I working without storyboards, the code only.

The question is, can I implement this in UITextField class?) Or maybe better to use UIView class?)

I experimented and tried to do it in TextField class, but stuck on UIView (underlayer), I can't make it behind my text field. I add a bit of code.)

Have you any ideas how to implement this in right way?)

Thanks for every answer!)

Example


enter image description here


Code...



UIViewController class


import UIKit

class ViewController: UIViewController {

var inputLimitTextField = InputLimitTextField(frame: CGRect(x: 45, y: 200, width: 300, height: 40))

override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(inputLimitTextField)
}
}

UITextField class


import UIKit

class InputLimitTextField: UITextField {

var underlayerView = UIView()

override init(frame: CGRect) {
    super.init(frame: frame)
    configureTextField()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    configureTextField()
}

func configureTextField() {
    backgroundColor = .purple
    
    underlayerView.backgroundColor = .red
    underlayerView.alpha = 0.5
    addSubview(underlayerView)
    

    underlayerView.translatesAutoresizingMaskIntoConstraints = false
   
    NSLayoutConstraint.activate([
        underlayerView.topAnchor.constraint(equalTo: self.bottomAnchor),
        underlayerView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
        underlayerView.centerXAnchor.constraint(equalTo: self.centerXAnchor)
    ])
}

override func layoutSubviews() {
    super.layoutSubviews()
    
    underlayerView.frame = self.bounds
    sendSubviewToBack(underlayerView)
}
}


Solution


  • Considering the fact that there is still no answer to my question that would solve this issue… Also, given that using subclasses is a pretty popular practice in programming... I didn't find a specific answer to such a question on the stack. That's why I decided to answer my own question. I hope my approach to solving the problem helps someone in the future...


    Code...


    UIViewController class...


    import UIKit
    
    class ViewController: UIViewController, UITextFieldDelegate {
    
    private lazy var inputLimitTextField = InputLimitTextField()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .white
        view.addSubview(inputLimitTextField)
        inputLimitTextFieldPosition()
    }
    
    private func inputLimitTextFieldPosition() {
        inputLimitTextField.center.x = self.view.center.x
        inputLimitTextField.center.y = self.view.center.y - 100
    }
    }
    

    UITextField class...


    import UIKit
    
    class InputLimitTextField: UITextField, UITextFieldDelegate {
    
    private lazy var nameLabel = UILabel()
    private lazy var counterLabel = UILabel()
    
    private let textLayer = CATextLayer()
    private let padding = UIEdgeInsets(top: 0.5, left: 10, bottom: 0.5, right: 17)
    
    private let purpleUIColor = UIColor(red: 0.2849253164, green: 0.1806431101, blue: 0.5, alpha: 1.0)
    private let purpleCGColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(),
                                              components: [0.2849253164, 0.1806431101, 0.5, 1.0])
    private let redUIColor = UIColor(red: 1, green: 0.1806431101, blue: 0.09760022642, alpha: 1)
    private let redCGColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(),
                                     components: [ 1, 0.1806431101, 0.09760022642, 1.0])
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        configureTextField()
        configureNameLabel()
        configureCunterLabel()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        configureTextField()
        configureNameLabel()
        configureCunterLabel()
    }
    
    private func configureTextField() {
        
        let screenRect = UIScreen.main.bounds
        let screenWidth = screenRect.size.width - 25
        let textFieldFrame = CGRect(x: 0, y: 0, width: screenWidth, height: 40)
        
        frame = textFieldFrame
        backgroundColor = .clear
        textColor = purpleUIColor
        font = UIFont(name: "Helvetica", size: 17)
        placeholder = "Input limit"
        textAlignment = .left
        contentVerticalAlignment = .center
        clearButtonMode = .always
        autocorrectionType = .no
        keyboardType = .default
        returnKeyType = .done
        delegate = self
        
        textLayer.backgroundColor = UIColor.white.cgColor
        textLayer.borderColor = purpleCGColor
        textLayer.borderWidth = 1.2
        textLayer.cornerRadius = 10
        textLayer.frame = layer.bounds
        layer.insertSublayer(textLayer, at: 0)
        
        layer.shadowColor = .init(gray: 0.5, alpha: 0.5)
        layer.shadowOpacity = 0.7
        layer.shadowOffset = .init(width: 2, height: 2)
        
        addSubview(nameLabel)
        nameLabel.frame = CGRect(x: 12, y: -12, width: 55, height: 16)
        addSubview(counterLabel)
        counterLabel.frame = CGRect(x: screenWidth - 34, y: 9, width: 22, height: 22)
    }
    
    override internal func textRect(forBounds bounds: CGRect) -> CGRect {
        let bounds = super.textRect(forBounds: bounds)
        return bounds.inset(by: padding)
    }
    
    override internal func editingRect(forBounds bounds: CGRect) -> CGRect {
        let bounds = super.editingRect(forBounds: bounds)
        return bounds.inset(by: padding)
    }
    
    override internal func clearButtonRect(forBounds bounds: CGRect) -> CGRect {
        let screenRect = UIScreen.main.bounds
        let screenWidth = screenRect.size.width - 25
        return CGRect(x: screenWidth - 70, y: 0, width: 40, height: 40)
    }
    
    private func enableUI() {
        self.textLayer.borderColor = redCGColor
        self.counterLabel.layer.borderColor = redCGColor
        self.counterLabel.textColor = redUIColor
        self.textColor = redUIColor
        self.nameLabel.layer.borderColor = redCGColor
        self.nameLabel.textColor = redUIColor
    }
    
    private func disableUI() {
        self.textLayer.borderColor = purpleCGColor
        self.counterLabel.layer.borderColor = purpleCGColor
        self.counterLabel.textColor = purpleUIColor
        self.textColor = purpleUIColor
        self.nameLabel.layer.borderColor = purpleCGColor
        self.nameLabel.textColor = purpleUIColor
    }
    
    func firstTenCharsColor(text: String) -> NSMutableAttributedString {
        let characterCount = 10
        let stringLength = text.utf16.count
        let attributedString = NSMutableAttributedString(string: text)
        if stringLength >= characterCount {
            attributedString.addAttribute(.foregroundColor, value: #colorLiteral( red: 0.2849253164, green: 0.1806431101, blue: 0.5, alpha: 1), range: NSMakeRange(0, characterCount) )
        }
        return attributedString
    }
    
    private func updateUI(inputText: String?) {
        
        guard let textCount = inputText?.count else { return }
        guard let text = self.text else { return }
        
        if (textCount <= 10){
            self.counterLabel.text = "\(10 - textCount)"
            disableUI()
        } else if (textCount >= 10) {
            self.counterLabel.text = "\(10 - textCount)"
            enableUI()
            self.attributedText = firstTenCharsColor(text: text)
        }
    }
    
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        
        guard let text = self.text, let textRange = Range(range, in: text) else { return true }
        let updatedText = text.replacingCharacters(in: textRange, with: string)
        
        self.updateUI(inputText: updatedText)
        
        return true
    }
    
    func textFieldShouldClear(_ textField: UITextField) -> Bool {
        self.textLayer.borderColor = purpleCGColor
        self.counterLabel.text = "10"
        disableUI()
        return true
    }
    
    private func configureNameLabel() {
        nameLabel.backgroundColor = .white
        nameLabel.layer.cornerRadius = 3
        nameLabel.layer.borderWidth = 1.2
        nameLabel.layer.borderColor = purpleCGColor
        nameLabel.layer.masksToBounds = true
        nameLabel.font = UIFont(name: "Helvetica", size: 11)
        nameLabel.text = "Input limit"
        nameLabel.textAlignment = .center
        nameLabel.textColor = purpleUIColor
    }
    
    private func configureCunterLabel() {
        counterLabel.backgroundColor = .white
        counterLabel.layer.cornerRadius = 5
        counterLabel.layer.borderWidth = 1.2
        counterLabel.layer.borderColor = purpleCGColor
        counterLabel.layer.masksToBounds = true
        counterLabel.font = UIFont(name: "Helvetica", size: 12)
        counterLabel.text = "10"
        counterLabel.textAlignment = .center
        counterLabel.textColor = purpleUIColor
    }
    }
    

    You can use it for any iPhone...


    enter link description here


    Stay safe and good luck! :)