首页 > 解决方案 > 在 Swift 中通过视图层次结构进行通信

问题描述

我为自己设定了一个挑战,即构建一个基本的计算器应用程序(具有与默认 Apple 计算器类似的功能),以便快速自学 ios 开发。我有一个可以工作的应用程序,但是键盘是直接在我的视图故事板中手动布置的,我认为为了使挑战更有趣(并且项目更清洁),我将通过子类化 UIView 将键盘构建为自定义组件。

我已经设法让某些东西正常工作,并且键盘渲染得很好。但是,我现在发现自己遇到了一个不知道如何解决的问题。当用户按下其中一个键盘按钮时,通常会更新 UI 中的输出。当按钮是我的故事板的直接成员时,这很容易,因为我可以将按钮连接到视图控制器中的操作。然而,现在,我可以将按钮操作连接到我的 UIView 子类,但是我如何将这些信息向上传递到视图层次结构,以便视图控制器可以在 UI 的其他地方设置适当的输出?

我知道 UIView 具有可以连接到 View Controller 代码的操作,但我找不到如何创建自定义操作(如果可能)以及如何调用它们。这可能吗?还有其他一些标准的方法吗?

这是我班级的代码:

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
    }
    */

}

标签: iosswift

解决方案


显然,有无数种方法可以用任何语言或框架制作计算器应用程序。为了帮助说明 Swift 和 UIKit 的功能,我整理了一些东西来向你展示你可以做什么,如果你选择的话。

您可以考虑的第一件事是对按钮本身进行子类化,以便它们可以携带更多类型安全的信息。

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

}

但是,为了使其工作,您需要声明一些枚举。

enum CalculatorButtonType {
    case integer, operation
}

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

因此,当您实例化您的键盘按钮时,您可以向它们注入相关且类型安全的信息。

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

        }

    }

}

这个键盘视图也有一个委托,它是来自它的视图控制器类型。您可以将此类型的委托设置为协议,视图控制器可以遵守该协议。但是如果只有一个视图控制器在使用这个自定义键盘,那么创建协议就没有意义了。

我们设置这个委托的原因是它可以在点击按钮时与视图控制器通信。

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)

    }

}

我以前从未制作过计算器应用程序,如果我这样做了,我不知道这是否就是我的做法。这是一个非常粗略的代码展示,需要更多的细微差别。但这应该有助于说明 Swift 和 UIKit 是如何工作的,以及你可以用它们做什么。

还要注意private访问控制修饰符的使用。养成使用它们的习惯。如果从未从对象外部访问方法或属性,请将其标记为private。您可以标记 enums fileprivate,例如,如果它们没有在 Swift 文件之外使用。


推荐阅读