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()
}
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.
I'd like to update the UIKeyboardAppearance within a ViewController. By this I mean let's say the VC loads with UIKeyboardAppearance.default. If I press a button, I'd like the keyboard to update to .dark and have the keyboard now show in that same VC as .dark.
As far as I can tell, iOS checks the value for UIKeyboardAppearance while loading the VC, and doesn't check again until it loads the VC again. Even if you change the value of UIKeyboardAppearance and hide/show the keyboard.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// creating a simple text box, and making the placeholder text the value of the keyboardAppearance
myTextBox.backgroundColor = UIColor.lightGray
myTextBox.frame = CGRect(x: 30, y: 200, width: 300, height: 50)
view.addSubview(myTextBox)
UITextField.appearance().keyboardAppearance = .dark
myTextBox.becomeFirstResponder()
myTextBox.placeholder = "Keybaoard Appearance is: \(UITextField.appearance().keyboardAppearance.rawValue)"
// a simple button to toggle the keyboardAppearance
toggleButton.frame = CGRect(x: 30, y: 300, width: 300, height: 50)
toggleButton.setTitle("Toggle Keyboard", for: .normal)
toggleButton.backgroundColor = UIColor.red
toggleButton.addTarget(self, action: #selector(toggleButtonFunction), for: .touchUpInside)
view.addSubview(toggleButton)
}
// toggles the keyboardAppearance. Hides the keyboard, and a second later shows it again.
#objc func toggleButtonFunction() {
if UITextField.appearance().keyboardAppearance == .dark {
UITextField.appearance().keyboardAppearance = .default
}
else {
UITextField.appearance().keyboardAppearance = .dark
}
myTextBox.resignFirstResponder()
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1), execute: {
self.myTextBox.becomeFirstResponder()
self.myTextBox.placeholder = "Keybaoard Appearance is: \(UITextField.appearance().keyboardAppearance.rawValue)"
})
}
let myTextBox = UITextField()
let toggleButton = UIButton()
}
I was hoping that after changing the UIKeyboardAppearance and hiding/showing the keyboard it would show with the new appearance (.dark or .default), but it continually shows with the same appearance until the VC is loaded again. You can see the value of UIKeyboardAppearance changes, but iOS seems to not check for that update until the VC loads again.
Is there any way to force a recheck within a VC?
Thanks for your help!
You can change the keyboard appearance of all text fields recursively on your screen (the allSubviewsOf(type:) extension is from this great answer by Mohammad Sadiq):
func changeTextFieldKeyboardAppearance() {
UITextField.appearance().keyboardAppearance = .dark
let textFields = view.allSubviewsOf(type: UITextField.self)
let firstResponder = textFields.first { $0.isFirstResponder }
firstResponder?.resignFirstResponder()
textFields.forEach { $0.keyboardAppearance = .dark }
firstResponder?.becomeFirstResponder()
}
[...]
extension UIView {
func allSubviewsOf<T: UIView>(type: T.Type) -> [T] {
var all = [T]()
func getSubview(view: UIView) {
if let aView = view as? T {
all.append(aView)
}
guard !view.subviews.isEmpty else {
return
}
view.subviews.forEach{ getSubview(view: $0) }
}
getSubview(view: self)
return all
}
}
If your view controller is embedded in a UITabBarController, you can trigger an update by changing its selectedIndex and changing it back to the original index immediately:
guard let tabBarController = tabBarController else {
return
}
let selectedIndex = tabBarController.selectedIndex
UITextField.appearance().keyboardAppearance = .dark
tabBarController.selectedIndex = selectedIndex == 1 ? 0 : 1
tabBarController.selectedIndex = selectedIndex
Thanks to Tamás for the answer!
It led me down the path to discover what I needed.
It looks like if you change the keyboardAppearance for UITextField
UITextField.appearance().keyboardAppearance = .dark
the system only checks on VC load. If you change it for each textField
myTextBox.keyboardAppearance = .dark
the system will check each time firstResponder changes and load the correct keyboard.
Thanks again Tamás!
I am unable to change the prompt color on my navigation bar. I've tried the code below in viewDidLoad, but nothing happens.
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]
Am I missing something? Is the code above wrong?
I was able to make the prompt color white on iOS 11 was setting the barStyle to black. I set the other color attributes (like the desired background color) using the appearance proxy:
myNavbar.barStyle = UIBarStyleBlack; // Objective-C
myNavbar.barStyle = .black // Swift
It seems like you're right about this one. You need to use UIAppearance to style the prompt text on iOS 11.
I've filed radar #34758558 that the titleTextAttributes property just stopped working for prompt in iOS 11.
The good news is that there are a couple of workarounds, which we can uncover by using Xcode's view hierarchy debugger:
// 1. This works, but potentially changes *all* labels in the navigation bar.
// If you want this, it works.
UILabel.appearance(whenContainedInInstancesOf: [UINavigationBar.self]).textColor = UIColor.white
The prompt is just a UILabel. If we use UIAppearance's whenContainedInInstancesOf:, we can pretty easily update the color the way we want.
If you look closely, you'll notice that there's also a wrapper view on the UILabel. It has its own class that might respond to UIAppearance...
// 2. This is a more precise workaround but it requires using a private class.
if let promptClass = NSClassFromString("_UINavigationBarModernPromptView") as? UIAppearanceContainer.Type
{
UILabel.appearance(whenContainedInInstancesOf: [promptClass]).textColor = UIColor.white
}
I'd advise sticking to the more general solution, since it doesn't use private API. (App review, etc.) Check out what you get with either of these two solutions:
You may use
for view in self.navigationController?.navigationBar.subviews ?? [] {
let subviews = view.subviews
if subviews.count > 0, let label = subviews[0] as? UILabel {
label.textColor = UIColor.white
label.backgroundColor = UIColor.red
}
}
It will be a temporary workaround until they'll fix it
More complicated version to support old and new iOS
func updatePromptUI(for state: State) {
if (state != .Online) {
//workaround for SOFT-7019 (iOS 11 bug - Offline message has transparent background)
if #available(iOS 11.0, *) {
showPromptView()
} else {
showOldPromptView()
}
}
else {
self.navigationItem.prompt = nil
if #available(iOS 11.0, *) {
self.removePromptView()
} else {
self.navigationController?.navigationBar.titleTextAttributes = nil
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor:UIColor.lightGray]
}
}
}
private func showOldPromptView() {
self.navigationItem.prompt = "Network Offline. Some actions may be unavailable."
let navbarFont = UIFont.systemFont(ofSize: 16)
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.font: navbarFont, NSAttributedStringKey.foregroundColor:UIColor.white]
}
private func showPromptView() {
self.navigationItem.prompt = String()
self.removePromptView()
let promptView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 18))
promptView.backgroundColor = .red
let promptLabel = UILabel(frame: CGRect(x: 0, y: 2, width: promptView.frame.width, height: 14))
promptLabel.text = "Network Offline. Some actions may be unavailable."
promptLabel.textColor = .white
promptLabel.textAlignment = .center
promptLabel.font = promptLabel.font.withSize(13)
promptView.addSubview(promptLabel)
self.navigationController?.navigationBar.addSubview(promptView)
}
private func removePromptView() {
for view in self.navigationController?.navigationBar.subviews ?? [] {
view.removeFromSuperview()
}
}
I suggest using a custom UINavigationBar subclass and overriding layoutSubviews:
- (void)layoutSubviews {
[super layoutSubviews];
if (self.topItem.prompt) {
UILabel *promptLabel = [[self recursiveSubviewsOfKind:UILabel.class] selectFirstObjectUsingBlock:^BOOL(UILabel *label) {
return [label.text isEqualToString:self.topItem.prompt];
}];
promptLabel.textColor = self.tintColor;
}
}
Basically I'm enumerating all UILabels in the subview hierarchy and check if their text matches the prompt text. Then we set the textColor to the tintColor (feel free to use a custom color). That way, we don't have to hardcode the private _UINavigationBarModernPromptView class as the prompt label's superview. So the code is be a bit more future-proof.
Converting the code to Swift and implementing the helper methods recursiveSubviewsOfKind: and selectFirstObjectUsingBlock: are left as an exercise to the reader 😉.
You can try this:
import UIKit
class ViewController: UITableViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
updatePrompt()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updatePrompt()
}
func updatePrompt() {
navigationItem.prompt = " "
for view in navigationController?.navigationBar.subviews ?? [] where NSStringFromClass(view.classForCoder) == "_UINavigationBarModernPromptView" {
if let prompt = view.subviews.first as? UILabel {
prompt.text = "Hello Red Prompt"
prompt.textColor = .red
}
}
navigationItem.title = "This is the title (Another color)"
}
}
Moshe's first answer didn't work for me because it changed the labels inside of system VCs like mail and text compose VCs. I could change the background of those nav bars but that opens up a whole other can of worms. I didn't want to go the private class route so I only changed UILabels contained inside of my custom navigation bar subclass.
UILabel.appearance(whenContainedInInstancesOf: [NavigationBar.self]).textColor = UIColor.white
Try this out:->
navController.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor.rawValue: UIColor.red]
I've found next work around for iOS 11.
You need set at viewDidLoad
navigationItem.prompt = UINavigationController.fakeUniqueText
and after that put next thing
navigationController?.promptLabel(completion: { label in
label?.textColor = .white
label?.font = Font.regularFont(size: .p12)
})
extension UINavigationController {
public static let fakeUniqueText = "\n\n\n\n\n"
func promptLabel(completion: #escaping (UILabel?) -> Void) {
gloabalThread(after: 0.5) { [weak self] in
guard let `self` = self else {
return
}
let label = self.findPromptLabel(at: self.navigationBar)
mainThread {
completion(label)
}
}
}
func findPromptLabel(at view: UIView) -> UILabel? {
if let label = view as? UILabel {
if label.text == UINavigationController.fakeUniqueText {
return label
}
}
var label: UILabel?
view.subviews.forEach { subview in
if let promptLabel = findPromptLabel(at: subview) {
label = promptLabel
}
}
return label
}
}
public func mainThread(_ completion: #escaping SimpleCompletion) {
DispatchQueue.main.async(execute: completion)
}
public func gloabalThread(after: Double, completion: #escaping SimpleCompletion) {
DispatchQueue.global().asyncAfter(deadline: .now() + after) {
completion()
}
}
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 am trying to dismiss keyboard in text view using accessory views with done button but keyboard is not showing done button in it.Actually i have writen my code in textViewDidBeginEditing using inputAccessoryView.Here is my code.
func textViewDidBeginEditing(textView: UITextView) {
currentTextView = textView //This is to tell the current position in text view
var indexPath:NSIndexPath = NSIndexPath(forRow: 0, inSection: 2)
let cell:EventTableTableViewCell = EventTableview.cellForRowAtIndexPath(indexPath) as! EventTableTableViewCell
cell.messageTextView.autocorrectionType = UITextAutocorrectionType.No
let keyboardDoneButton = UIToolbar()
keyboardDoneButton.sizeToFit()
let item = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("doneButton"))
var toolbarButtons = [item]
//Put the buttons into the ToolBar and display the tool bar
keyboardDoneButton.setItems(toolbarButtons, animated: false)
keyboardDoneButton.userInteractionEnabled = true
//cell.messageTextView.inputAccessoryView = keyboardDoneButton
textView.inputAccessoryView = keyboardDoneButton
}
}
func doneButton()
{
UIApplication.sharedApplication().sendAction("resignFirstResponder", to:nil, from:nil, forEvent:nil)
}
_textView.returnKeyType = UIReturnKeyType.Done
Did you set the keyboard in IB?
Storyboard > Textfield > Attributes Inspector > Return Key > Done
Implement the delegate method:
- textfieldShouldReturn:
Found here: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITextFieldDelegate_Protocol/#//apple_ref/occ/intfm/UITextFieldDelegate/textFieldShouldReturn: