I am wondering if there is a way to detect when user press delete button or try to erase character in UISearchBar? I searched for quite a bit on Google and I tried couple of solutions where one put a whiteSpaceCharacter in the empty UITextField and the other use shouldChangeTextInRange delegate to do.
The one with empty space, the user can somehow know that there is a space there by just press hold and highlight the space so I think that was not a very good solution.
The one that use shouldChangeTextInRange does not work when the search bar is empty.
Another thing to note is that majority of the solutions I found was in Objective C and it would be great if there are some way I can do this in Swift. Or would be great if anyone can point me to the right path.
Don't know why you would wan't do delete when the searchBar is empty, but you can do it like this:
import UIKit
class ViewController: UIViewController, UISearchBarDelegate{
private var search : UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
search = UISearchBar()
search.frame = CGRectMake(0, 0, 200, 200)
search.delegate = self
self.view.addSubview(search)
}
func searchBar(searchBar: UISearchBar, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
let char = text.cStringUsingEncoding(NSUTF8StringEncoding)!
let isBackSpace = strcmp(char, "\\b")
if (isBackSpace == -92) {
print("Backspace was pressed")
}
return true
}
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
if searchText == "" {
search.text = " "
}
}
}
This takes use of the UISearchBarDelegate, and finds out if backspace is pressed, via the shouldChangeTextInRange function. If the string is empty, it reapplies a whitespace to make it continuously deletable. It's kinda tacky, but the only way I know of to do it, as it doesen't register when the field is empty otherwise.
Hope it's of any use.
Related
I want to listen for every text change in UITextView. The setup is very trivial.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(
self,
selector: #selector(textViewDidChangeWithNotification(_:)),
name: UITextView.textDidChangeNotification,
object: nil
)
}
#objc private func textViewDidChangeWithNotification(_ notification: Notification) {
print("Text: \(String(describing: inputTextView.text))")
}
It works OK in most cases, but then I have found some UITextInput's black box magic.
Step 1: 'I' typed. We can see 'I' in the output.
Step 2: Important step. Select all text with double tap on the field.
Step 3: Select 'If' from word suggestions.
And there is no 'If' in the debuggers output. On the other side if the caret will be at the end of the 'I' word and we select 'If' output results are correct.
Is there any way to observe ALL text changes?
I can get text by using:
func textViewDidEndEditing(_ textView: UITextView)
but I need to observe all changes in real time. The other option I always see is to use:
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool
but this method is a bad practice to observe changes. If you type two spaces repeatedly iOS will replace first space with dot and obviously will not inform you about this action in this method and a lot of other problems with it.
OK, after a lot of research I've tried RxSwift because I thought that observing text in reactive paradigm framework should succeed in 100% cases. And it worked without any issues!
inputTextView.rx.text.subscribe(onNext: { string in
print("rx: \(string)")
})
So it seems that these guys have found the solution.
https://github.com/ReactiveX/RxSwift/blob/master/RxCocoa/iOS/UITextView%2BRx.swift
And here is the solution that gives you information about all text changes despite of auto correction, text selection, and etc..
class ViewController: UIViewController {
#IBOutlet weak var inputTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
inputTextView.textStorage.delegate = self
}
}
extension ViewController: NSTextStorageDelegate {
func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorage.EditActions, range editedRange: NSRange, changeInLength delta: Int) {
print("string: \(textStorage.string)")
}
}
You can use func textViewDidChangeSelection(_ textView: UITextView) to detect the change in selection ?
If you want to listen for every change shouldChangeTextIn is the way to go. You can write conditions to solve the problems associated with it
I created a UIViewRepresentable to wrap UITextField for SwiftUI, so I can e.g. change the first responder when the enter key was tapped by the user.
This is my UIViewRepresentable (I removed the first responder code to keep it simple)
struct CustomUIKitTextField: UIViewRepresentable {
#Binding var text: String
var placeholder: String
func makeUIView(context: UIViewRepresentableContext<CustomUIKitTextField>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.delegate = context.coordinator
textField.placeholder = placeholder
return textField
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomUIKitTextField>) {
uiView.text = text
uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
uiView.setContentCompressionResistancePriority(.required, for: .vertical)
}
func makeCoordinator() -> CustomUIKitTextField.Coordinator {
Coordinator(parent: self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: CustomUIKitTextField
init(parent: CustomUIKitTextField) {
self.parent = parent
}
func textFieldDidChangeSelection(_ textField: UITextField) {
parent.text = textField.text ?? ""
}
}
}
The first screen of the app has a "Sign in with email" button which pushes MailView that displays a CustomUIKitTextField and uses a #Published property of an ObservableObject view model as the TextField's text.
struct MailView: View {
#ObservedObject var viewModel: MailSignUpViewModel
var body: some View {
VStack {
CustomUIKitTextField(placeholder: self.viewModel.placeholder,
text: self.$viewModel.mailAddress)
.padding(.top, 30)
.padding(.bottom, 10)
NavigationLink(destination: UsernameView(viewModel: UsernameSignUpViewModel())) {
Text("Next")
}
Spacer()
}
}
}
Everything works fine until I push another view like MailView, say e.g. UsernameView. It is implemented exactly in the same way, but somehow the CustomUIKitTextField gets an updateUIView call with an empty string once I finish typing.
There is additional weird behavior like when I wrap MailView and UsernameView in another NavigationView, everything works fine. But that is obviously not the way to fix it, since I would have multiple NavigationViews then.
It also works when using an #State property instead of a #Published property inside a view model. But I do not want to use #State since I really want to keep the model code outside the view.
Is there anybody who faced the same issue or a similar one?
It looks like you’re using the wrong delegate method. textFieldDidChangeSelection will produce some inconsistent results (which is what it sounds like you’re dealing with). Instead, I recommend using textFieldDidEndEditing which will also give you access to the passed in control, but it guarantees that you’re getting the object as it is resigning the first responder. This is important because it means you’re getting the object after the properties have been changed and it’s releasing the responder object.
So, I would change your delegate method as follows:
func textFieldDidEndEditing(_ textField: UITextField) {
parent.text = textField.text ?? ""
}
For more info, see this link for the textFieldDidEndEditing method:
https://developer.apple.com/documentation/uikit/uitextfielddelegate/1619591-textfielddidendediting
And this link for info on the UITextFieldDelegate object:
https://developer.apple.com/documentation/uikit/uitextfielddelegate
EDIT
Based on the comment, if you're looking to examine the text everytime it changes by one character, you should implement this delegate function:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// User pressed the delete key
if string.isEmpty {
// If you want to update your query string here after the delete key is pressed, you can rebuild it like we are below
return true
}
//this will build the full string the user intends so we can use it to build our search
let currentText = textField.text ?? ""
let replacementText = (currentText as NSString).replacingCharacters(in: range, with: string)
// You can set your parent.text here if you like, or you can fire the search function as well
// When you're done though, return true to indicate that the textField should display the changes from the user
return true
}
I also needed a UITextField representation in SwiftUI, which reacts to every character change, so I went with the answer by binaryPilot84. While the UITextFieldDelegate method textField(_:shouldChangeCharactersIn:replacementString:) is great, it has one caveat -- every time we update the text with this method, the cursor moves to the end of the text. It might be desired behavior. However, it was not for me, so I implemented target-action like so:
public func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.addTarget(context.coordinator, action: #selector(context.coordinator.textChanged), for: .editingChanged)
return textField
}
public final class Coordinator: NSObject {
#Binding private var text: String
public init(text: Binding<String>) {
self._text = text
}
#objc func textChanged(_ sender: UITextField) {
guard let text = sender.text else { return }
self.text = text
}
}
I am unable to detect x button action in UISearchBar iOS 9. is there anyone can help me?
if your view controller is your search bar delegate, you could implement searchBarCancelButtonClicked function and resignFirstResponder from there.
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
searchBar.text = ""
searchBar.resignFirstResponder()
// or you could force view to end editing mode using self.view.endEditing(true)
}
you can do something like,
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.characters.count == 0 {
performSelector("hideKeyboardWithSearchBar:", withObject:searchBar, afterDelay:0)
}
}
refer apple documentation for more details
Hope this will help :)
func hideKeyboardWithSearchBar(bar:UISearchBar) {
bar.resignFirstResponder()
}
I have been trying to get the keyboard during a textview to dismiss but it STILL doesn't change the return button's action.
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
if text == "\n"
{
textView.resignFirstResponder()
return false
}
return true
}
I was following the tutorial at https://www.youtube.com/watch?v=IsnoS8_G2SU
By now i have essentially copied the code 100%.
P.S. the textfield's outlet is named Textviews
Please help!
EDIT: Someone marked this as duplicate so let me explain why it isnt - I already have done what it tells me to do, and I'm not sure why it doesn't work for me.
You have made everything correct, but there is one step missing.
In order to hide or show the keyboard via the responder you have to set the delegate otherwise it won't work.
You can do it for example in the viewDidLoad:
override func viewDidLoad() {
myTextView.delegate = self
}
If your textfield is Textviews then you should have
Textviews.resignFirstResponder()
I hope this helps
I would like to get rid of the "return" function of the keyboard while the user is typing, so there are no new lines, so instead I would like the 'return' key to function as 'Done' so it would hide the keyboard.
I am using a UITextView, that is editable, so the user is able to type their post, and post it to the main timeline, but since I have fixed cells, I don't want the user to be able to press 'return' and their post would be out of range of the timeline.
I found this that works with UITextField, but not with UITextView:
func textFieldShouldReturn(textField: UITextField!) -> Bool {
textField.resignFirstResponder() //if desired
return true
}
So I just wanted to know if there is a way to do that in a UITextView, or at least to be able to hide the keyboard if pressed return, instead of creating a new line.
You can set the return key type of the text field:
textField.returnKeyType = UIReturnKeyType.done
Update
You can definitely use the same approach to set the return key to "Done", as mentioned above. However, UITextView doesn't provide a callback when user hits the return key. As a workaround, you can try to handle the textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) delegate call, and dismiss the keyboard when you detect the input of a new line character:
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if (text == "\n") {
textView.resignFirstResponder()
}
return true
}
I have tried many codes and finally this worked for me in Swift 3.0 Latest [April 2019] this achieved using UITextFields
The "ViewController" class should be inherited the "UITextFieldDelegate" for making this code working.
class ViewController: UIViewController,UITextFieldDelegate
Add the Text field with the Proper Tag number and this tag number is used to take the control to appropriate text field based on incremental tag number assigned to it.
override func viewDidLoad() {
userNameTextField.delegate = self
userNameTextField.tag = 0
userNameTextField.returnKeyType = .next
passwordTextField.delegate = self
passwordTextField.tag = 1
passwordTextField.returnKeyType = .go
}
In the above code, the "returnKeyType = UIReturnKeyType.next" where will make the Key pad return key to display as "Next" you also have other options as "Join/Go" etc, based on your application change the values.
This "textFieldShouldReturn" is a method of UITextFieldDelegate controlled and here we have next field selection based on the Tag value incrementation.
func textFieldShouldReturn(_ textField: UITextField) -> Bool
{
if let nextField = textField.superview?.viewWithTag(textField.tag + 1) as? UITextField {
nextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
return true;
}
return false
}
If you're working with a storyboard or xib, you can change the UITextView's Return button to 'Done' (or various other options) within Interface Builder, without the need for any setup code. Just look for this option in the Attributes inspector:
From there, you just pair it up with the UITextViewDelegate code that others have already provided here.
Swift v5:
extension ExampleViewController: UITextViewDelegate {
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if (text == "\n") {
textView.resignFirstResponder()
}
return true
}
}
And then, in your viewDidLoad() method:
exampleTextView.delegate = self
Working in Swift 4
Add this in viewDidLoad().
textField.returnKeyType = UIReturnKeyType.Done
Add this anywhere you like.
extension UITextView: UITextViewDelegate {
public func textViewDidChange(_ textView: UITextView) {
if text.last == "\n" { //Check if last char is newline
text.removeLast() //Remove newline
textView.resignFirstResponder() //Dismiss keyboard
}
}
}