I've created a custom info button that I want to put inside a regular UILabel.
The idea is to have the screen say "Tap the (BUTTON HERE) for more information". Is there a way to do this without creating two UILabels? And if creating 2 labels is the only way, how can I put everything on one line?
I tried to do (button) inside the label.text but that shows the button's properties instead of placing the button. I also tried label.addSubview(button) which works but adds the button in the wrong place.
The best way to do this is using a UITextView with an NSAttributedString, where one of the attributes is your link.
let textView = UITextView()
textView.delegate = self
// These allow the link to be tapped
textView.isEditable = false
textView.isScrollEnabled = false
// Removes padding that UITextView uses, making it look more like a UILabel
textView.textContainer.lineFragmentPadding = 0.0
textView.textContainerInset = .zero
Then for the NSAttributedString
let text = "Tap HERE for more information"
let linkText = "HERE"
// Get range for tappable section
let linkRange = (text as NSString).range(of: linkText)
// Styling
let attributes: [NSAttributedString.Key: Any] = [
.foregroundColor: UIColor.black
]
// Actual Link!
let linkTextAttributes: [NSAttributedString.Key: Any] = [
.underlineStyle: NSUnderlineStyle.single.rawValue,
.link: "https://www.example.com" //The link you want to link to
]
let attributedString = NSMutableAttributedString(string: text, attributes: attributes)
attributedString.addAttributes(linkTextAttributes, range: linkRange)
Then use these UITextView delegate functions
// Removes a lot of the actions when user selects text
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
// Handle the user tapping the link however you like here
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
viewModel.urlTapped(URL)
return false
}
Related
I have a Text view thal look like this:
class StudyText: UITextView, UITextViewDelegate {
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
print(URL)
return false
}
override var canBecomeFirstResponder: Bool {
return false
}
}
and this is the struct:
struct ClickableText: UIViewRepresentable {
#Binding var text: NSMutableAttributedString
func makeUIView(context: Context) -> StudyText {
let view = StudyText()
view.dataDetectorTypes = .all
view.isEditable = false
view.isSelectable = true
view.delegate = view
view.isUserInteractionEnabled = true
return view
}
func updateUIView(_ uiView: StudyText, context: Context) {
uiView.attributedText = text
}
}
And I am using the attributed links.
Every solution I tried doesn't make the links respond to a quick tap. immediately. It takes a bit of delay until the print statement is presented.
I tried this:
view.delaysContentTouches = false
And I tried this:
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tappedTextView(tapGesture:)))
self.addGestureRecognizer(tapRecognizer)
#objc func tappedTextView(tapGesture: UIGestureRecognizer) {
let textView = tapGesture.view as! UITextView
let tapLocation = tapGesture.location(in: textView)
let textPosition = textView.closestPosition(to: tapLocation)
let attr = textView.textStyling(at: textPosition!, in: .forward)!
if let url: URL = attr[NSAttributedString.Key(rawValue: NSAttributedString.Key.link.rawValue)] as? URL {
print("clicking here: \(url)")
}
}
But none of them worked. It always responds with a delay
How can I fix this?
UITextView responds to both single tap gestures (which let you follow a link) and double tap gestures (which let you select text). Immediately after you tap the link once, it's unclear whether you've completed your gesture or whether a second tap is coming. Only after a short delay with no second tap can it be sure that you were in fact doing a single tap at which point textView(_:shouldInteractWith:in:interaction:) is called.
Unfortunately there's no standard way to make UITextView allow following links without allowing text selection. You might be able to hunt through the gesture recognizers registered on the view and find the one responsible for recognizing double taps and disable it, but doing so could have unintended side effects.
I'm using iOS 11 and Swift 4.
I am trying to programmatically generate a link inside a UITextView.
All the attributes work (i.e. color, size, range etc.) - but unfortunately, the link does not work. Can anybody tell me why?
Here is my code:
#IBOutlet weak var textView: UITextView!
// Setting the attributes
let linkAttributes = [
NSAttributedStringKey.link: URL(string: "https://www.apple.com")!,
NSAttributedStringKey.font: UIFont(name: "Helvetica", size: 18.0)!,
NSAttributedStringKey.foregroundColor: UIColor.blue
] as [NSAttributedStringKey : Any]
let attributedString = NSMutableAttributedString(string: "Just click here to do stuff...")
// Set the 'click here' substring to be the link
attributedString.setAttributes(linkAttributes, range: NSMakeRange(5, 10))
self.textView.delegate = self
self.textView.attributedText = attributedString
Again, the link seems correct, but clicking it does not work.
Here is the screenshot:
You should enable isSelectable and disable isEditable for your text view.
textView.isSelectable = true
textView.isEditable = false
You can also set up these behaviors inside Interface Builder in the Behavior section of the text view's Attributes inspector:
In my application I created an extension to UILabel that contains the following method:
func formatTextInLabel() {
let text = self.text
let font = UIFont(name: fontName, size: 16.0)
let titleDict: NSDictionary = [NSFontAttributeName: font!]
// This will give me an attributedString with the desired font
let attributedString = NSMutableAttributedString(string: text!, attributes: titleDict as! [String : AnyObject])
let regex = try? NSRegularExpression(pattern: "#(\\w+)", options: [])
let matches = regex!.matches(in: text!, options: [], range: NSMakeRange(0, text!.characters.count))
for match in matches {
let matchRange = match.rangeAt(0)
let titleDict: NSDictionary = [NSForegroundColorAttributeName: orangeColor]
attributedString.addAttributes(titleDict as! [String : AnyObject], range: matchRange)
}
self.attributedText = attributedString
}
and it highlights all #hashtags in my chosen color. But they are not clickable, I found some post on SO that says it's impossible to achieve: How to detect and make hyperlinks/mentions/hashtags clickable in UILabel? but it seems to be outdated (hopefully). Can you give me a hint how can I e.g. display in the console the name of selected hashtag?
One more thing to add - I'm aware of some pods like ActiveLabel or similar, but I tried them and had problems with constraints in my case, so I want to achieve this effect from my own code.
Try adding a UITapGestureRecognizer to the label:
let tap = UITapGestureRecognizer(target: self, action: #selector(handleLabelTap(_:)))
label.addGestureRecognizer(tap)
func handleLabelTap(_ sender: UITapGestureRecognizer) {
// Do something here for when the user taps on the label
}
For that to work, you probably have to set isUserInteractionEnabled to true on for the label. I just did a test in Xcode and it worked. So:
label.isUserInteractionEnabled = true
EDIT:
If you want to determine which hashtag has been tapped, at the top of your class declaration, before it's initialized create properties for an empty UILabel array, and an empty UITapGestureRecognizer array like this:
var labels:[UILabel] = [UILabel]()
var labelTaps:[UITapGestureRecognizer] = [UITapGestureRecognizer]()
Then when you create each label append the label and the tap gesture to each respective array
let label = UILabel()
// Set all label properties as you wish
// Make sure to add
label.isUserInteractionEnabled = true
// Create the tap gesture
let tap = UITapGestureRecognizer(target: self, action: #selector(handleLabelTap(_:)))
label.addGestureRecognizer(tap)
labels.append(label)
labelTaps.append(tap)
Then when a user taps on one of the hash-tags, you can get the index of which tap gesture was sent to the method, and then use that to figure out which label it is because it would have the same index in the 'labels' array as it's respective gesture recognizer would have in the 'labelTaps' array. Here is how you do that:
func handleLabelTap(_ sender: UITapGestureRecognizer) {
if let index = labelTaps.index(of: sender) {
let tappedLabel:UILabel = labels[index]
// Now you can do whatever you want
print("User tapped label with hash-tag: \(tappedLabel.text)")
}
}
You can't make links clickable in a UILabel, but you can in a UITextField or UITextView. Those allow adding clickable links.
If the link is displayed as a link, like http://address#domain.com then you can simply turn on link detection. If you want the link to display an arbitrary string like "click here" and make that string clickable you'll need to install an attributed string into the text field/text view.
What type of links are these, and what do you want it to look like in the text?
I need to make "Terms of Use" in the string below blue and have it respond to an click. I don't want to use a button so how can I do this in a UILabel?
String: By tapping Next and continuing you agree to the Terms of Use
Code I tried but failed just got text color change:
var myMutableString = NSMutableAttributedString()
myMutableString = NSMutableAttributedString(string: self.termsLabel.text! as String, attributes: [NSFontAttributeName:UIFont(name: self.termsLabel.font.fontName, size: self.termsLabel.font.pointSize)!])
myMutableString.addAttribute(NSForegroundColorAttributeName, value: UIColor.blue, range: NSRange(location:self.termsLabel.text!.characters.count - 12,length:12))
self.termsLabel.attributedText = myMutableString
Don't use a UILabel. Use a UITextField or UITextView, and install attributed text into it, including a link. Then set up the delegate methods to detect & respond to links. You need to implement the UITextViewDelegate method
func textView(UITextView,
shouldInteractWith: URL,
in: NSRange,
interaction: UITextItemInteraction)
One of the easiest ways to configure your link is to load the contents of an RTF file into the field.
I have a demo project on GitHub called DatesInSwift that creates clickable links that trigger a custom URL that invokes the URL in the app.
That project uses an extension to UITextView that has adds #IBInspectable property RTF_Filename to UITextView fields. All you have to do is to set the RTF_Filename property of your text view to the filename of the RTF file that you need to load.
It looks like my project was written for Swift 2, and uses the old version of the UITextViewDelegate method, which was called
func textView(_ textView: UITextView,
shouldInteractWithURL URL: Foundation.URL,
inRange characterRange: NSRange) -> Bool
I came to the conclusion that i couldn't get exactly what I wanted so I threw the text in a button and added this code
let mainText = "By tapping Next and continuing you agree to the Terms Of Use"
let attributeText = "Terms Of Use"
let range = (mainText as NSString).range(of: attributeText)
let attributedString = NSMutableAttributedString(string:mainText)
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.blue, range: range)
self.termsLabel.attributedText = attributedString
let tapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.goToTOSView))
tapGesture.numberOfTapsRequired = 1
self.termsLabel.isUserInteractionEnabled = true
self.termsLabel.addGestureRecognizer(tapGesture)
I'm trying to make separate pieces of text UILabels clickable. What I'm looking for is commonly known as a hyperlink in web development.
Link 1
Link 2
Link 3
Each a tag is its own UILabel, and it would ideally open Safari to the specified href when the text between the tags is clicked.
I've found a bevy of resources on how to do this sort of thing in Objective-C, but they all seem unnecessarily complicated and don't translate well to Swift (they fit an Objective-C organizational structure that doesn't work well in Swift and goes against the recommended way of using the language).
Here are a few:
How to add hyperlink in iPhone app?
How to make a clickable link inside a NSTextField and Cocoa
Text as Hyperlink in Objective-C
If I had a 3 UILabels,
Item 1
Item 2
Item 3
then what would be the best "Swift-y" way to make each item open to a different URL in Safari?
I could create separate buttons for each, but the UILabels are programmatically populated, so I was thinking that making the text respond to taps might be a better option.
Swift 3
I created a LinkUILabel class in github:
https://github.com/jorgecsan/LinkUILabel
With this you only need add the url inspectable as the shows the image:
or assign the url variable programmatically:
linkUILabel.url = "www.example.com"
If you want to implement by your self also I found that solution!:)
using:
// This is the label
#IBOutlet weak var label: UILabel!
override func loadView() {
super.loadView()
// This is the key
let tap = UITapGestureRecognizer(target: self, action: #selector(self.onClicLabel(sender:)))
label.isUserInteractionEnabled = true
label.addGestureRecognizer(tap)
}
// And that's the function :)
func onClicLabel(sender:UITapGestureRecognizer) {
openUrl("http://www.google.com")
}
func openUrl(urlString:String!) {
let url = URL(string: urlString)!
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(url)
}
}
Hope it helps!:)
The One approach would be something like the following.
The assumptions are:
self.urls is a string array containing the urls associated with each UILabel.
Each UILabel tag has been set to the corresponding index in the array
labelTapped: is set as the touchUpInside handler for the labels.
import Foundation
import UIKit
class urltest {
var urls:[String]
init() {
self.urls=[String]() // Load URLs into here
}
#IBAction func labelTapped(sender:UILabel!) {
let urlIndex=sender.tag;
if (urlIndex >= 0 && urlIndex < self.urls.count) {
self.openUrl(self.urls[urlIndex]);
}
}
func openUrl(url:String!) {
let targetURL=NSURL.URLWithString(url)
let application=UIApplication.sharedApplication()
application.openURL(targetURL);
}
}
Hyperlink via UITextView
var termsConditionsTextView: UITextView = {
let view = UITextView()
view.backgroundColor = .clear
view.textAlignment = .left
let firstTitleString = "By registering for THIS_APP I agree with the "
let secondTitleString = "Terms & Conditions"
let finishTitleString = firstTitleString + secondTitleString
let attributedString = NSMutableAttributedString(string: finishTitleString)
attributedString.addAttribute(.link, value: "https://stackoverflow.com", range: NSRange(location: firstTitleString.count, length: secondTitleString.count))
view.attributedText = attributedString
view.textContainerInset = .zero
view.linkTextAttributes = [
.foregroundColor: UIColor.blue,
.underlineStyle: NSUnderlineStyle.single.isEmpty
]
view.font = view.font = UIFont(name: "YOUR_FONT_NAME", size: 16)
view.textColor = UIColor.black
return view }()