The app I'm coding is an event-based app. On the screen where you would create a new event I have a UITextField with a UIDatePicker set as its input view initialized as:
lazy var eventDateTxt: UITextField = {
let tf = UITextField()
tf.attributedPlaceholder = NSAttributedString(string: "Pick Your Event Date",
attributes: [NSAttributedString.Key.foregroundColor: UIColor.zipVeryLightGray])
tf.font = .zipBody
tf.borderStyle = .roundedRect
tf.tintColor = .white
tf.backgroundColor = .zipGray
tf.textColor = .white
tf.adjustsFontSizeToFitWidth = true
tf.minimumFontSize = 10.0;
let datePicker = UIDatePicker()
datePicker.datePickerMode = .dateAndTime
datePicker.minimumDate = Date()
tf.inputView = datePicker
datePicker.addTarget(self, action: #selector(dateChanged), for: .valueChanged)
return tf
}()
dateChanged looks like this
#objc func dateChanged(sender: UIDatePicker){
let dateFormat = DateFormatter()
dateFormat.dateStyle = .long
dateFormat.timeStyle = .short
eventDateTxt.text = dateFormat.string(from: sender.date)
event.startTime = sender.date
}
and although it doesn't matter, here is my code for the UITextFieldDelegate
extension NewEventViewController: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == titleText {
if textField.text == "Event Title" {
textField.text = ""
}
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
if textField == titleText {
if textField.text == "" {
textField.text = "Event Title"
}
}
}
}
Now the problem: When I click the UITextField to open the DatePicker, it pops up as a small pop up view on the bottom of the screen and looks like this:
https://ibb.co/WBJ9GsN
note the very bottom of the image.
Now if you click the date at the bottom there it opens the DatePicker as expected and looks like this:
https://ibb.co/N7VvfX6
when doing so an error comes up in the consol
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600002d79ea0 'UIView-bottom-readableContentGuide-constraint' UILayoutGuide:0x60000379d340'UIViewLayoutMarginsGuide'.bottom == UILayoutGuide:0x60000379d260'UIViewReadableContentGuide'.bottom (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
2021-08-11 12:52:37.262846-0400 zip_official[93261:10730049] [DatePicker] UIDatePicker 0x7faa2732fe80 is being laid out below its minimum width of 280. This may not look like expected, especially with larger than normal font sizes.
Using the suggested breakpoint didn't lead me anywhere.
So my question: How do I make it so the DatePicker pops up immediately without the little bottom tab.
This date picker thing is a lot of pain in the back. It changed twice for last two iOS versions. I had the same issue earlier and what I did was:
Make the date picker stick to the bottom (with some constant to place it a little above safe area). In your case, I would give it a frame.
let datePicker = UIDatePicker(frame: CGRect(x: eventDateTxt.frame.minX, y: eventDateTxt.frame.minY, width: view.frame.width, height: 150))
Some pseudo code (for iOS 14 and higher I guess):
let datePicker = UIDatePicker()
datePicker.preferredDatePickerStyle = .wheels
If your iOS version < 14, then maybe do it with frames.
For better UI output
if #available(iOS 14.0, *) {
datePicker.preferredDatePickerStyle = .inline
} else if #available(iOS 13.4, *) {
datePicker.preferredDatePickerStyle = .wheels
}
Related
I have a page where there is a UITextField that I add programmatically, I set the text type to number and add constraints and all that. Then I add a done button to the accessoryView of the text field and add a function to run when that done button is pressed. My problem is, when the page loads, I want the text field to be focused and the keyboard shown. I set the becomeFirstResponder on it, but when the page loads, the keyboard shows up for a split second then immediately disappears and the delegate methods are run.
I need to find a way to make the text field "active", "focused", whatever you want to call it when the page loads, and for the keyboard to be there and ready. I can't seem to find any help aside from call becomeFirstResponder on it, which only works for a split second.
Here is the code I am using to build the page and run everything, I simplified it to reduce clutter and read times, but if you need more info, please let me know and I will be happy to provide the full code...
class AgeViewController: UIViewController {
var selectedAge: Int = 0
var textInput: UITextField!
let settings = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
createPage()
}
override func viewWillAppear(_ animated: Bool) {
textInput.becomeFirstResponder()
//I have tried this in both viewWillAppear and viewDidAppear
}
func createPage() {
textInput = UITextField()
textInput.font = .systemFont(ofSize: 50)
textInput.placeholder = "35"
textInput.borderStyle = .none
textInput.keyboardType = .numberPad
textInput.returnKey = .done
textInput.textAlignment = .right
addDoneButton()
textInput.delegate = self
view.addSubView(textInput)
//create a label and add it to the page
}
private fun addDoneButton() {
let doneToolbar: UIToolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50)
doneToolbar.barStyle = .default
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(doneTapped))
let items = [flexSpace, done]
doneToolbar.sizeToFit()
textInput.inputAccessoryView = doneToolbar
}
#objc func doneTapped() {
textInput.resignFirstResponder()
}
}
extension AgeViewController: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
textField.textColor = UIColor(named: "text")!
}
func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
if textField.text != nil {
selectedAge = Int(textField.text!) ?? 35
settings.set(selectedAge, forKey: Strings.age)
} else {
textField.textColor = UIColor(named: "grayText")!
}
}
Like I said, the page loads, the keyboard shows up for a split second, then goes away and the delegate methods are called for didEndEditing. I don't understand why it isn't staying focused, I am calling becomeFirstResponder. I have tried calling textInput.becomeFirstResponder() in 3 different places, all with the same result. The first was right after I add the subview to the view, then I tried in viewDidAppear and finally in viewWillAppear, all have the same result, shows up for a split second, then goes away. Sorry for the long post, thank you for any help, I really appreciate it.
My question is: When the UITextField is empty, how do I click the "Backspace" button to go to the previous UITextField? I have been struggling trying to do this in my code below?
Second Question: How do I only allow 1 character to get entered in the UITextField?
I am new at Swift code and trying to learn. Any help would be great.
What I am trying to do is have the user be able to type in a code in the 6 UITextFields and be able to click the "Backspace" button on any one of the UITextFields with only allowing the user to enter one number in each UITextField.
Code Below:
#objc func textFieldDidChange(textfield: UITextField) {
let text = textfield.text!
if text.utf16.count == 0 {
switch textfield {
case textField2:
textField1.becomeFirstResponder()
textField1.backgroundColor = UIColor.blue
textField1.tintColor = .clear
case textField3:
textField2.becomeFirstResponder()
textField2.backgroundColor = UIColor.blue
textField2.tintColor = .clear
case textField4:
textField3.becomeFirstResponder()
textField3.backgroundColor = UIColor.blue
textField3.tintColor = .clear
case textField5:
textField4.becomeFirstResponder()
textField4.backgroundColor = UIColor.blue
textField4.tintColor = .clear
case textField6:
textField5.becomeFirstResponder()
textField5.backgroundColor = UIColor.blue
textField5.tintColor = .clear
textField6.resignFirstResponder()
textField6.backgroundColor = UIColor.blue
textField6.tintColor = .clear
default:
break
}
}
else if text.utf16.count == 1 {
switch textfield {
case textField1:
textField1.backgroundColor = UIColor.black
textField1.textColor = .white
textField1.tintColor = .clear
textField2.becomeFirstResponder()
textField2.backgroundColor = UIColor.black
textField2.textColor = .white
textField2.tintColor = .clear
case textField2:
textField3.becomeFirstResponder()
textField3.backgroundColor = UIColor.black
textField3.textColor = .white
textField3.tintColor = .clear
case textField3:
textField4.becomeFirstResponder()
textField4.backgroundColor = UIColor.black
textField4.textColor = .white
textField4.tintColor = .clear
case textField4:
textField5.becomeFirstResponder()
textField5.backgroundColor = UIColor.black
textField5.textColor = .white
textField5.tintColor = .clear
case textField5:
textField6.becomeFirstResponder()
textField6.backgroundColor = UIColor.black
textField6.textColor = .white
textField6.tintColor = .clear
case textField6:
textField6.resignFirstResponder()
default:
break
}
}
}
I'd just like to point out that I'm still relatively new to iOS and Swift in general, but even with just a few minutes of searching, I was able to find some seeds of ideas which provided me with the suggested solution.
Based on your (improved) question, I believe a different approach is required. What you really don't want to use a text component. "Why"?
I here you ask. Because they don't actually provide you with the functionality that you want and come with a considerable overhead.
For this, what you really want is more control. You want to know when a key is pressed and you want to respond to it (I know, sounds like a text component, but) and be notified when more extended functionality occurs, like the delete key is pressed.
After a few minutes of research, some trial and error, I found that the UIKeyInput is more along the lines of what you want.
It will tell you when text is inserted and, more importantly, will tell you when Delete is pressed
The added benefit is, you can filter the input directly. You can take the first character from the String and ignore the rest or auto fill the following elements with the remaining text. You can perform validation (for numerical only content) and what ever else you might want to do
So, I started a really new project, added a UILabel to the UIViewController in the storyboard, bound it to the source and implemented the UIKeyInput protocol as such...
class ViewController: UIViewController {
override var canBecomeFirstResponder: Bool {
return true
}
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
becomeFirstResponder()
}
}
extension ViewController: UIKeyInput {
var hasText: Bool {
return true
}
func insertText(_ text: String) {
print(text)
label.text = text
}
func deleteBackward() {
print("Delete backward")
}
}
I ran the project and when a key was typed, the label was updated with the new key and when delete was pressed, the Delete backward text was printed to console.
Now. You have some choices to make. To use a single UIViewController and (maybe) a series of UILabels and manage interactions within it, so when a key is typed, you present the next label as the input focus (and when delete is pressed, you move back) or do you create a series of UIControls which represent each digit and manage via some delegate call back process.
You may also need to implement the UITextInputTraits protocol, which will allow you to control the keyboard presented
You might also like to have a read through Responding to Keyboard Events on iOS, CustomTextInputView.swift and Showing the iOS keyboard without a text input which were just some of the resources I used to hobble this basic example together with.
you can use this extension for your second question:
import UIKit
private var maxLengths = [UITextField: Int]()
extension UITextField {
#IBInspectable var maxLength: Int {
get {
guard let length = maxLengths[self] else {
return Int.max
}
return length
}
set {
maxLengths[self] = newValue
addTarget(
self,
action: #selector(limitLength),
for: UIControlEvents.editingChanged
)
}
}
#objc func limitLength(textField: UITextField) {
guard let prospectiveText = textField.text,
prospectiveText.count > maxLength
else {
return
}
let selection = selectedTextRange
let maxCharIndex = prospectiveText.index(prospectiveText.startIndex, offsetBy: maxLength)
text = prospectiveText.substring(to: maxCharIndex)
selectedTextRange = selection
}
}
when you add this extension to your project you can see an extra attribute in "Attribute Inspector" tab and you can set the max length of UITextField.
Fairly new to iOS development so forgive me for asking something that might be quite obvious. As you all know the UITextField's keyboard with keyboardType set to .NumberPad looks like the following...
.NumberPad keyboard
What I would like to do is replace the empty space in the lower left corner with a minus sign. Is this possible or does one need to write an entire custom keyboard to achieve this?
Would really appreciate the help.
Add a toolbar to your textfield inputAccessoryView and when the textfield will become the responder then the keyboard will show the toolbar (Swift 3.0):
func addToolBar(){
let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: 44))
let minusButton = UIBarButtonItem(title: "-", style: .plain, target: self, action: #selector(toggleMinus))
toolbar.items = [minusButton]
theTextField.inputAccessoryView = toolbar
}
func toggleMinus(){
// Get text from text field
if var text = theTextField.text , text.isEmpty == false{
// Toggle
if text.hasPrefix("-") {
text = text.replacingOccurrences(of: "-", with: "")
}
else
{
text = "-\(text)"
}
// Set text in text field
theTextField.text = text
}
}
hope it helps.
Swift 5.2
Set up the UIToolbar as described above and then use an extension on UITextField:
import UIKit
extension UITextField {
func toggleMinus() {
guard let text = self.text, !text.isEmpty else { return }
self.text = String(text.hasPrefix("-") ? text.dropFirst() : "-\(text)")
}
}
Usage:
#objc func toggleMinus() {
yourTextField.toggleMinus()
}
Fairly new to iOS development so forgive me for asking something that might be quite obvious. As you all know the UITextField's keyboard with keyboardType set to .NumberPad looks like the following...
.NumberPad keyboard
What I would like to do is replace the empty space in the lower left corner with a minus sign. Is this possible or does one need to write an entire custom keyboard to achieve this?
Would really appreciate the help.
Add a toolbar to your textfield inputAccessoryView and when the textfield will become the responder then the keyboard will show the toolbar (Swift 3.0):
func addToolBar(){
let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: 44))
let minusButton = UIBarButtonItem(title: "-", style: .plain, target: self, action: #selector(toggleMinus))
toolbar.items = [minusButton]
theTextField.inputAccessoryView = toolbar
}
func toggleMinus(){
// Get text from text field
if var text = theTextField.text , text.isEmpty == false{
// Toggle
if text.hasPrefix("-") {
text = text.replacingOccurrences(of: "-", with: "")
}
else
{
text = "-\(text)"
}
// Set text in text field
theTextField.text = text
}
}
hope it helps.
Swift 5.2
Set up the UIToolbar as described above and then use an extension on UITextField:
import UIKit
extension UITextField {
func toggleMinus() {
guard let text = self.text, !text.isEmpty else { return }
self.text = String(text.hasPrefix("-") ? text.dropFirst() : "-\(text)")
}
}
Usage:
#objc func toggleMinus() {
yourTextField.toggleMinus()
}
I've been having trouble trying to set the UIDatePicker font and color. Everything else in my app was fairly straightforward to adjust except this. Does anybody know how to do this? I'm using Swift for iOS8.
Changing the date mode to something else seems to force a re-draw with the newly set text color.
datePicker.setValue(UIColor.whiteColor(), forKeyPath: "textColor")
datePicker.datePickerMode = .CountDownTimer
datePicker.datePickerMode = .DateAndTime //or whatever your original mode was
you just need to set 2 lines of code in viewdidLoad / viewWillAppear accoding where you using DatePicker.
dobDatePicker.setValue(UIColor.whiteColor(), forKeyPath: "textColor")
dobDatePicker.setValue(false, forKey: "highlightsToday")
See the Result like this:
The only way for changing the font of UIDatePickerView (until now) is swizzling:
you can change the font by an extension of UILabel! (this is not recommended but it works!)
import Foundation
import UIKit
public extension UILabel {
#objc func setFontSwizzled(font: UIFont) {
if self.shouldOverride() {
self.setFontSwizzled(font: <THE UIFont FOR ALL DATEPICKERS!>)
} else {
self.setFontSwizzled(font: font)
}
}
private func shouldOverride() -> Bool {
let classes = ["UIDatePicker", "UIDatePickerWeekMonthDayView", "UIDatePickerContentView"]
var view = self.superview
while view != nil {
let className = NSStringFromClass(type(of: view!))
if classes.contains(className) {
return true
}
view = view!.superview
}
return false
}
private static let swizzledSetFontImplementation: Void = {
let instance: UILabel = UILabel()
let aClass: AnyClass! = object_getClass(instance)
let originalMethod = class_getInstanceMethod(aClass, #selector(setter: font))
let swizzledMethod = class_getInstanceMethod(aClass, #selector(setFontSwizzled))
if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod {
// switch implementation..
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}()
static func swizzleSetFont() {
_ = self.swizzledSetFontImplementation
}
}
and for changing the color you just simply call the function below:
datePicker.setValue(UIColor.whiteColor(), forKeyPath: "textColor")
if it's necessary to be re-rendered you need to call:
datePicker.datePickerMode = .CountDownTimer
datePicker.datePickerMode = .DateAndTime //or whatever your original mode was
you can use
datePicker.setValue(UIColor.whiteColor(), forKey: "textColor")
datePicker.setValue(false, forKey: "highlightsToday")
//for selector color
datePickerView.subviews[0].subviews[1].backgroundColor = UIColor.whiteColor()
datePickerView.subviews[0].subviews[2].backgroundColor = UIColor.whiteColor()
I believe this is the definitive solution for countdown timers.
It's an expansion of yildirimosman's answer.
//text color
datePicker.setValue(UIColor.whiteColor(), forKey: "textColor")
//picker background
datePicker.subviews[0].subviews[0].backgroundColor = UIColor.clearColor() //the picker's own background view
//dividers
datePicker.subviews[0].subviews[1].backgroundColor = UIColor.whiteColor()
datePicker.subviews[0].subviews[2].backgroundColor = UIColor.whiteColor()
//labels: "hours" and "min"
datePicker.subviews[0].subviews[3].setValue(UIColor.lightGrayColor(), forKey: "textColor")
datePicker.subviews[0].subviews[4].setValue(UIColor.lightGrayColor(), forKey: "textColor")
//refresh the tableview (to force initial row textColor to change to white)
datePicker.subviews[0].setNeedsLayout()
datePicker.subviews[0].layoutIfNeeded()
try this:
/* set color for UIDatePicker font */
//text color of today string
self.datePicker.performSelector("setHighlightsToday:", withObject:Constants.Colors.mainHeaderColor)
//text color for hoglighted color
self.datePicker.performSelector("_setHighlightColor:", withObject:Constants.Colors.mainHeaderColor)
//other text color
self.datePicker.setValue(Constants.Colors.mainHeaderColor, forKey: "textColor")
Adding textColor and highlightsToday to user defined variables did the trick for me :
Swift 4
override func layoutSubviews() {
super.layoutSubviews()
datePickerView.setValue(UIColor.white, forKeyPath: "textColor")
}
The API does not provide a way to do this. You can make a pretty convincing replica yourself using a UIPickerView rather than using UIDatePicker. Se here
You can set value using forKeyPath: "textColor". The code:
datePicker.setValue(UIColor.whiteColor(), forKeyPath: "textColor")
where datePicker is your UIDatePicker object, and the first parameter is the color that you want
I wanted to do this but set it for when someone has the date set prior to now, or after now. I had to reload the data, but when I did it ended up setting it to the current DateTime when using the above example.
So what I did was set a temporary value and then set it after the reload. It does make it do an animated effect, but it works. If you know a better way, let me know...
func dateChanged(sender: UIDatePicker) {
print(sender.date.description)
let tempDate = sender.date
let currentDate = NSDate()
if originalDate.isLessThanDate(currentDate) {
originalDate = sender.date
if sender.date.isGreaterThanDate(currentDate) {
sender.setValue(UIColor.blackColor(), forKeyPath: "textColor")
sender.datePickerMode = .CountDownTimer
sender.datePickerMode = .DateAndTime
sender.date = tempDate
sender.reloadInputViews()
}
}
if sender.date.isLessThanDate(currentDate) {
sender.setValue(UIColor.redColor(), forKeyPath: "textColor")
sender.datePickerMode = .CountDownTimer
sender.datePickerMode = .DateAndTime
sender.date = tempDate
sender.reloadInputViews()
}
}
You can use extensions to get and set textColor like bellow
extension UIDatePicker {
var textColor: UIColor? {
set {
setValue(newValue, forKeyPath: "textColor")
}
get {
return value(forKeyPath: "textColor") as? UIColor
}
}
}
And then set the color:
datePicker.textColor = .red
I saw the issue you were having and was having a similar issue. Using Xcode 6.3.1 I used this code in mine and worked great:
myPicker.backgroundColor = UIColor.whiteColor()
In case this helps.
This worked for me:
setting text color for all date picker subviews
for view in datePicker.subviews {
view.setValue(UIColor.white, forKeyPath: "textColor")
}
I ran into a similar issue with the latest SwiftUI / Swift 5 on XCode 11. All of the options above did not work and the DatePicker either stayed black text or crashed.
In your SwiftUI file set init() before var body
init() {
UIDatePicker.appearance().backgroundColor = .clear
}
Then in your var body view do this
DatePicker(selection: $dob, in: ...Date(), displayedComponents: .date) {
Text("Select Date")
}.colorInvert()
That inverted the black text to be white using the iOS Dark Theme. Looks/works great. Hope this helps.
In Xcode 11.5, iOS 13, mode is set automatically, by setting the date picker colors in your storyboard as follows:
background: systemBackgroundColor
text: labelColor
This results in the following:
I found that I also had to set the interface style in the simulator settings: