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.
I've implemented changing the height of UITextView dynamically when height reaches a certain value by following this solution https://stackoverflow.com/a/38454252/12006517
This works fine however text view freezes when I paste a large chunk of text in it first time. After pasting large chunk of text it doesn't go to the end of text content and cursor disappears while text view freezes. I've to hit delete key and start entering then it starts to work fine.
Subsequent paste of large chunk of text works. So problem happens only pasting first time.
How do I fix this issue?
class MyViewController: UIViewController {
let messageTextViewMaxHeight: CGFloat = 200
}
extension MyViewController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
if textView.contentSize.height >= self.messageTextViewMaxHeight {
textView.isScrollEnabled = true
} else {
textView.frame.size.height = textView.contentSize.height
}
}
}
You can try below code. It working fine.
func textViewDidChange(_ textView: UITextView) {
let sizeThatShouldFitTheContent = textView.sizeThatFits(textView.frame.size)
if sizeThatShouldFitTheContent.height > 120 {
textView.isScrollEnabled = true
}else{
textView.isScrollEnabled = false
}
}
I have a text view in my project that I want to expand when user input more that 20 characters so I used this method
func textViewDidChange(_ textView: UITextView) {
if chatTexts.text.characters.count > 20 {
print("up to 20")
chatTexts.isScrollEnabled = false
}
and when keyboard is going up i used this to move text view to the top of keyboard
func keyboardShown(notification: NSNotification) {
if let infoKey = notification.userInfo?[UIKeyboardFrameEndUserInfoKey],
let rawFrame = (infoKey as AnyObject).cgRectValue {
let keyboardFrame = view.convert(rawFrame, from: nil)
self.heightKeyboard = keyboardFrame.size.height
levelChatViewController.sizeForOffsetKeyboard = heightKeyboard!
if levelChatViewController.sizeForOffsetKeyboard == 253.0 {
print("the size is 253.0")
animateViewMoving(up: true, moveValue: 207)
} else {
print("the size is 216.0")
animateViewMoving(up: true, moveValue: 167)
}
}
But when the user start to write some texts in textview the textview will going down under the keyboard
remember that before adding textViewDidChange every thing was ok and the textview was going to the top of the keyboard and the only problem was that doesn't scroll so I used textViewDidChange method to do that but this problem happend
Is it possible to perform custom action when user touch autodetected phone link in UITextView. Please do not advice to use UIWebView instead.
And please don't just repeat text from apple classes reference
- certainly I've already read it.
Thanks.
Update: From ios10,
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction;
From ios7 and Later UITextView has the delegate method:
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange *NS_DEPRECATED_IOS(7_0, 10_0, "Use textView:shouldInteractWithURL:inRange:forInteractionType: instead");*
to intercept the clicks to links. And this is the best way to do it.
For ios6 and earlier a nice way to do this is to by subclassing UIApplication and overwriting the -(BOOL)openURL:(NSURL *)url
#interface MyApplication : UIApplication {
}
#end
#implementation MyApplication
-(BOOL)openURL:(NSURL *)url{
if ([self.delegate openURL:url])
return YES;
else
return [super openURL:url];
}
#end
You will need to implement openURL: in your delegate.
Now, to have the application start with your new subclass of UIApplication, locate the file main.m in your project. In this small file that bootstraps your app, there is usually this line:
int retVal = UIApplicationMain(argc, argv, nil, nil);
The third parameter is the class name for your application. So, replacing this line for:
int retVal = UIApplicationMain(argc, argv, #"MyApplication", nil);
This did the trick for me.
In iOS 7 or Later
You can use the following UITextView delegate Method:
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange
The text view calls this method if the user taps or long-presses the URL link. Implementation of this method is optional. By default, the text view opens the application responsible for handling the URL type and passes it the URL. You can use this method to trigger an alternative action, such as displaying the web content at the URL in a web view within the current application.
Important:
Links in text views are interactive only if the text view is
selectable but noneditable. That is, if the value of the UITextView
the selectable property is YES and the isEditable property is NO.
With Swift 5 and iOS 12, you can use one of the three following patterns in order to interact with links in a UITextView.
#1. Using UITextView's dataDetectorTypes property.
The simplest way to interact with phone numbers, urls or addresses in a UITextView is to use dataDetectorTypes property. The sample code below shows how to implement it. With this code, when the user taps on the phone number, a UIAlertController pops up.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let textView = UITextView()
textView.text = "Phone number: +33687654321"
textView.isUserInteractionEnabled = true
textView.isEditable = false
textView.isSelectable = true
textView.dataDetectorTypes = [.phoneNumber]
textView.isScrollEnabled = false
textView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(textView)
textView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
textView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
textView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor).isActive = true
}
}
#2. Using UITextViewDelegate's textView(_:shouldInteractWith:in:interaction:) method
If you want to perform some custom action instead of making a UIAlertController pop up when you tap on a phone number while using dataDetectorTypes, you have to make your UIViewController conform to UITextViewDelegate protocol and implement textView(_:shouldInteractWith:in:interaction:). The code below shows how to implement it:
import UIKit
class ViewController: UIViewController, UITextViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let textView = UITextView()
textView.delegate = self
textView.text = "Phone number: +33687654321"
textView.isUserInteractionEnabled = true
textView.isEditable = false
textView.isSelectable = true
textView.dataDetectorTypes = [.phoneNumber]
textView.isScrollEnabled = false
textView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(textView)
textView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
textView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
textView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor).isActive = true
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
/* perform your own custom actions here */
print(URL) // prints: "tel:+33687654321"
return false // return true if you also want UIAlertController to pop up
}
}
#3. Using NSAttributedString and NSAttributedString.Key.link
As an alternative, you can use NSAttributedString and set a URL for its NSAttributedString.Key.link attribute.The sample code below shows a possible implementation of it. With this code, when user taps on the attributed string, a UIAlertController pops up.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let attributedString = NSMutableAttributedString(string: "Contact: ")
let phoneUrl = NSURL(string: "tel:+33687654321")! // "telprompt://+33687654321" also works
let attributes = [NSAttributedString.Key.link: phoneUrl]
let phoneAttributedString = NSAttributedString(string: "phone number", attributes: attributes)
attributedString.append(phoneAttributedString)
let textView = UITextView()
textView.attributedText = attributedString
textView.isUserInteractionEnabled = true
textView.isEditable = false
textView.isSelectable = true
textView.isScrollEnabled = false
textView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(textView)
textView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
textView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
textView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor).isActive = true
}
}
For Swift 3
textView.delegate = self
extension MyTextView: UITextViewDelegate {
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
GCITracking.sharedInstance.track(externalLink: URL)
return true
}
}
or if target is >= IOS 10
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool
Swift version:
Your standard UITextView setup should look something like this, don't forget the delegate and dataDetectorTypes.
var textView = UITextView(x: 10, y: 10, width: CardWidth - 20, height: placeholderHeight) //This is my custom initializer
textView.text = "dsfadsaf www.google.com"
textView.selectable = true
textView.dataDetectorTypes = UIDataDetectorTypes.Link
textView.delegate = self
addSubview(textView)
After your class ends add this piece:
class myVC: UIViewController {
//viewdidload and other stuff here
}
extension MainCard: UITextViewDelegate {
func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
//Do your stuff over here
var webViewController = SVModalWebViewController(URL: URL)
view.presentViewController(webViewController, animated: true, completion: nil)
return false
}
}
Swift 4:
1) Create the following class (subclassed UITextView):
import Foundation
protocol QuickDetectLinkTextViewDelegate: class {
func tappedLink()
}
class QuickDetectLinkTextView: UITextView {
var linkDetectDelegate: QuickDetectLinkTextViewDelegate?
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let glyphIndex: Int? = layoutManager.glyphIndex(for: point, in: textContainer, fractionOfDistanceThroughGlyph: nil)
let index: Int? = layoutManager.characterIndexForGlyph(at: glyphIndex ?? 0)
if let characterIndex = index {
if characterIndex < textStorage.length {
if textStorage.attribute(NSLinkAttributeName, at: characterIndex, effectiveRange: nil) != nil {
linkDetectDelegate?.tappedLink()
return self
}
}
}
return nil
}
}
2) Wherever you set up your textview, do this:
//init, viewDidLoad, etc
textView.linkDetectDelegate = self
//outlet
#IBOutlet weak var textView: QuickDetectLinkTextView!
//change ClassName to your class
extension ClassName: QuickDetectLinkTextViewDelegate {
func tappedLink() {
print("Tapped link, do something")
}
}
If you're using storyboard, make sure your textview looks like this in the right pane identity inspector:
Voila! Now you get the link tap immediately instead of when the URL shouldInteractWith URL method
application:handleOpenURL: is called when another app opens your app by opening a URL with a scheme your app supports. It's not called when your app begins opening a URL.
I think the only way to do what Vladimir wants is to use a UIWebView instead of a UITextView. Make your view controller implement UIWebViewDelegate, set the UIWebView's delegate to the view controller, and in the view controller implement webView:shouldStartLoadWithRequest:navigationType: to open [request URL] in a view instead of quitting your app and opening it in Mobile Safari.
I haven't tried that myself but you can try to implement application:handleOpenURL: method in your application delegate - it looks like all openURL request pass through this callback.
Not sure how you would intercept the detected data link, or what type of function you need to run. But you may be able to utilize the didBeginEditing TextField method to run a test/scan through the textfield if you know what your looking for..such as comparing text strings that meet ###-###-#### format, or begin with "www." to grab those fields, but you would need to write a little code to sniff through the textfields string, reconize what you need, and then extract it for your function's use. I don't think this would be that difficult, once you narrowed down exactly what it is that you wanted and then focussed your if() statement filters down to very specific matching pattern of what you needed.
Of couse this implies that the user is going to touch the textbox in order to activate the didBeginEditing(). If that is not the type of user interaction you were looking for you could just use a trigger Timer, that starts on ViewDidAppear() or other based on need and runs through the textfields string, then at the end of you run through the textfield string methods that you built, you just turn the Timer back off.
I am attempting to have a UIDatePicker come up as a keyboard when the user hits a UIButton. I was able to get it to work with a textfield, but I don't like how the cursor is visible and the user could enter in any text if they had an external keyboard. Here is my code:
#IBAction func dateFieldStart(sender: UITextField) {
var datePickerStartView : UIDatePicker = UIDatePicker()
datePickerStartView.datePickerMode = UIDatePickerMode.Time
sender.inputView = datePickerStartView // error when sender is UIButton
}
I tried changing the sender to UIButton but it gave this error on the line that is marked above:
Cannot assign to 'inputView' in 'sender'
I have tried researching it and no one else seems to have had a problem with it. Anyone know how to trigger a UIDatePicker inputView using a UIButton or anything that might work better that the user cannot type into? Thanks!
This is years after the original question, but for anyone who may be looking for solution to this you can subclass UIButton and provide a getter and setter for the inputView property. Be sure to call becomeFirstResponder in the setter and override canBecomeFirstResponder. For example:
class MyButton: UIButton {
var myView: UIView? = UIView()
var toolBarView: UIView? = UIView()
override var inputView: UIView? {
get {
myView
}
set {
myView = newValue
becomeFirstResponder()
}
}
override var inputAccessoryView: UIView? {
get {
toolBarView
}
set {
toolBarView = newValue
}
}
override var canBecomeFirstResponder: Bool {
true
}
}
let tempInput = UITextField( frame:CGRect.zero )
tempInput.inputView = self.myPickerView // Your picker
self.view.addSubview( tempInput )
tempInput.becomeFirstResponder()
It's a good idea to keep a reference to tempInput so you can clean-up on close
I wanted to do the same thing, I ended up just overlaying a UITextField over the button and using the inputView of that instead.
Tip: set tintColor of the UITextField to UIColor.clearColor() to hide the cursor.
You can create a view for the picker off screen view and move it on screen when you need it. Here's another post on this.