Gucci
Gucci

Reputation: 149

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)
}
}

Upvotes: 1

Views: 223

Answers (1)

Gucci
Gucci

Reputation: 149


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! :)


Upvotes: 0

Related Questions