Search code examples
iosswiftswift3class-extensions

Extensions with stored properties


I'm still learning Swift, and I'm trying to create an extension to add a placeholder to UITextView.

My idea for it is to create 2 UITextViews, one with a text as a placeholder, and when the user starts editing the text, it's actually hidden and the user is modifying the other UITextView.

However, my question here is not for having a placeholder in UITextView, but it's in regards of how-to use extensions to solve this problem.
My question is, what do I have to change in my implementation to create an extension that would look from the caller side as:
myTextView.placeholder("a placeholder text..")

So far, I have created it in my UIViewController, and need to move it to an extension, but I have a lot of stored properties, so it wouldn't work.

Here's my code:

import UIKit

class ViewController: UIViewController, UITextViewDelegate {


    let myTextView: UITextView = {
        let textView = UITextView()
        textView.tag = 0
        textView.backgroundColor = UIColor.yellow
        textView.layer.cornerRadius = 8
        textView.translatesAutoresizingMaskIntoConstraints = false
        return textView
    }()

    let textViewPlaceHolder: UITextView = {
        let textViewPlaceHolder = UITextView()
        textViewPlaceHolder.tag = 1
        textViewPlaceHolder.text = "Placeholder text.."
        textViewPlaceHolder.textColor = UIColor.lightGray
        textViewPlaceHolder.backgroundColor = UIColor.clear
        textViewPlaceHolder.translatesAutoresizingMaskIntoConstraints = false
        return textViewPlaceHolder
    }()


    func textViewDidChange(_ textView: UITextView) {
        myTextView.becomeFirstResponder()
        if textView.tag == 1 && (myTextView.text != nil || myTextView.text != "") {
            textView.isHidden = true
            textViewPlaceHolder.resignFirstResponder()
        } else if textView.tag == 0 {
            if myTextView.text == nil || myTextView.text == "" {
                textViewPlaceHolder.becomeFirstResponder()
                myTextView.resignFirstResponder()
                textViewPlaceHolder.isHidden = false
                textViewPlaceHolder.text = "Placeholder text.."

            }
        }
    }

    func textViewDidBeginEditing(_ textView: UITextView) {
        DispatchQueue.main.async {
            self.textViewPlaceHolder.selectedRange = NSMakeRange(0, 0)
        }
    }


    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.lightGray
        view.addSubview(myTextView)
        textViewConstraints()
        view.addSubview(textViewPlaceHolder)
        myTextViewPHConstraints()

        myTextView.delegate = self
        textViewPlaceHolder.delegate = self

    }

    func textViewConstraints() {
        myTextView.widthAnchor.constraint(equalTo: self.view.widthAnchor, constant: -16).isActive = true
        myTextView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
        myTextView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
        myTextView.heightAnchor.constraint(equalToConstant: 200).isActive = true
    }

    func myTextViewPHConstraints() {
        textViewPlaceHolder.widthAnchor.constraint(equalTo: self.view.widthAnchor, constant: -16).isActive = true
        textViewPlaceHolder.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
        textViewPlaceHolder.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
        textViewPlaceHolder.heightAnchor.constraint(equalToConstant: 200).isActive = true
    }

}

As I mentioned above, I'm still learning Swift and this question is not for solving a very specific problem, it's more than that, it's meant to learn how to solve problems in Swift extensions.

Note: Don't use the code above to solve placeholder problem as it's no perfectly working.


Solution

  • You can do something like this:

    extension UITextView {
    
        private struct AssociatedKeys {
            static var placeholder = "placeholder"
        }
    
        var placeholder: String! {
            get {
                guard let placeholder = objc_getAssociatedObject(self, &AssociatedKeys.placeholder) as? String else {
                    return String()
                }
    
                return placeholder
            }
    
            set(value) {
                objc_setAssociatedObject(self, &AssociatedKeys.placeholder, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    }