Error with UITextField moving - ios

I'm trying to design a view within my app that displays an error UILabel when the text inside of a UITextField does not match a certain regex. Everything works fine except for when my UITextField contains an invalid entry and then I tap the screen to dismiss the keyboard. After the keyboard dismisses, the UITextField moves on top of my UITextView (the red text) and I cannot find out why. I have set breakpoints in my code, but it seems that none of my code is executed after the keyboard dismisses. Any help is appreciated.
//
// ViewController.swift
// KeyboardLabel
//
import UIKit
class ViewController: UIViewController {
#IBOutlet var titleLabel:UILabel!
#IBOutlet var errorLabel:UITextView!
#IBOutlet var textBox:UITextField!
#IBOutlet var createButton:UIButton!
var deltaHeight:CGFloat!
var beenMoved = true
override func viewDidLoad() {
super.viewDidLoad()
// Register for notifications when the text in the text field is changed
NSNotificationCenter.defaultCenter().addObserver(self, selector: "validateText", name: UITextFieldTextDidChangeNotification, object: textBox)
// Add a gesture recognizer to dismiss the keyboard
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "dismissKeyboard"))
// Set the text of the labels
titleLabel.text = "Container Number:"
errorLabel.text = "Error, Invalid container number.\nRe-enter number."
// Calculate the height to move the text field and button
deltaHeight = errorLabel.frame.size.height + 8.0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func dismissKeyboard() {
textBox.resignFirstResponder()
}
func validateText() {
dispatch_async(dispatch_get_main_queue(), {
// Regular expression for determining whether the text is valid
let predicate = NSPredicate(format: "SELF MATCHES %#", "[a-zA-Z0-9#]+")
let empty = self.textBox.text!.isEmpty
let valid = predicate.evaluateWithObject(self.textBox.text)
// If the string is valid
if valid || empty {
// If the view has been moved down
if(!self.beenMoved) {
// Hide the error label then move the text field and create button up
self.errorLabel.hidden = true
UIView.animateWithDuration(0.4, animations: {
self.textBox.frame.origin.y -= self.deltaHeight
self.createButton.frame.origin.y -= self.deltaHeight
})
// Flip the flag
self.beenMoved = true
}
}
// If the string is invalid
else {
// If the view has been moved up
if(self.beenMoved) {
// Show the error label then move the text field and create button down
UIView.animateWithDuration(0.4, animations: {
self.textBox.frame.origin.y += self.deltaHeight
self.createButton.frame.origin.y += self.deltaHeight
}, completion: {
(flag:Bool) in
self.errorLabel.hidden = false
})
// Flip the flag
self.beenMoved = false
}
}
// If the text field is empty
if empty {
// Disable the create button
self.createButton.enabled = false
}
else {
// Enable the create button
self.createButton.enabled = true
}
})
}
}

The behavior is actually such that initially, the UITextField moves on top of the UITextView if you enter something that does not match the regex. This is because you are moving the UITextField and button with the following lines of code:
self.textBox.frame.origin.y += self.deltaHeight
self.createButton.frame.origin.y += self.deltaHeight
When the keyboard dismisses, the UITextField and UIButton return to their original locations. I am assuming you have no constraints on the UI elements.

Related

Swift UITextView Delegate

I am having a problem and have searched all across StackO and did not see a solution.
I have a UITextview extension with TextViewDelegate that I call inside of my VC so that i can have a placeholder label. The problem is i now need to add a func that checks for remaining chars in that same textView which i am able to get to work properly. But i cant grab a label to present it on the VC from that extension. I have been trying delegates but since it is a delegate itself i cant use my normal methods. What is the best route to go about this? Thank You for your help!
Here is the code. The placeholder label code is left out since it will make everything longer and I do not feel its needed for a solution. But I can add if necessary. And i can not move this code straight into VC as i need this extension to stay like this.
extension UITextView: UITextViewDelegate {
/// When the UITextView change, show or hide the label based on if the UITextView is empty or not
public func textViewDidChange(_ textView: UITextView) {
if let placeholderLabel = self.viewWithTag(100) as? UILabel {
placeholderLabel.isHidden = !self.text.isEmpty
}
checkRemainingChars(textView: textView)
}
func checkRemainingChars(textView: UITextView) {
let allowedChars = 140
if let charsInTextField = textView.text?.count {
let charsInLabel = charsInTextField
let remainingChars = charsInLabel
if remainingChars <= allowedChars {
//Need to grab this label
charsLeftLabel.textColor = UIColor.lightGray
}
if remainingChars >= 120 {
//Need to grab this label
charsLeftLabel.textColor = UIColor.orange
}
if remainingChars >= allowedChars {
//Need to grab this label
charsLeftLabel.textColor = UIColor.red
}
//This prints fine
print("Remaining chars is \(remainingChars)/140")
//Need to grab this label
charsLeftLabel.text = String(remainingChars)
}
}
Thanks again.

iOS (swift) exit/dismiss preselect textView in share extension

when launching the iOS share extension the textView will by default already be selected / entered. (the keyboard will be visible and the textView will be in edit mode)
I don't want this to happen, how do I programatically exit the textView
override func viewDidLoad() {
self.textView.exit() // obviously doesn't work
}
I see tons of posts about how to exit when user press enter on the keyboard, I do not want to do it "when something delegate" I just want the textview to not be in edit mode when the extension is launched (on viewDidLoad).
I have also tried (as suggested in other post)
self.textView.endEditing(true)
which did not hide the keyboard or exit the textView
You can call textView.resignFirstResponder() in presentationAnimationDidFinish
class ShareViewController: SLComposeServiceViewController {
var textViewTintColor: UIColor?
override func viewDidLoad() {
super.viewDidLoad()
// hide cursor which appears during presentation animation
self.textViewTintColor = self.textView.tintColor
self.textView.tintColor = .clear
}
override func presentationAnimationDidFinish() {
super.presentationAnimationDidFinish()
guard let tintColor = self.textViewTintColor else { return }
self.textView.resignFirstResponder()
// reset cursor
self.textView.tintColor = tintColor
self.textViewTintColor = nil
}
}

(Swift) UITextView not displaying text

I'm making a text-based iOS game which uses a single view consisting of a UITextView (scrollable, not editable) and a UITextField. I've made it so when I press return while editing the textField, the keyboard closes and the text of the textField is added to the textView. However, the text of the UITextView doesn't appear at all. The background color still appears, but nothing else, no matter how I try to display it. I've already tried setting the font size and color.
Here's code from the ViewController that handles both UI components.
import UIKit
class ViewController: UIViewController, UITextFieldDelegate, UITextViewDelegate {
// outlets / actions
#IBOutlet weak var outputView: UITextView!
#IBOutlet weak var inputField: UITextField!
var uni: Universe? // a custom class that contains the game's data
override func viewDidLoad() {
//outputView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.New, context: nil)
inputField.delegate = self
outputView.delegate = self
inputField.text = "press return for help"
outputView.text = ""
outputView.textColor = UIColor.greenColor()
outputView.frame = self.view.frame
// The next 3 lines make a new Universe object.
uni = Assembler.build(outputView)
uni!.setUni()
uni!.setParser()
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
uni!.player.viewLocation()
}
// delegate method; this should do 3 things:
// 1. remove the keyboard
// 2. call uni.parser.read(text)
// 3. remove the text in the field
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
outputView.text! += textField.text! + "\n"
uni!.parser!.read(textField.text!) // This function interprets the text from textField and prints something appropriate in the textView.
textField.text = ""
return true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// The rest of the code makes the whole frame shift up/down when the keyboard appears/disappears to keep the UITextField visible.
}
Any help at all would be greatly appreciated. The similar questions on this site haven't given me anything to go on.

becomeFirstResponder returns true and keyboard is dismissed

In the following code I am trying to transfer control from UITextField to the next via a next button.
I am doing this by calling becomeFirstResponder on the next UITextField.
If I don't type anything in the first and current UITextField the next button works as expected. The keyboard stays up and the focus is transferred.
If I do type something, and only if the field is empty. The method becomeFirstResponder for the next field is called and returns true, yet the keyboard is dismissed and focus is not transferred.
public func numberPad(numberPad: APNumberPad, functionButtonAction:UIButton, textInput: UIResponder) {
var current:UITextField?
for field in editCells {
if (current != nil) {
field.valueTextField.becomeFirstResponder()
return;
}
if (field.valueTextField == activeField) {
current = field.valueTextField
}
}
textInput.resignFirstResponder()
}
This function is called when the NEXT or DONE button is pressed on the keyboard. Which is a custom number keypad. APNumberPad specifically.
https://github.com/podkovyrin/APNumberPad
It is my delegate function.
Anyone know any reason becomeFirstResponder would return true and not work, only in some cases, but work in others?
And yes this is the main UI thread. Adding a call to resignFirstResponder on the current field, then a delay and calling becomeFirstResponder works. This causes the keypad to flicker, no matter how small the delay though.
Edit... I am now doing this... and am living with the keyboard flicker for now:
Delay is a helper function for GCD
public func numberPad(numberPad: APNumberPad, functionButtonAction:UIButton, textInput: UIResponder) {
var current:UITextField?
for field in editCells {
if (current != nil) {
current?.resignFirstResponder()
delay (0) {
field.valueTextField.becomeFirstResponder()
}
return;
}
if (field.valueTextField == activeField) {
current = field.valueTextField
}
}
textInput.resignFirstResponder()
}
I don't know if it helps you, or not. I wrote a simple UITextField extension that contains a returnView variable which decides what the textfield should do on return key press:
turn to next text field (if the returnView is an UITextField)
simulate button touch (if the returnView is a UIButton)
or hide keyboard
class NextTextField: UITextField, UITextFieldDelegate {
#IBOutlet weak var returnView: UIView? {
didSet {
if returnView is UITextField {
returnKeyType = UIReturnKeyType.Next
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
delegate = self
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
if let nextTextField = self.returnView as? UITextField {
nextTextField.becomeFirstResponder()
} else if let nextButton = self.returnView as? UIButton {
nextButton.sendActionsForControlEvents(.TouchUpInside)
} else {
self.resignFirstResponder()
}
return true
}
}

Custom Input View in Swift

I've spent hours trying to figure out how to create/then get a custom inputView to work.
I have a grid of TextInputs (think scrabble board) that when pressed should load a custom inputView to insert text.
I've created a .xib file containing the UI elements for the custom inputView. I was able to create a CustomInputViewController and have the inputView appear but never able to get the actual TextInput to update it's value/text.
Apple documentation has seemed light on how to get this to work and the many tutorials I've have seen have been using Obj-C (which I have been unable to convert over due to small things that seem unable to now be done in swift).
What is the overarching architecture and necessary pieces of code that should be implemented to create a customInputView for multiple textInputs (delegate chain, controller, .xibs, views etc)?
Set up a nib file with the appropriate inputView layout and items. In my case I set each button to an action on File Owner of inputViewButtonPressed.
Set up a storyboard (or nib if you prefer) for a view controller.
Then using the following code, you should get what you're looking for:
class ViewController: UIViewController, UITextFieldDelegate {
var myInputView : UIView!
var activeTextField : UITextField?
override func viewDidLoad() {
super.viewDidLoad()
// load input view from nib
if let objects = NSBundle.mainBundle().loadNibNamed("InputView", owner: self, options: nil) {
myInputView = objects[0] as UIView
}
// Set up all the text fields with us as the delegate and
// using our input view
for view in self.view.subviews {
if let textField = view as? UITextField {
textField.inputView = myInputView
textField.delegate = self
}
}
}
func textFieldDidBeginEditing(textField: UITextField) {
activeTextField = textField
}
func textFieldDidEndEditing(textField: UITextField) {
activeTextField = nil
}
#IBAction func inputViewButtonPressed(button:UIButton) {
// Update the text field with the text from the button pressed
activeTextField?.text = button.titleLabel?.text
// Close the text field
activeTextField?.resignFirstResponder()
}
}
Alternatively, if you're wanting to be more keyboard-like, you can use this function as your action (it uses the new let syntax from Swift 1.2), break it up if you need 1.1 compatibility:
#IBAction func insertButtonText(button:UIButton) {
if let textField = activeTextField, title = button.titleLabel?.text, range = textField.selectedTextRange {
// Update the text field with the text from the button pressed
textField.replaceRange(range, withText: title)
}
}
This uses the UITextInput protocol to update the text field as appropriate. Handling delete is a little more complicated, but still not too bad:
#IBAction func deleteText(button:UIButton) {
if let textField = activeTextField, range = textField.selectedTextRange {
if range.empty {
// If the selection is empty, delete the character behind the cursor
let start = textField.positionFromPosition(range.start, inDirection: .Left, offset: 1)
let deleteRange = textField.textRangeFromPosition(start, toPosition: range.end)
textField.replaceRange(deleteRange, withText: "")
}
else {
// If the selection isn't empty, delete the chars in the selection
textField.replaceRange(range, withText: "")
}
}
}
You shouldn't go through all that hassle. There's a new class in iOS 8 called: UIAlertController where you can add TextFields for the user to input data. You can style it as an Alert or an ActionSheet.
Example:
let alertAnswer = UIAlertController(title: "Input your scrabble Answer", message: nil, preferredStyle: .Alert) // or .ActionSheet
Now that you have the controller, add fields to it:
alertAnswer.addTextFieldWithConfigurationHandler { (get) -> Void in
getAnswer.placeholder = "Answer"
getAnswer.keyboardType = .Default
getAnswer.clearsOnBeginEditing = true
getAnswer.borderStyle = .RoundedRect
} // add as many fields as you want with custom tweaks
Add action buttons:
let submitAnswer = UIAlertAction(title: "Submit", style: .Default, handler: nil)
let cancel = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
alertAnswer.addAction(submitAnswer)
alertAnswer.addAction(cancel)
Present the controller whenever you want with:
self.presentViewController(alertAnswer, animated: true, completion: nil)
As you see, you have various handlers to pass custom code at any step.
As example, this is how it would look:

Resources