ios - 在 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
}
*/
}
解决方案
显然,有无数种方法可以用任何语言或框架制作计算器应用程序。为了帮助说明 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 文件之外使用。
推荐阅读
- javascript - 从服务器获得响应后,未附加 JSON
- python - 如何根据一周中不同日子的不同时间段修剪数据集?
- javascript - 如何使用 ES6 缩短此功能?
- javascript - 根据输入参数在 JavaScript 中动态生成函数
- python - 如何避免 [Errno 98] 地址已在使用中
- python - Python脚本随着时间的推移变慢
- javascript - 获取乒乓球游戏的键号时无法读取未定义的属性“keyCode”
- laravel - 不存在具有关系的急切加载
- python-3.x - Selenium.common.exceptions.WebDriverException:消息:未知错误:找不到 Chrome 二进制文件
- java - 尝试在空对象引用上调用虚拟方法'boolean java.util.ArrayList.add