Comment observer une variable d'une autre classe

Bonjour,

je ne trouve pas la solution donc je viens vers vous ;-).

J’ai un textField qui, quand je clique dessus, fait ouvrir un pickerView dans le inputView et place les données dans le textField.
Mais, comme je vais devoir utiliser ce code plusieurs fois, j’aimerais ne pas le répéter et le placer dans une class à part.
J’ai donc créer une première classe TempoTextField qui est une extension de TextField et une deuxième class TempoPickerView qui est une extension de PickerView et qui a une variable « tempo » qui change bien a chaque fois que je tourne le pickerView.
Mais dans la class TempoTextField, je ne sais pas comment observer la variable tempo. Un didSet ne fonctionne pas entre 2 class ? Je n’y arrive pas en tout cas. Et j’aimerais que le text du TextField change automatiquement quand la variable « tempo » change.

Je ne sais pas si je suis très clair…
la première class :

class TempoTextField: UITextField, UITextFieldDelegate {

var tempoPickerView = TempoPickerView()

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

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

private func setup() {
    inputView = tempoPickerView
    delegate = self
    self.text = String(tempoPickerView.tempo)
}

}

et la deuxième :

class TempoPickerView: UIPickerView {
private let pickerViewRows = 1000
private let pickerViewMiddle = 500

private let customHeight: CGFloat = 30

var tempo: Int = 0

var tempoArray = [0,0,0] {
    didSet {
        tempo = (tempoArray[0] * 100) + (tempoArray[1] * 10) + tempoArray[2]
    }
}

override init(frame: CGRect) {
    super.init(frame: frame)
    delegate = self
    dataSource = self
    setup()
}
required init?(coder: NSCoder) {
    super.init(coder: coder)
    delegate = self
    dataSource = self
    setup()
}

func setup() {
    self.selectRow(pickerViewMiddle, inComponent: 0, animated: false)
    self.selectRow(pickerViewMiddle, inComponent: 1, animated: false)
    self.selectRow(pickerViewMiddle, inComponent: 2, animated: false)
}
}
extension TempoPickerView: UIPickerViewDataSource {

func numberOfComponents(in pickerView: UIPickerView) -> Int {
    return tempoArray.count
}

func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
    return pickerViewRows
}
}
extension TempoPickerView: UIPickerViewDelegate {
func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
    return customHeight
}

func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
    tempoArray[component] = row % 10
}

func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
    return String(row % 10)
}
}

merci pour les réponses.

C’est bien compliqué ton code. Plus grand monde ne code directement les objets graphiques. Storyboard ou SwiftUI sont nettement plus efficaces pour créer des interfaces adaptatives que s’embêter à gérer par code la frame et la position des objets sur l’écran.

Si tu débutes en programmation en utilisant un vieux livre, des tutos anciens ou de vieilles vidéo, je recommande de tout balancer et de reprendre sur une base moderne.

Enfin bref, comment résoudre ton problème ? A ta place, je ne chercherais pas à faire communiquer des classes entre-elles. Pour des tas de raison, notamment l’utilisation du paradigme MVC (Modele/Vue/Controlleur). Pourquoi ne pas créer un composant graphique personnalisé, intégrant un TextField et un PickerView dans une même classe ?

Ca peut parfois être intéressant de créer des sous-classes de composants que l’on veut réutiliser dans le projet.
Par contre, autant je comprends pourquoi tu as redéfini ton champ texte, autant le picker n’a pas besoin de sous-classes mais uniquement d’un delegate pour le remplir et récupérer ses valeurs. Tu pourrais l’intégrer dans ta classes de champ texte, et faire en sorte que ton champ texte soit l’interlocuteur du picker.

Voici un exemple que j’avais fait pour un client : un champ texte avec un DatePicker :

protocol DateTextFieldHandler:class {
    func dateTextField(_ dateTextField:DateTextField, changedDate newDate:Date)
}

class DateTextField: UITextField, UITextFieldDelegate {
    weak var handler:DateTextFieldHandler?
    
    let datePicker = UIDatePicker()
    let dateFormatter = DateFormatter.localized
    
    var date: Date {
        get {
            return datePicker.date
        }
        set {
            datePicker.date = newValue
            datePickerValueChanged(datePicker)
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame:frame)
        setup()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    
    private func setup() {
        datePicker.datePickerMode = .dateAndTime
        datePicker.addTarget(self, action: #selector(datePickerValueChanged), for: .valueChanged)
        inputView = datePicker
        delegate = self
        
        dateFormatter.dateStyle = .full
        dateFormatter.timeStyle = .short
    }
    
    @objc func datePickerValueChanged(_ sender:UIDatePicker) {
        handler?.dateTextField(self, changedDate: sender.date)
        self.text = dateFormatter.string(from: sender.date)
    }
    
    override func leftViewRect(forBounds bounds: CGRect) -> CGRect {
        let rect = super.leftViewRect(forBounds: bounds)
        
        return rect.offsetBy(dx: 5, dy: 0)
    }
    
    func setPrefixText(_ text:String?) {
        guard let text = text, text.count > 0 else {
            self.leftView = nil
            self.leftViewMode = .never
            return
        }
        let label = UILabel()
        label.text = text
        label.textColor = UIColor.lightGray
        label.sizeToFit()
        self.leftView = label
        self.leftViewMode = .always
    }
}

Merci Draken & Maxime,

J’ai tout mis dans le même fichier, qui en fin de compte n’est pas très gros. Ça règle mon problème et fonctionne très bien maintenant.
Ceci dit, Maxime, J’ai fait le test avec ton code. Il fonctionne très bien sur Xcode 11 mais pas avec Xcode 12…

Par contre si quelqu’un connait la méthode pour faire boucler un pickerView, je suis preneur. Car dans le code ci dessous c’est un peu de la bidouille.

Voici le code :

class TempoTextField: UITextField, UITextFieldDelegate {
    
    var pickerView = UIPickerView()
    private let pickerViewMiddle = 500
    var tempoArray = [0,0,0] {
        didSet {
            self.text = String((tempoArray[0] * 100) + (tempoArray[1] * 10) + tempoArray[2])
        }
    }
    private let pickerViewRows = 1000
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }
    
    private func setup() {
        pickerView.delegate = self
        pickerView.dataSource = self
        pickerView.selectRow(pickerViewMiddle, inComponent: 0, animated: false)
        pickerView.selectRow(pickerViewMiddle, inComponent: 1, animated: false)
        pickerView.selectRow(pickerViewMiddle, inComponent: 2, animated: false)
        inputView = pickerView
        delegate = self
    }
}

extension TempoTextField: UIPickerViewDataSource {
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return tempoArray.count
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return pickerViewRows
    }
}

extension TempoTextField: UIPickerViewDelegate {
    
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        tempoArray[component] = row % 10
    }
    
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return String(row % 10)
    }
}