我想这是一个低的头脑,我挣扎,但不幸的是,我所有的搜索在这个论坛和其他来源没有给予我一个胶水呢。
我正在为iOS创建一个购物清单应用程序。在用于输入购物清单位置的Viewcontroller中,我只显示相关的输入字段,这取决于要放在购物清单上的商品种类。
因此,我用不同的原型单元设置了一个tableView,其中一些单元包含UITextFields来处理这种动态设置。
我已经定义了一个键盘工具栏,包含一个按钮在右边隐藏键盘(这工作)和两个按钮(“下一个”和“返回”)在左边跳转到下一个相应的前一个输入字段,这应该成为第一响应者,光标设置在这个字段并显示键盘。
不幸的是,这种firstResponder的切换不起作用,光标没有设置到下一个/上一个输入字段,有时甚至键盘消失了。
跳回根本不起作用,键盘总是在下一个活动字段是不同原型单元的一部分时消失(例如,从“品牌”字段向前移动到“数量”字段)。
有人能解决这个问题吗?
对于处理,我定义了两个通知:
let keyBoardBarBackNotification = Notification.Name("keyBoardBarBackNotification")
let keyBoardBarNextNotification = Notification.Name("keyBoardBarNextNotification")
工具栏的定义是在UIViewController的扩展中完成的:
func setupKeyboardBar() -> UIToolbar {
let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 50))
let leftButton = UIBarButtonItem(image: UIImage(systemName: "chevron.left"), style: .plain, target: self, action: #selector(leftButtonTapped))
leftButton.tintColor = UIColor.systemBlue
let nextButton = UIBarButtonItem(image: UIImage(systemName: "chevron.right"), style: .plain, target: self, action: #selector(nextButtonTapped))
nextButton.tintColor = UIColor.systemBlue
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let fixSpace = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(image: UIImage(systemName: "keyboard.chevron.compact.down"), style: .plain, target: self, action: #selector(doneButtonTapped))
doneButton.tintColor = UIColor.darkGray
toolbar.setItems([fixSpace, leftButton, fixSpace, nextButton, flexSpace, doneButton], animated: true)
toolbar.sizeToFit()
return toolbar
}
@objc func leftButtonTapped() {
view.endEditing(true)
NotificationCenter.default.post(Notification(name: keyBoardBarBackNotification))
}
@objc func nextButtonTapped() {
view.endEditing(true)
NotificationCenter.default.post(Notification(name: keyBoardBarNextNotification))
}
@objc func doneButtonTapped() {
view.endEditing(true)
}
}
在viewController中,我设置了键盘处理例程和一个例程“switchActiveField”来确定下一个应该成为firstResponder的实际字段:
class AddPositionVC: UIViewController {
@IBOutlet weak var menue: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.menue.delegate = self
self.menue.dataSource = self
self.menue.separatorStyle = .none
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardDidShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleBackButtonPressed), name: keyBoardBarBackNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleNextButtonPressed), name: keyBoardBarNextNotification, object: nil)
}
enum TableCellType: String {
case product = "Product:"
case brand = "Brand:"
case quantity = "Quantity:"
case price = "Price:"
case shop = "Shop:"
// ...
}
var actualField = TableCellType.product // field that becomes firstResponder
// Arrray, defining the fields to be diplayed
var menueList: Array<TableCellType> = [.product, .brand, .quantity, .shop
]
// Array with IndexPath of displayed fields
var tableViewIndex = Dictionary<TableCellType, IndexPath>()
@objc func handleKeyboardDidShow(notification: NSNotification) {
guard let endframeKeyboard = notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey]
as? CGRect else { return }
let insets = UIEdgeInsets( top: 0, left: 0, bottom: endframeKeyboard.size.height - 60, right: 0 )
self.menue.contentInset = insets
self.menue.scrollIndicatorInsets = insets
self.scrollToMenuezeile(self.actualField)
self.view.layoutIfNeeded()
}
@objc func handleKeyboardWillHide() {
self.menue.contentInset = .zero
self.view.layoutIfNeeded()
}
@objc func handleBackButtonPressed() {
switchActiveField(self.actualField, back: true)
}
@objc func handleNextButtonPressed() {
switchActiveField(self.actualField, back: false)
}
// Definition, which field should become next firstResponder
func switchActiveField(_ art: TableCellType, back bck: Bool) {
switch art {
case .brand:
self.actualField = bck ? .product : .quantity
case .quantity:
self.actualField = bck ? .brand : .shop
case .price:
self.actualField = bck ? .quantity : .shop
case .product:
self.actualField = bck ? .shop : .brand
case .shop:
self.actualField = bck ? .price : .product
// ....
}
if let index = self.tableViewIndex[self.actualField] {
self.menue.reloadRows(at: [index], with: .automatic)
}
}
}
tableView的扩展名为:
extension AddPositionVC: UITableViewDelegate, UITableViewDataSource {
func scrollToMenuezeile(_ art: TableCellType) {
if let index = self.tableViewIndex[art] {
self.menue.scrollToRow(at: index, at: .bottom, animated: false)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return menueList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let tableCellType = self.menueList[indexPath.row]
self.tableViewIndex[tableCellType] = indexPath
switch tableCellType {
case .product, .brand, .shop:
let cell = tableView.dequeueReusableCell(withIdentifier: "LabelTextFieldCell", for: indexPath) as! LabelTextFieldCell
cell.item.text = tableCellType.rawValue
cell.itemInput.inputAccessoryView = self.setupKeyboardBar()
cell.itemInput.text = "" // respective Input
if self.actualField == tableCellType {
cell.itemInput.becomeFirstResponder()
}
return cell
case .quantity, .price:
let cell = tableView.dequeueReusableCell(withIdentifier: "QuantityPriceCell", for: indexPath) as! QuantityPriceCell
cell.quantity.inputAccessoryView = self.setupKeyboardBar()
cell.quantity.text = "" // respective Input
cell.price.inputAccessoryView = self.setupKeyboardBar()
cell.price.text = "" // respective Input
if self.actualField == .price {
cell.price.becomeFirstResponder()
} else if self.actualField == .quantity {
cell.quantity.becomeFirstResponder()
}
return cell
}
}
}
//*********************************************
// MARK: - tableViewCells
//*********************************************
class LabelTextFieldCell: UITableViewCell, UITextFieldDelegate {
override func awakeFromNib() {
super.awakeFromNib()
itemInput.delegate = self
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
self.itemInput.resignFirstResponder()
}
@IBOutlet weak var item: UILabel!
@IBOutlet weak var itemInput: UITextField!
}
class QuantityPriceCell: UITableViewCell, UITextFieldDelegate {
override func awakeFromNib() {
super.awakeFromNib()
self.quantity.delegate = self
self.price.delegate = self
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
textField.resignFirstResponder()
}
@IBOutlet weak var quantity: UITextField!
@IBOutlet weak var price: UITextField!
}
谢谢你的支持。
1条答案
按热度按时间wqlqzqxt1#
有各种各样的方法可以实现这一点......事实上,很容易找到具有许多特性的开源第三方库--只需搜索(Google或任何地方)
swift ios form builder
。但是,如果你想自己做,基本的想法是:
var activeField: UITextField?
textFieldDidBeginEditing
上的每个字段:如果你所有的字段都在屏幕上,那就很简单了。
如果它们不能垂直放置(特别是当键盘显示时),如果它们都在滚动视图中,同样,非常直接。
将它们放入tableView的单元格中会变得很复杂,原因有以下几个:
要添加重复的相似但不同的“行”,我们不需要使用表格视图。
例如,如果我们有一个
UIStackView
和.axis = .vertical
:我们现在添加了10个单标签“细胞”。
因此,对于您的任务,我们可以编写以下函数,而不是在
LabelTextFieldCell
中使用表视图:以及类似的(但稍微复杂一些):
然后类似于
cellForRowAt
使用它:如果我们将stackView添加到scrollView,我们就有了一个可滚动的“Form”。
下面是一个完整的示例,您可以尝试(没有
@IBOutlet
或@IBAction
连接......只需要将一个空白视图控制器的类设置为FormVC
):我们将把“行视图”构建器函数放在扩展中,只是为了保持代码的独立性和可读性:
运行时,它看起来像这样:
如果您添加更多的“行”-或者更简单地增加堆栈视图间距,如
stackView.spacing = 100
-您将看到当键盘显示时,它如何继续使用scrollView。当然,你在评论中提到:"...更多输入字段(例如,使用日期选择器的日期等)",因此您需要编写新的“行构建器”函数,并向Next tap添加一些逻辑,以转到/来自选择器,而不是文本字段。
但是,你可能会发现这是一个有帮助的起点。