Creating a tappable text section inside a UITextView - ios

I have the following problem, I have a textview that says something like this "By tapping the "I agree" button, I certify that I have read and agree to the Online Terms and Conditions"
what I want to do is to add a tap gesture recognizer to the part that says "Online Terms and Conditions" and call a method when the user taps this text section, if the user clicks in any other section of the text nothing should happen.
How can I do this? (I can't post any code due to the NDA I signed) :( I hope I can still be helped.
Thanks in advance.

You can use attributed string with the tag NSLinkAttributeName set for the range of tappable text. Then in the textView delegate implement
optional func textView(_ textView: UITextView, shouldInteractWith URL:URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool
Note that your text field must be selectable and non-editable for this to work.
Reference: https://developer.apple.com/reference/uikit/uitextviewdelegate/1649337-textview
Prior to iOS 10 you can use:
optional func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool`
Reference:
https://developer.apple.com/reference/uikit/uitextviewdelegate/1618606-textview
If you need to support both use the #available api - put #available(iOS, deprecated: 10.0) before pre-iOS 10 version and #available(iOS 10.0, *) before the one for iOS 10. That way you will avoid compile-time errors.

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.

RxSwift wrapper for textView:shouldInteractWithURL:inRange:interaction:

I want to have a method to intercept links tap in a UITextView through RxSwift something similar to:
textView.rx.didTapLink
.subscribe(onNext: { link, characterRange, interaction in
// handle link tap
})
I saw there is no implementation for delegate forwarding for the textView:shouldInteractWithURL:inRange:interaction: method so I presume I must add an extension for the RxTextViewDelegateProxy to implement the missing delegate method but don't know how to continue from there or if what I want is event possible without forking RxSwift but it should be possible I presume. I really appreciate any help.
Because the method in question returns a value (the OS pulls data from your app using this method,) it doesn't fit well with the Rx "push data" ecosystem. The appropriate way to implement this is as follows:
Given:
class MyTextViewDelegate: NSObject, UITextViewDelegate {
func textView(_ textView: UITextView, shouldInteractWith url: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
return true // do what you think best here.
}
}
You can connect it to the text view like this:
textView.rx.delegate.setForwardToDelegate(MyTextViewDelegate(), retainDelegate: true)
Using the forwardToDelegate allows you to continue to use the push type delegate methods using the Rx system.

Detecting paste UITextView in textViewDidChange

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

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

Resources