【问题标题】:How to manage firstResponder with two UITextFields using UIDatePicker input如何使用 UIDatePicker 输入管理具有两个 UITextField 的 firstResponder
【发布时间】:2019-06-13 03:44:21
【问题描述】:

我有一个ViewController 和两个UITextFields,它们使用UIDatePicker(s) 进行输入。一个是“From”日期,另一个是“To”日期,所以这似乎是一个相当常见的场景。

问题在于,当一个字段的 DatePicker 处于活动状态时,用户可能会点击屏幕上的另一个字段。活动的 DatePicker 仍然与第一个字段相关联,因此 UI 让用户感到困惑,因为光标在另一个字段中闪烁,但输入并没有转到那里。

我认为正确的 UI 解决方案是防止用户点击任何其他字段,直到 DatePicker 被解除。但是我无法找到一种方法来检测 firstResponder 正在更改或暂时禁用其他字段的编辑。

这是我当前的代码没有做我需要做的事情:

class AddTripLocationVC: UIViewController, UITextFieldDelegate {

@IBOutlet private weak var tripLocFromDateInput: UITextField!
@IBOutlet private weak var tripLocToDateInput: UITextField!

override func viewDidLoad() {
    super.viewDidLoad()

    //set up datepickers for text field inputs
    createDatePicker(forDateField: tripLocFromDateInput)
    createDatePicker(forDateField: tripLocToDateInput)

}

//automatically update the label fields when the date vairables are updated by the UIDatePicker
var locFromDate: Date? {
    didSet {
        guard locFromDate != nil else { return }
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "dd MMM yyyy"
        tripLocFromDateInput.text = dateFormatter.string(from: locFromDate!)
    }
}

var locToDate: Date? {
    didSet {
        guard locToDate != nil else { return }
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "dd MMM yyyy"
        tripLocFromDateInput.text = dateFormatter.string(from: locToDate!)
        //Comment after solution found...The problem was the line above
        //It should be:
        tripLocToDateInput.text = dateFormatter.string(from: locToDate!)
    }
}

func createDatePicker(forDateField dateField: UITextField) {

    let datePickerView = UIDatePicker()
    datePickerView.datePickerMode = .date
    dateField.inputView = datePickerView
    datePickerView.addTarget(self, action: #selector(handleDatePicker(sender:)), for: .valueChanged)

    let doneButton = UIBarButtonItem.init(title: "Done", style: .done, target: self, action: #selector(self.datePickerDone))
    let toolBar = UIToolbar.init(frame: CGRect(x: 0, y: 0, width: view.bounds.size.width, height: 44))
    toolBar.setItems([UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil), doneButton],
                     animated: true)
    dateField.inputAccessoryView = toolBar
}

@objc
func handleDatePicker(sender: UIDatePicker) {

    if tripLocFromDateInput.isFirstResponder {
        locFromDate = sender.date
    } else {
        if tripLocToDateInput.isFirstResponder {
            locToDate = sender.date
        } else {
            print("Error: Can't find first responder field.")
        }
    }
}

@objc
func datePickerDone(dateField: UITextView) {

    if tripLocFromDateInput.isFirstResponder {
        tripLocFromDateInput.resignFirstResponder()
    } else {
        if tripLocToDateInput.isFirstResponder {
            tripLocToDateInput.resignFirstResponder()
        } else {
            print("Error: Can't find first responder field.")
        }
    }
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    self.view.endEditing(true)
}

func textFieldDidEndEditing(_ textField: UITextField) {
    textField.becomeFirstResponder()
}
}

对于 iOS 中这种类型的“从”和“到”日期字段,用户界面是否有“标准”方法?

【问题讨论】:

  • 为什么在textFieldDidEndEditing中设置textField.becomeFirstResponder()!!!
  • 在下面查看我与 ekscrypto 的交流。正如您预期的那样,这不起作用。问题是什么会起作用?
  • @ekscrypto 您的工作代码帮助我意识到我的问题实际上是由 handleDatePicker() 更新的两个日期变量。他们错误地更新了同一个字段,这使得 UI 行为错误(相应地在上面进行了评论)。我还看到,即使焦点发生变化,未关闭的 DatePicker 也会更新焦点所在的任何字段,因此即使用户点击另一个字段,用户界面实际上也不会令人困惑。
  • @Asad Ali Choudhry 请参阅 ekscrypto 已接受答案下方的评论。您还建议的方法无效,但他随后的工作代码有效。这使我意识到我的相关代码也在工作,问题实际上出在 DatePickers 更新的日期变量之一的 didSet{ } 中。我已经在上面显示了该代码(我最初发布时认为它不相关),并接受了 ekscrypto 的解决方案,因为他的代码也可以正常工作。

标签: swift uidatepicker


【解决方案1】:

您可以注册一个 UITextField 委托,其中包含您特别感兴趣的两种方法,特别是:

