我正在设置一个非常简单的iPhone应用程序,它有多个文本字段,使用日期选择器控件输入日期和时间字符串。
问题是,虽然一切都运行得很好,但我发现我正在为多个字段复制大量代码,这感觉就像是不好的做法。
我想要的是一种将这段代码压缩成一个单独的类的方法,只需在我需要的时候从任何地方调用它。
下面是我在视图控制器中运行良好的示例,但是对于多个字段必须多次重复:
@IBOutlet weak var dateField: UITextField!
@objc func dateFieldModified(sender:UIDatePicker) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yyyy"
dateField.text = dateFormatter.string(from: sender.date)
}
@IBAction func dateFieldModify(_ sender: UITextField) {
let datePicker = UIDatePicker()
datePicker.datePickerMode = UIDatePicker.Mode.date
datePicker.addTarget(self, action: #selector(self.dateFieldModified(sender:)), for: UIControl.Event.valueChanged)
dateField.inputView = datePicker
}
总而言之,当用户输入文本字段“dateField”时,弹出日期选择器并允许用户滚动并选择日期。当用户选择日期时,文本字段将设置为反映所选日期。
我希望能够在我的视图控制器中执行以下操作:
@IBOutlet weak var dateField: UITextField!
@IBAction func dateFieldModify(_ sender: UITextField) {
let datePicker = DatePicker(field:dateField, mode:"date", format: "MM/dd/yyyy")
datePicker.loadDatePicker()
}
并且有一个单独定义的类可能看起来像这样:
class DatePicker {
var field:UITextField
var mode:String
var format:String
init(field:UITextField, mode:String, format:String) {
self.field = field
self.mode = mode
self.format = format
}
@objc func dateFieldModified(sender:UIDatePicker) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
field.text = dateFormatter.string(from: sender.date)
}
func loadDatePicker() {
let datePicker = UIDatePicker()
datePicker.datePickerMode = UIDatePicker.Mode.date
datePicker.addTarget(self, action: #selector(self.dateFieldModified(sender:)), for: UIControl.Event.valueChanged)
field.inputView = datePicker
}
}
实现我上面写的新的DatePicker类几乎一切正常:当我点击“dateField”文本字段时,日期选择器打开,我可以滚动并选择一个日期。但是,选择日期后... dateField保持为空。
我一般都是Swift和OOP的新手,所以我确信我有一些代码或一个重要的概念。
是的,您可以(并且可能应该)将此逻辑封装在单独的类中。但是就像OOPer所说,你可能想为此定义一个UITextField
子类。
我还会提出一些其他的建议:
date
属性,这样你就可以设置和检索与该文本字段相关的Date
。就像UIDatePicker
一样,视图控制器应该通过date
属性与这个新控件进行交互,而不必处理日期格式化程序本身。dateFormat
与DateFormatter
,而是dateStyle
和timeStyle
。您始终希望用户界面显示本地化的日期字符串。因此,我可能会建议:
/// Date text field delegate protocol
@objc protocol DateTextFieldDelegate {
@objc optional
func dateTextField(_ dateTextField: DateTextField, didChangeDate: Date?)
@objc optional
func didTapDone(dateTextField: DateTextField)
}
/// Date text field
///
/// Used for entry of dates in UITextField, replacing keyboard with date picker.
class DateTextField: UITextField {
/// `DateTextField` delegate
///
/// You don't need to supply a delegate, but if you do, this will tell you as
/// the user changes the date.
weak var dateTextFieldDelegate: DateTextFieldDelegate?
/// Default date
///
/// If `nil`, uses today's date
var defaultDate: Date?
/// Date formatter for date
///
/// Feel free to change `dateStyle` and `timeStyle` to suit the needs of your app.
let formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
return formatter
}()
/// Date
///
/// The user's selected date.
var date: Date? {
didSet {
dateTextFieldDelegate?.dateTextField?(self, didChangeDate: date)
if !isManuallyEditing {
text = date.map { formatter.string(from: $0) }
}
datePicker.date = date ?? defaultDate ?? Date()
}
}
var dateTextFieldButtonType: DateTextFieldButtonType = .done {
didSet { doneButton?.title = dateTextFieldButtonType.buttonText }
}
/// The date picker.
lazy var datePicker: UIDatePicker = {
let picker = UIDatePicker()
picker.datePickerMode = .date
return picker
}()
// MARK: - Private properties
/// Private reference for "Done" button
private var doneButton: UIBarButtonItem!
/// Private flag is the user is manually changing the date.
private var isManuallyEditing = false
// MARK: - Initialization
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
}
// MARK: - Private utility methods
private extension DateTextField {
func configure() {
inputView = datePicker
datePicker.addTarget(self, action: #selector(datePickerModified(_:)), for: .valueChanged)
let toolBar = UIToolbar()
toolBar.barStyle = .default
toolBar.isTranslucent = true
let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
doneButton = UIBarButtonItem(title: dateTextFieldButtonType.buttonText, style: .done, target: self, action: #selector(didTapDone(_:)))
clearButtonMode = .whileEditing
toolBar.setItems([space, doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
toolBar.sizeToFit()
inputAccessoryView = toolBar
addTarget(self, action: #selector(textFieldModified(_:)), for: .editingChanged)
}
}
// MARK: - Actions
extension DateTextField {
@objc func didTapDone(_ sender: Any) {
if dateTextFieldButtonType == .select {
date = datePicker.date
}
resignFirstResponder()
dateTextFieldDelegate?.didTapDone?(dateTextField: self)
}
@objc func textFieldModified(_ textField: UITextField) {
isManuallyEditing = true
date = text.flatMap { formatter.date(from: $0) }
isManuallyEditing = false
}
@objc func datePickerModified(_ datePicker: UIDatePicker) {
date = datePicker.date
}
}
// MARK: - Enumerations
extension DateTextField {
enum DateTextFieldButtonType {
case select
case done
case next
}
}
extension DateTextField.DateTextFieldButtonType {
var buttonText: String {
switch self {
case .select: return NSLocalizedString("Select", comment: "DateTextFieldButtonType")
case .done: return NSLocalizedString("Done", comment: "DateTextFieldButtonType")
case .next: return NSLocalizedString("Next", comment: "DateTextFieldButtonType")
}
}
}
然后你可以在IB中指定DateTextField
的基类而不是UITextField
,你就完成了。
或者,如果要实现委托协议,可以执行以下操作:
class ViewController: UIViewController {
@IBOutlet weak var textField: DateTextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.dateTextFieldDelegate = self
}
}
extension ViewController: DateTextFieldDelegate {
func didTapDone(dateTextField: DateTextField) {
print("keyboard dismissed")
}
func dateTextField(_ dateTextField: DateTextField, didChangeDate date: Date?) {
print(date ?? "No date specified")
}
}
或者如果你想拥有日期和时间:
class ViewController: UIViewController {
@IBOutlet weak var textField: DateTextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.datePicker.datePickerMode = .dateAndTime
textField.formatter.dateStyle = .full
textField.formatter.timeStyle = .medium
}
}
显然,您可以根据需要修改此行为,但它说明了这一想法。
但是你应该从视图控制器中抽出带有日期选择器的文本字段并将其放在自己的类中。并根据您认为最适合您应用的行为来定制此子类。
管理两个单独类的实例(UITextField
和你的DatePicker
)有点烦人,并且可能很容易引起参考周期。
如何定义自己的TextField类型?
像这样的东西:
class DatePickerTextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
setUp()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUp()
}
private func setUp() {
self.addTarget(self, action: #selector(dateFieldModify(_:)), for: .editingDidBegin)
}
@objc func dateFieldModified(_ sender: UIDatePicker) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yyyy"
self.text = dateFormatter.string(from: sender.date)
}
@IBAction func dateFieldModify(_ sender: UITextField) {
let datePicker = UIDatePicker()
datePicker.datePickerMode = .date
datePicker.addTarget(self, action: #selector(self.dateFieldModified), for: .valueChanged)
self.inputView = datePicker
}
}
我想你可以自己改进一下。