I want to make the attributed string action in swift. I want append my custom variable to table view cell data.
I added colour and line also for that string, now i want to add the link for that added attributed string.
Please find my code with the following.
I add this code into cell for row at Index path.
let value = NSMutableAttributedString(string: " tap here.", attributes:[NSAttributedStringKey.link: URL(string: "http://www.google.com")!]).withTextColor(UIColor.disSatifyclr).withUnderlineColor(UIColor.disSatifyclr).withUnderlineStyle(.styleSingle)
if (cell.desclbl.text?.contains("more about donating, "))! {
let description = descriptions[indexPath.section][indexPath.row]
let attributedString = NSMutableAttributedString(string: description)
attributedString.append(value)
cell.desclbl.attributedText = attributedString
}
Here you can handle action using UITextView instead of UILabel on tap of string
class ViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
textView.attributedText = prepareLink()
textView.tintColor = UIColor.black
textView.isSelectable = true
textView.isEditable = false
textView.delegate = self
}
func prepareLink() -> NSMutableAttributedString {
let formattedString = NSMutableAttributedString(string: " tap here ")
let linkAttribute = [
NSAttributedString.Key.foregroundColor: UIColor.green,
NSAttributedString.Key.underlineColor: UIColor.green,
NSAttributedString.Key.underlineStyle: 1,
NSAttributedString.Key.link: URL(string: "http://www.google.com")!
] as [NSAttributedString.Key : Any]
formattedString.addAttributes(linkAttribute, range: NSMakeRange(0, formattedString.length))
return formattedString
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
if (URL.absoluteString == "http://www.google.com") {
// Do Something
}
return true
}
}
Note: If you have to continue with UILabel then you have to implement UITapGesture to handle action.
Related
I found this question: I'm implementing a hashtag recognizer in UITextView but it never highlights two words with the same beginning?. By doing that I want to check the user's run time input text on the text view and each time user types a #hashtag, It will automatically highlight after adding a comma.
Code:-
import UIKit
class ViewController: UIViewController,UITextViewDelegate {
#IBOutlet weak var txtView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
txtView.delegate = self
}
func textViewDidChange(_ textView: UITextView) {
textView.resolveHashTags()
}
}
extension UITextView {
func resolveHashTags() {
let nsText = NSString(string: self.text)
let words = nsText.components(separatedBy: CharacterSet(charactersIn: "#ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_").inverted)
let attrString = NSMutableAttributedString()
attrString.setAttributedString(self.attributedText)
for word in words {
if word.count < 3 {
continue
}
if word.hasPrefix("#") {
let matchRange:NSRange = nsText.range(of: word as String)
let stringifiedWord = word.dropFirst()
if let firstChar = stringifiedWord.unicodeScalars.first, NSCharacterSet.decimalDigits.contains(firstChar) {
} else {
attrString.addAttribute(NSAttributedString.Key.link, value: "hash:\(stringifiedWord)", range: matchRange)
}
}
}
self.attributedText = attrString
}
}
Current ScreenShot:-
Question:- Can someone please explain to me how to highlight the text after adding a comma? I've tried to implement by above code but no results yet.
Any help would be greatly appreciated.
Thanks in advance.
How can I get the whole text in the blue rectangle, inside UITextView, if user select a portion like this:
My current code is to get only the selected text:
if let range = textView.selectedTextRange {
let selectedText = textView.text(in: range) {
//selectedText
}
}
You have to set your view controller as the text view delegate UITextViewDelegate
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
}
Implement textViewDidChangeSelection method
func textViewDidChangeSelection(_ textView: UITextView)
In this function you can get the UITextPosition of the start and end of the lines of the selection, then create a new range and retrieve the selected text:
func textViewDidChangeSelection(_ textView: UITextView) {
guard let selectedTextRange = textView.selectedTextRange,
let start = textView.tokenizer
.position(from: selectedTextRange.start, toBoundary: .line, inDirection: .backward),
let end = textView.tokenizer
.position(from: selectedTextRange.end, toBoundary: .line, inDirection: .forward),
let range = textView.textRange(from: start, to: end),
let text = textView.text(in: range)
else { return }
print(text)
}
Add this extension helpers to your project:
extension UITextDirection {
static let forward: Self = .init(rawValue: 0)
static let backward: Self = .init(rawValue: 1)
}
To match a Styleguide I have to add kerning to a textfield, both placeholder and value itself.
With UIKit I was able to do so with:
class MyTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
// ...
self.attributedPlaceholder = NSAttributedString(string: self.placeholder ?? "", attributes: [
//...
NSAttributedString.Key.kern: 0.3
])
self.attributedText = NSAttributedString(string: "", attributes: [
// ...
NSAttributedString.Key.kern: 0.3
])
}
}
In SwiftUI, I could figure out that I could apply a kerning effect to a Text element like so:
Text("My label with kerning")
.kerning(0.7)
Unfortunately, I could not find a way to apply a kerning style to neither a TextField's value nor placeholder. Any ideas on this one? Thanks in advance
There is a simple tutorial on HackingwithSwift that shows how to implement a UITextView. It can easily be adapted for UITextField.
Here is a quick example showing how to use UIViewRepresentable for you UITextField. Setting the kerning on both the text and the placeholder.
struct ContentView: View {
#State var text = ""
var body: some View {
MyTextField(text: $text, placeholder: "Placeholder")
}
}
struct MyTextField: UIViewRepresentable {
#Binding var text: String
var placeholder: String
func makeUIView(context: Context) -> UITextField {
return UITextField()
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.attributedPlaceholder = NSAttributedString(string: self.placeholder, attributes: [
NSAttributedString.Key.kern: 0.3
])
uiView.attributedText = NSAttributedString(string: self.text, attributes: [
NSAttributedString.Key.kern: 0.3
])
}
}
Update
The above doesn't work for setting the kerning on the attributedText. Borrowing from the fantastic work done by Costantino Pistagna in his medium article we need to do a little more work.
Firstly we need to create a wrapped version of the UITextField that allows us access to the delegate methods.
class WrappableTextField: UITextField, UITextFieldDelegate {
var textFieldChangedHandler: ((String)->Void)?
var onCommitHandler: (()->Void)?
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let nextField = textField.superview?.superview?.viewWithTag(textField.tag + 1) as? UITextField {
nextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return false
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let currentValue = textField.text as NSString? {
let proposedValue = currentValue.replacingCharacters(in: range, with: string)
print(proposedValue)
self.attributedText = NSAttributedString(string: currentValue as String, attributes: [
NSAttributedString.Key.kern: 10
])
textFieldChangedHandler?(proposedValue as String)
}
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
onCommitHandler?()
}
}
As the shouldChangeCharactersIn delegate method will get called every time the text changes, we should use that to update the attributedText value. I tried using first the proposedVale but it would double up the characters, it works as expected if we use the currentValue
Now we can use the WrappedTextField in the UIViewRepresentable.
struct SATextField: UIViewRepresentable {
private let tmpView = WrappableTextField()
//var exposed to SwiftUI object init
var tag:Int = 0
var placeholder:String?
var changeHandler:((String)->Void)?
var onCommitHandler:(()->Void)?
func makeUIView(context: UIViewRepresentableContext<SATextField>) -> WrappableTextField {
tmpView.tag = tag
tmpView.delegate = tmpView
tmpView.placeholder = placeholder
tmpView.attributedPlaceholder = NSAttributedString(string: self.placeholder ?? "", attributes: [
NSAttributedString.Key.kern: 10
])
tmpView.onCommitHandler = onCommitHandler
tmpView.textFieldChangedHandler = changeHandler
return tmpView
}
func updateUIView(_ uiView: WrappableTextField, context: UIViewRepresentableContext<SATextField>) {
uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
uiView.setContentHuggingPriority(.defaultLow, for: .horizontal)
}
}
We set the attributed text for the placeholder in the makeUIView. The placeholder text is not being updated so we don't need to worry about changing that.
And here is how we use it:
struct ContentView: View {
#State var text = ""
var body: some View {
SATextField(tag: 0, placeholder: "Placeholder", changeHandler: { (newText) in
self.text = newText
}) {
// do something when the editing of this textfield ends
}
}
}
I need to make part of a text interactive, this is a screenshot of what I made so far, which is good, but the problems are
1- it requires a long tap
2- and force me to have the text selectable
any idea about how to solve this?
import UIKit
class ViewController: UIViewController, UITextViewDelegate
{
#IBOutlet weak var textView: UITextView!
override func viewDidLoad()
{
super.viewDidLoad()
let attributedString = NSMutableAttributedString(string: "text with a link", attributes: nil)
attributedString.setSubstringAsLink(substring: "link", linkURL: "CUSTOM://WHATEVER")
let linkAttributes: [String : AnyObject] = [NSForegroundColorAttributeName : UIColor.redColor(), NSUnderlineColorAttributeName : UIColor.redColor(), NSUnderlineStyleAttributeName : NSUnderlineStyle.StyleSingle.rawValue]
textView.linkTextAttributes = linkAttributes
textView.attributedText = attributedString
textView.selectable = true
textView.editable = false
textView.userInteractionEnabled = true
textView.delegate = self
}
func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool
{
if URL == "CUSTOM://WHATEVER"
{
print("????")
}
else
{
print("!!!!")
}
return true
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension NSMutableAttributedString
{
public func setSubstringAsLink(substring substring: String, linkURL: String) -> Bool
{
let range = self.mutableString.rangeOfString(substring)
if range.location != NSNotFound
{
self.addAttribute(NSLinkAttributeName, value: linkURL, range: range)
return true
}
return false
}
}
I don't know how to solve your specific issues but I guess you are better of subclassing UILabel instead of UITextView. Also, try to not re-invent the wheel, use TTAttributedLabel or similar libraries to do what you want.
I resolved this problem like below,
let attributedString = NSMutableAttributedString(string: "Press this link text")
attributedString.addAttribute(NSLinkAttributeName, value: "http://www.google.com", range: NSRange(location: 6, length: 14))
let linkAttributes = [
NSForegroundColorAttributeName: UIColor.greyishBrownColor(),
NSUnderlineColorAttributeName: UIColor.greyishBrownColor(),
NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue
]
agreementsTextView.delegate = self
agreementsTextView.attributedText = attributedString
agreementsTextView.linkTextAttributes = linkAttributes
// Delegate Method
func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
if URL.absoluteString == "http://www.google.com" || URL.relativePath == "http://www.google.com" {
print("Tapped")
}
return false
}
Range might be wrong, this should fix your problem
Im writing a desktop app in swift for mac 10.11 and I would like to add a link to the about page.
Very much like this: https://developer.apple.com/library/mac/qa/qa1487/_index.html
I haven't been able to find a good tutorial or reference.
Any help would be much appreciated
Swift 4, xCode 9
#IBDesignable
class HyperlinkTextField: NSTextField {
#IBInspectable var href: String = ""
override func resetCursorRects() {
discardCursorRects()
addCursorRect(self.bounds, cursor: NSCursor.pointingHand)
}
override func awakeFromNib() {
super.awakeFromNib()
// TODO: Fix this and get the hover click to work.
let attributes: [NSAttributedStringKey: Any] = [
NSAttributedStringKey.foregroundColor: NSColor.linkColor,
NSAttributedStringKey.underlineStyle: NSUnderlineStyle.styleSingle.rawValue as AnyObject
]
attributedStringValue = NSAttributedString(string: self.stringValue, attributes: attributes)
}
override func mouseDown(with theEvent: NSEvent) {
if let localHref = URL(string: href) {
NSWorkspace.shared.open(localHref)
}
}
}
Modified the existing answers to allow for a substring of the label's text to become underlined and blue, so you can do something like: This is the answer
// A text field that can contain a hyperlink within a range of characters in the text.
#IBDesignable
public class SubstringLinkedTextField: NSTextField {
// the URL that will be opened when the link is clicked.
public var link: String = ""
#available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'link' instead.")
#IBInspectable public var HREF: String {
get {
return self.link
}
set {
self.link = newValue
self.needsDisplay = true
}
}
// the substring within the field's text that will become an underlined link. if empty or no match found, the entire text will become the link.
public var linkText: String = ""
#available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'linkText' instead.")
#IBInspectable public var LinkText: String {
get {
return self.linkText
}
set {
self.linkText = newValue
self.needsDisplay = true
}
}
override public func awakeFromNib() {
super.awakeFromNib()
self.allowsEditingTextAttributes = true
self.isSelectable = true
let url = URL(string: self.link)
let attributes: [NSAttributedStringKey: AnyObject] = [
NSAttributedStringKey(rawValue: NSAttributedStringKey.link.rawValue): url as AnyObject
]
let attributedStr = NSMutableAttributedString(string: self.stringValue)
if self.linkText.count > 0 {
if let range = self.stringValue.indexOf(substring: self.linkText) {
attributedStr.setAttributes(attributes, range: range)
} else {
attributedStr.setAttributes(attributes, range: NSMakeRange(0, self.stringValue.count))
}
} else {
attributedStr.setAttributes(attributes, range: NSMakeRange(0, self.stringValue.count))
}
self.attributedStringValue = attributedStr
}
}
The easiest way is to subclass NSTextField to create a HyperlinkTextField. Below is an example:
First, let's add a HyperlinkTextField class to your project:
// HyperlinkTextField.swift
import Cocoa
#IBDesignable
class HyperlinkTextField: NSTextField {
#IBInspectable var href: String = ""
override func awakeFromNib() {
super.awakeFromNib()
let attributes: [String: AnyObject] = [
NSForegroundColorAttributeName: NSColor.blueColor(),
NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue
]
self.attributedStringValue = NSAttributedString(string: self.stringValue, attributes: attributes)
}
override func mouseDown(theEvent: NSEvent) {
NSWorkspace.sharedWorkspace().openURL(NSURL(string: self.href)!)
}
}
Next, in Interface Builder, drag a label from the Object library to your window.
Select that label, go to the menu View > Utilities > Show Identity Inspector (or press Cmd + Opt + 3) and change the class to HyperlinkTextField
Go to the Attributes Inspector (Cmd + Opt + 4) and set Href to the URL you want to visit.
The label shows black text in Interface Builder but everything will be fine when you run your app. Clicking on the label will open the link in your default browser.
One thing I couldn't achieve was to make the HyperlinkTextField shows up as blue and underlined in Interface Builder. Comments on how to do that are welcome.
Exactly what I needed. Here is the Swift3 version:
import Cocoa
#IBDesignable
class HyperTextField: NSTextField {
#IBInspectable var href: String = ""
override func awakeFromNib() {
super.awakeFromNib()
let attributes: [String: AnyObject] = [
NSForegroundColorAttributeName: NSColor.blue,
NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue as AnyObject
]
self.attributedStringValue = NSAttributedString(string: self.stringValue, attributes: attributes)
}
override func mouseDown(with event: NSEvent) {
NSWorkspace.shared().open(URL(string: self.href)!)
}
}
let link = NSTextField()
link.isBezeled = false
link.drawsBackground = false
link.isEditable = false
link.isSelectable = true
link.allowsEditingTextAttributes = true
let url = URL(string: "http://www.google.com")
let linkTextAttributes: [NSAttributedStringKey: Any] = [
NSAttributedStringKey.underlineStyle: NSUnderlineStyle.styleSingle.rawValue,
NSAttributedStringKey.foregroundColor: NSColor.blue,
NSAttributedStringKey.link: url as Any
]
let string = "whatever"
link.attributedStringValue = NSAttributedString(string: string, attributes: linkTextAttributes)
window.contentView?.addSubview(link)
swift 4.2
class HyperlinkTextField: NSTextField {
private var detector: NSDataDetector!
override func awakeFromNib() {
super.awakeFromNib()
detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
}
func setText(text: String ){
let matches = detector.matches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count))
for match in matches {
guard let range = Range(match.range, in: text) else { continue }
let url = text[range]
let attributedString = NSMutableAttributedString(string: text)
attributedString.addAttribute(.link, value: url, range: match.range)
self.attributedStringValue = attributedString
}
}
}