Detecting paste UITextView in textViewDidChange - ios

I am creating a UITextView for use as a rudimentary IDE. When the user pastes text (code) I am modifying it so that it fits inline (correctly indented) with the other text. However, I am also coloring the text (attributedText) using the textViewDidChange method. As a result when I paste text, it is first inserted then adjusted inline. The process looks a bit weird especially for a large chunck of text. I was wondering if there is a way for me to catch that the text is being pasted in textViewDidChange to avoid running attribute changes until the text is modified. Note that coloring attributes in the of the textView shouldChangeTextIn method is not possible as I am implementing some custom behavior through the interaction of the two methods and adding attributes needs to be done at the textViewDidChange stage.
EXAMPLE SETUP
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if (text == UIPasteboard.general.string) {
// MODIFY PASTABLE TEXT HERE
// INSERT MODIFIED TEXT
textView.replace(textRange, withText: newText)
// PREVENT OLD TEXT FROM BEING INSERTED
return false
}
}
func textViewDidChange(_ textView: UITextView) {
// MANAGE ATTRIBUTES
}
WEIRD BEHAVIOR

Related

Ignore space after predictive text selection in UITextView

I found something similar to my problem checkout here but it does not work for me: link
Thid UITextView delegate method calls twice If we tap on predictive text
1st call insert text 2nd call insert space after if
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool
I found one hack:
if text.count > 1 { return false }
But this logic failed when user tap on single character predictive text
I am doing some customizations in this method e.g. mentions support so after doing some work I update attributedText of TextView on mainThread so before setting attributedText to the textView this method get call for space in predictive text selection case and at that time previous text is not setup yet so it will insert just space.
Two things comes in my mind, somehow handle the call of this method that it should not call twice until text is set to textView
Second somehow ignore the space after predictive text.
if text == " " { return false }
We also can not do this because after that user will not be able to insert spaces.
Predictive texts:

UITextView delegate textViewDidChangeSelection is called twice

I'm trying to do some stuff when user taps Enter button, so I have implemented the following delegates for UITextView:
// Delegate is called when text is gonna change
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if (text == "\n") {
// Some behavior when user taps Enter
return true
}
// Some other code
return true
}
// Delegate is called when selection is changed
func textViewDidChangeSelection(_ textView: UITextView) {
// Some code
}
The problem is, textViewDidChangeSelection is called twice when user taps Enter.
In second call, textView.selectedRange is changed to the last character of text view. This produces a problem when user taps Enter after any line in the middle of text, because caret position is changed to end of text.
I've attached a ‏reproducible example here so you can check, I'm not sure if the problem is in the delegates, or in the way I attached the delegate to the view.
To re-produce the problem, do the following scenario:
Write some lines, for example:
Line 1.
Bla bla bla
line 3
Go to the end of line 1.
Tap on Enter.
The new line is there in the right position, but the caret position is changed to the end of text view.
Notes:
I've checked the following post in stackoverflow, but it doesn't
fix my problem.
The file you need to review in my ‏reproducible example is EditorTextView.swift
only.
OK finally, I found a solution for this weird behavior of delegation in the UITextView wrapper for SwiftUI.
So the entire issue was with setting the text again every time updateUIView was called. That is not necessary and it caused a weird feedback loop with textViewDidChange delegate method (that is where I set the selected range when the last character was \n, which is the case for a new line).
I don't know exactly why does that happen, I just think it's a bug with how UIViewRepresentables works under the hood.
So, to solve the issue, I had to move the following line from updateUIView:
uiView.text = document
And add it to makeUIView:
textView.text = document
This way, it will be bound permanently.

Detect change of text in a UI Text field when the values are changed from the controller

I'm working on an iOS assignment submission and I'm required to change the value of one text field and this change should reflect on 4 other text fields. On a normal scenario "editingChanged" action would suffice, but I'm using my own custom keyboard (Just buttons on the same view) therefore this action does not get called. What would be the most efficient way to detect a change in text when the change is done by the controller.
You can have a variable where you store the value in the ViewController and then in the didSet of that value do the necessary changes:
var value: String {
didSet {
// do the changes of the others UITextfields here.
}
}
You should set the delegate of the UITextField, and then implement func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool.
Within the body of this function you can change the value of the other text fields

iOS 11: UITextView typingAttributes reset when typing

I use typingAttributes to set a new font. On iOS 10, everything works, but on iOS 11, first typed character is correct, but then attributes are being reset to the previous ones, and the second character is typed with the previous font. Is this a bug? Can I fix it somehow?
Why this happened:
Apple updated typingAttributes since iOS 11
This dictionary contains the attribute keys (and corresponding values) to apply to newly typed text. When the text view’s selection changes, the contents of the dictionary are cleared automatically.
How to fix it:
#Serdnad's code works but it will skip the first character. Here is my finding after trying everything that I can possibly think of
1. If you just want one universal typing attribute for your text view
Simply set the typing attribute once in this delegate method and you are all set with this single universal font
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
//Set your typing attributes here
textView.typingAttributes = [NSAttributedStringKey.foregroundColor.rawValue: UIColor.blue, NSAttributedStringKey.font.rawValue: UIFont.systemFont(ofSize: 17)]
return true
}
2. In my case, a Rich Text Editor with attributes changeinge all the times:
In this case, I do have to set typing attributes each and every time after entering anything. Thank you iOS 11 for this update!
However, instead of setting that in textViewDidChange method, doing it in shouldChangeTextIn method works better since it gets called before entering characters into the text view.
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
textView.typingAttributes = [NSAttributedStringKey.foregroundColor.rawValue: UIColor.blue, NSAttributedStringKey.font.rawValue: UIFont.systemFont(ofSize: 17)]
return true
}
Had issues with memory usage while using typingAttributes
This solution worked:
textField.defaultTextAttributes = yourAttributes // (set in viewDidLoad / setupUI)
What was the problem with using typingAttributes: at some point in time, the memory usage went up and never stopped and caused the app to freeze.
I ran into the same issue and eventually solved it by setting typingAttributes again after every edit.
Swift 3
func textViewDidChange(_ textView: UITextView) {
NotesTextView.typingAttributes = [NSForegroundColorAttributeName: UIColor.blue, NSFontAttributeName: UIFont.systemFont(ofSize: 17)]
}
From iOS 11, Apple clear the attributes after every character.
When the text view’s selection changes, the contents of the dictionary are cleared automatically.
https://developer.apple.com/documentation/uikit/uitextview/1618629-typingattributes

Narrowing Down UITextField on BackSpace while typing

Using Storyboard, I setup a UITextField and two static UILabels. I setup the constraints so the static labels spread out as user types in the TextField.
[Label1] [TextField] [Label2]
[Label1] [TextFieldIsGettingFilled] [Label2]
Everything is fine until now. The TextField is getting wider as user types in. However, if the user uses backspace (deletes character), TextField doesn't get narrower. So it becomes like:
[Label1] [TextField ] [Label2]
What is the way of detecting backspace and narrow TextFields' width accordingly while user is still typing?
EDIT
I made an example and this works (increases and decreases according to text):
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
textField.invalidateIntrinsicContentSize()
return true
}
Source: Resize a UITextField while typing (by using Autolayout)

Resources