Je me suis lancé le défi de créer une application de calculatrice de base (avec des fonctionnalités similaires à la calculatrice Apple par défaut) afin de m'initier au développement ios avec swift. J'ai une application qui fonctionne mais le clavier est disposé manuellement directement dans mon storyboard de vue, et j'ai pensé que pour rendre le défi plus intéressant (et le projet plus propre), je construirais le clavier en tant que composant personnalisé en sous-classant UIView.

J'ai réussi à faire fonctionner quelque chose et le clavier rend bien. Cependant, je me retrouve maintenant avec un problème que je ne sais pas comment résoudre. Lorsque l'utilisateur appuie sur l'un des boutons du clavier, cela met généralement à jour la sortie dans l'interface utilisateur. Lorsque les boutons étaient des membres directs de mon Storyboard, c'était facile car je pouvais connecter les boutons à des actions dans le View Controller. Maintenant, cependant, je peux connecter les actions des boutons à ma sous-classe d'UIView, mais comment puis-je transmettre ces informations vers le haut de la hiérarchie View afin que le contrôleur de vue puisse ensuite définir les sorties appropriées ailleurs dans l'interface utilisateur ?

Je sais que les UIViews ont des actions que je peux connecter au code View Controller, mais je ne trouve pas comment créer des actions personnalisées, si possible, puis comment les invoquer. Est-ce possible? Existe-t-il une autre façon standard de procéder ?

Voici le code de ma classe :

import UIKit

@IBDesignable
class KeypadView: UIView {
    typealias keyParams = (title: String, leading: String, top: String, color: UIColor)

    let keys: [keyParams] = [(title: "1", leading: "-1", top: "-1", color: .gray),
                             (title: "2", leading: "1", top: "-1", color: .gray),
                             (title: "3", leading: "2", top: "-1", color: .gray),
                             (title: "+", leading: "-2", top: "-1", color: .systemBlue),
                             (title: "4", leading: "-1", top: "1", color: .gray),
                             (title: "5", leading: "4", top: "2", color: .gray),
                             (title: "6", leading: "5", top: "3", color: .gray),
                             (title: "-", leading: "-2", top: "+", color: .systemBlue),
                             (title: "7", leading: "-1", top: "4", color: .gray),
                             (title: "8", leading: "7", top: "5", color: .gray),
                             (title: "9", leading: "8", top: "6", color: .gray),
                             (title: "×", leading: "-2", top: "-", color: .systemBlue),
                             (title: "C", leading: "-1", top: "7", color: .systemRed),
                             (title: "0", leading: "C", top: "8", color: .gray),
                             (title: "=", leading: "0", top: "9", color: .systemGreen),
                             (title: "÷", leading: "-2", top: "×", color: .systemBlue)]

    var buttonDict: [String: UIButton] = [String: UIButton]()

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

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

    func setupButtons() {

        for key in keys {
            let button = UIButton()
            button.setTitle(key.title, for: .normal)
            button.backgroundColor = key.color
            button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)

            buttonDict[key.title] = button

            addSubview(button)

            button.translatesAutoresizingMaskIntoConstraints = false
            button.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.2).isActive = true
            button.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.2).isActive = true

            if(key.leading == "-1") {
                button.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
            } else if (key.leading == "-2") {
                button.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
            } else {
                if let leadingButton = buttonDict[key.leading] {
                    button.leadingAnchor.constraint(equalTo: leadingButton.trailingAnchor, constant: 15).isActive = true
                }
            }

            if(key.top == "-1") {
                button.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
            } else {
                if let topButton = buttonDict[key.top] {
                    button.topAnchor.constraint(equalTo: topButton.bottomAnchor, constant: 15).isActive = true
                }
            }

            //button.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
            //button.widthAnchor.constraint(equalToConstant: 44.0).isActive = true
        }
    }

    @objc func buttonPressed(_ sender: UIButton) {
        print("\(sender.titleLabel?.text) was pressed")
    }

    /*
    // Only override draw() if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    override func draw(_ rect: CGRect) {
        // Drawing code
    }
    */

}
0
Luke 2 févr. 2020 à 01:03

1 réponse

Meilleure réponse

Il existe d'innombrables façons de créer une application de calculatrice, dans n'importe quel langage ou framework, évidemment. Pour illustrer les capacités de Swift et d'UIKit, j'ai rassemblé quelques éléments pour vous montrer ce que vous pouviez faire, si vous le souhaitez.

La première chose que vous pouvez envisager est de sous-classer les boutons eux-mêmes afin qu'ils puissent contenir des informations plus sûres.

class CalculatorButton: UIButton {

    var type: CalculatorButtonType?
    var integer: Int?
    var operation: CalculatorOperation?

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

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

}

Pour que cela fonctionne, cependant, vous devez déclarer certaines énumérations.

enum CalculatorButtonType {
    case integer, operation
}

enum CalculatorOperation {
    case add, subtract, divide, multiply
}

Ainsi, lorsque vous instanciez les boutons de votre clavier, vous pouvez leur injecter des informations pertinentes et sécurisées.

class KeypadView: UIView {

    weak var delegate: CalculatorViewController?

    private func addButtons() {

        let plusButton = CalculatorButton()
        plusButton.type = .operation
        plusButton.operation = .add
        plusButton.addTarget(self, action: #selector(buttonPressed(_:)), for: .touchUpInside)

        let oneButton = CalculatorButton()
        oneButton.type = .integer
        oneButton.integer = 1
        oneButton.addTarget(self, action: #selector(buttonPressed(_:)), for: .touchUpInside)

    }

    @objc private func buttonPressed(_ sender: CalculatorButton) {

        switch sender.type {

        case .operation:
            delegate?.didTapOperation(sender.operation)

        case .integer:
            delegate?.didTapInteger(sender.integer)

        default:
            break

        }

    }

}

Une chose que cette vue du clavier a également est un délégué, du type du contrôleur de vue dont il provient. Vous pouvez faire de ce délégué de type un protocole auquel le contrôleur de vue peut se conformer. Mais si un seul contrôleur de vue utilise ce clavier personnalisé, inutile de créer un protocole.

Et la raison pour laquelle nous avons configuré ce délégué est qu'il puisse communiquer avec le contrôleur de vue lorsque les boutons sont appuyés.

class CalculatorViewController: UIViewController {

    private let keypad = KeypadView()

    func addKeypad() {
        keypad.delegate = self // this establishes the delegate relationship
    }

    func didTapInteger(_ n: Int?) {

        guard let n = n else {
            return
        }
        print(n)

    }

    func didTapOperation(_ o: CalculatorOperation?) {

        guard let o = o else {
            return
        }
        print(o)

    }

}

Je n'ai jamais créé d'application de calculatrice auparavant et si je le faisais, je ne savais même pas si c'était comme ça que je m'y prendrais. Et il s'agit d'un affichage de code très grossier qui nécessiterait plus de nuances. Mais cela devrait aider à illustrer le fonctionnement de Swift et UIKit et les choses que vous pouvez faire avec eux.

Notez également l'utilisation des modificateurs de contrôle d'accès private. Prenez l'habitude de les utiliser. Si une méthode ou une propriété n'est jamais accessible depuis l'extérieur de l'objet, marquez-la private. Vous pouvez marquer les énumérations fileprivate, par exemple, si elles ne sont pas utilisées en dehors du fichier Swift.

1
bxod 2 févr. 2020 à 02:53