  • textFieldDidBeginEditing(:)
  • textFieldDidEndEditing(:)

如需了解更多信息,请访问:https://developer.apple.com/documentation/uikit/uitextfielddelegate

请注意,您还可以在任何 UITextField 上使用becomeFirstResponder() 来激活其中一个。无论哪个处于活动状态,都应该有闪烁的光标。


编辑:根据您更新的代码:

//
//  ViewController.swift
//  datepickers
//
//  Created by Dave Poirier on 2019-06-13.
//

import UIKit

class ViewController: UIViewController,  UITextFieldDelegate {

    @IBOutlet private weak var tripLocFromDateInput: UITextField!
    @IBOutlet private weak var tripLocToDateInput: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()

        //set up datepickers for text field inputs
        createDatePicker(forDateField: tripLocFromDateInput)
        createDatePicker(forDateField: tripLocToDateInput)

        //set text field delegates to be notified when focus changes
        tripLocFromDateInput.delegate = self
        tripLocToDateInput.delegate = self
    }


    func createDatePicker(forDateField dateField: UITextField) {

        let datePickerView = UIDatePicker()
        datePickerView.datePickerMode = .date
        dateField.inputView = datePickerView
        datePickerView.addTarget(self, action: #selector(handleDatePicker(sender:)), for: .valueChanged)

        let doneButton = UIBarButtonItem.init(title: "Done", style: .done, target: self, action: #selector(self.datePickerDone))
        let toolBar = UIToolbar.init(frame: CGRect(x: 0, y: 0, width: view.bounds.size.width, height: 44))
        toolBar.setItems([UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil), doneButton],
                         animated: true)
        dateField.inputAccessoryView = toolBar
    }

    @objc
    func handleDatePicker(sender: UIDatePicker) {

        if tripLocFromDateInput.isFirstResponder {
            let locFromDate = sender.date
            tripLocFromDateInput.text = "FROM: \(locFromDate)"
        } else {
            if tripLocToDateInput.isFirstResponder {
                let locToDate = sender.date
                tripLocToDateInput.text = "TO: \(locToDate)"
            } else {
                print("Error: Can't find first responder field.")
            }
        }
    }

    @objc
    func datePickerDone(dateField: UITextView) {

        if tripLocFromDateInput.isFirstResponder {
            tripLocFromDateInput.resignFirstResponder()
        } else {
            if tripLocToDateInput.isFirstResponder {
                tripLocToDateInput.resignFirstResponder()
            } else {
                print("Error: Can't find first responder field.")
            }
        }
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        self.view.endEditing(true)
    }
}

}

上面的代码在一个全新的项目中为我工作,创建了两个输入字段并连接了 IBOutlets

【讨论】:

  • 感谢您的快速回复。但是,我已经添加了我在上面尝试过的代码,如果 firstResponder 离开,它就会强制返回到当前字段,并且它不起作用。当点击 UIDatePicker 上的“完成”按钮时,会触发 textFieldDidEndEditing is 中的断点,这会强制正在编辑的字段再次成为 firstResponder,因此 UIDatePicker 拒绝被解除。所以我一定误解了你建议的解决方案......
  • 明确地说,我正在寻找一种方法来检测焦点(如果不是 firstResponder)离开当前字段 DatePicker 仍处于活动状态跨度>
  • @tkhelm 我用一些工作代码更新了我的答案。请注意,我添加了对 super.touchesBegan() 的调用,以便可以正确传播触摸事件,以防万一有一天这最终出现在 UIScrollView 中或您需要手势。
  • 感谢您花时间在这方面。您的工作代码帮助我意识到我的问题实际上是由 handleDatePicker() 更新的两个日期变量。他们错误地更新了同一个字段,导致 UI 行为错误。我还看到,即使焦点发生变化,未关闭的 DatePicker 也会更新焦点所在的任何字段,因此即使用户点击另一个字段,用户界面实际上也不会令人困惑。
  • 顺便说一句,对我来说, touchesBegan() 永远不会被调用/执行。我明白为什么我应该包含 super.touchesBegan() 但它似乎与解决方案无关。它似乎根本不需要包括在内。
猜你喜欢
  • 2012-06-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多