I am trying to design a UIlabel with clickable UIButton's inside like in the image below.
The idea here is that I am getting some text which contains some markers and when that marker appears I have to draw the button instead of the text inside the UILabel. These buttons should be clickable.
I have tried to accomplish it with AttributedText to UILabel but with attributed text, I am not able to give the extra spacing for the button also the corner radius and the border color don't look as good as they should be, but the bigger problem is that I am not able to get the correct position of the UILabel tap, I guess maybe because I am using a custom font. I tried this approach: https://samwize.com/2016/03/04/how-to-create-multiple-tappable-links-in-a-uilabel/ to get the tapped characters but its always a few characters off and when the text gets bigger it becomes more inaccurate.
I think there are two approaches to it
Create a UILabel and use attributed text to give the proper formatting and a UILabel tap gesture to handle and locate the position of the click. (I have tried this approach and it's not very successful)
Maybe create a custom view where we add text to the view and the buttons whenever the marker appears, I am not sure how to accomplish this but with approach, I believe we can get away with handling the
tap Gestures because UiButton already will give us the tap event.
class RQCommentaryLabel: UICrimsonLabel {
var commentaryTextProvider: CommentaryTextProvider?
private var tapGesture: UITapGestureRecognizer?
override init (fontType: FontType, textStyle: UIFont.TextStyle, fontWeight: FontWeight, text: String?) {
super.init(fontType: fontType, textStyle: textStyle, fontWeight: fontWeight, text: text)
tapGesture = UITapGestureRecognizer(target: self, action: #selector(tappedOnLabel(_ :)))
self.isUserInteractionEnabled = true
tapGesture?.numberOfTapsRequired = 1
self.addGestureRecognizer(tapGesture!)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func renderCommentryText(textProvider: CommentaryTextProvider) {
self.font = UIFont.systemFont(ofSize: UIFont.systemFontSize)
self.commentaryTextProvider = textProvider
let formattedtext = textProvider.commentaryRenderedText()
self.attributedText = formattedtext
}
#objc func tappedOnLabel(_ gesture: UITapGestureRecognizer) {
let dict = commentaryTextProvider?.commentaryData() ?? [:]
for(key, value) in dict {
let location = gesture.location(in: self)
if gesture.didTapAttributedTextInLabel(label: self, inRange: value, location: location) {
print("Tapped :\(key)")
}
}
}
UIGestureRecognizer Extension
extension UITapGestureRecognizer {
func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange, location: CGPoint) -> Bool {
// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: label.bounds.size)
let textStorage = NSTextStorage(attributedString: label.attributedText!)
// Configure layoutManager and textStorage
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
// Configure textContainer
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = label.lineBreakMode
textContainer.maximumNumberOfLines = label.numberOfLines
let indexOfCharacter = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return NSLocationInRange(indexOfCharacter, targetRange)
}
}
Helper class
class CommentaryTextProvider {
private let commentarytext: String
private var commentaryRanges = [String: NSRange]()
init(commentarytext: String) {
self.commentarytext = commentarytext
}
func commentaryRenderedText() -> NSAttributedString {
let matches = commentaySymbolMatches(text: commentarytext)
let removedString = commentarytext.replacingOccurrences(of: "[", with: " ").replacingOccurrences(of: "]", with: " ")
let attributedString = NSMutableAttributedString(string: removedString)
let shadow = NSShadow()
shadow.shadowColor = UIColor.systemGray
shadow.shadowOffset = CGSize(width: 1.0, height: 1.0)
shadow.shadowBlurRadius = 2.0
commentaryRanges = [:]
for match in matches {
attributedString.addAttributes([
.foregroundColor: UIColor.RQColors.switchColor,
.baselineOffset: 3,
.textEffect: NSAttributedString.TextEffectStyle.letterpressStyle,
.font: UIFont.fontfor(.englishNormal, weight: .bold, style: .body),
.shadow: shadow
], range: match.range)
let subStr = (attributedString.string as NSString).substring(with: match.range)
commentaryRanges[subStr] = match.range
}
return attributedString
}
func commentaryData() -> [String: NSRange] {
return commentaryRanges
}
private func commentaySymbolMatches(text: String) -> [NSTextCheckingResult] {
let pattern = "\\[[^\\[]*\\]"
let regex = try! NSRegularExpression(pattern: pattern)
return regex.matches(in: text, range: NSRange(text.startIndex..., in: text))
}
Any idea how I can achieve this behavior? Thanks
Related
I did the implementation in this in my ios app.but when base text given by, guard let text = self.lblTermsAndConditions.text else { return } contains double quotation func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool returns false.
For example when text given by lblTermsAndConditions is, By clicking \"Signup\",I agree to the Terms and Conditions & Privacy Policy and privacyPolicyRange = Terms and Conditions & Privacy Policy didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool returns false.
my code
#IBOutlet weak var privacyPolicyLabel: HuqLabel!{
didSet{
privacyPolicyLabel.text = "By clicking \"Signup\",I agree to the Terms and Conditions & Privacy Policy"
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.privacyPolicyLabel.isUserInteractionEnabled = true
let tapgesture = UITapGestureRecognizer(target: self, action: #selector(tappedOnLabel(_ :)))
tapgesture.numberOfTapsRequired = 1
tapgesture.numberOfTouchesRequired = 1
self.privacyPolicyLabel.addGestureRecognizer(tapgesture)
self.privacyPolicyLabel.isUserInteractionEnabled = true }
extension at the bottom of the viewcontroller
extension UITapGestureRecognizer {
func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
let textStorage = NSTextStorage(attributedString: label.attributedText!)
// Configure layoutManager and textStorage
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
// Configure textContainer
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = label.lineBreakMode
textContainer.maximumNumberOfLines = label.numberOfLines
let labelSize = label.bounds.size
textContainer.size = labelSize
// Find the tapped character location and compare it to the specified range
let locationOfTouchInLabel = self.location(in: label)
let textBoundingBox = layoutManager.usedRect(for: textContainer)
let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x,
y: locationOfTouchInLabel.y - textContainerOffset.y);
let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return NSLocationInRange(indexOfCharacter, targetRange)
}
}
I am using it like this
guard let text = self.privacyPolicyLabel.text else { return }
let privacyPolicyRange = (text as NSString).range(of: "Terms and Conditions & Privacy Policy")
if gesture.didTapAttributedTextInLabel(label: self.privacyPolicyLabel, inRange: privacyPolicyRange) {
print("clicked on terms and conditions")
}else{
print("false")
}
This prints false.
How can I fix this?
Edit:
my custom label
import UIKit
class HuqLabel: UILabel {
private var attributes = HuqStringAttributes.attributes(for: .bigTitle)
private var typograpyStyle: HuqTypographyStyle!
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
private func configure() {
adjustsFontForContentSizeCategory = true
numberOfLines = 0
}
// Must be called after setting the attributed text
func setNumberOfLines(_ numOfLines: Int, breakMode: NSLineBreakMode = .byTruncatingTail) {
numberOfLines = numOfLines
lineBreakMode = breakMode
}
func setText(_ text: String) {
self.text = text
setLetterSpacing()
}
func set(_ text: String = "", typographyStyle: HuqTypographyStyle, alignment: NSTextAlignment = .left) {
typograpyStyle = typographyStyle
self.text = text
accessibilityLabel = text
self.attributes = HuqStringAttributes.attributes(for: typographyStyle, alignment: alignment)
setLetterSpacing()
}
func setAttributesForStrikeThrough(_ text: String, typographyStyle: HuqTypographyStyle, alignment: NSTextAlignment = .left) {
self.text = text
accessibilityLabel = text
self.attributes = HuqStringAttributes.attributes(for: typographyStyle, alignment: alignment)
let attributedString = NSMutableAttributedString(string: text, attributes: self.attributes)
attributedString.addAttribute(NSAttributedString.Key.kern, value: letterSpacingForTypographyStyle(for: typographyStyle), range: NSRange(location: 0, length: text.count))
attributedString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: 1, range: NSMakeRange(0, attributedString.length))
attributedText = attributedString
}
func setLetterSpacing() {
let attributedString = NSMutableAttributedString(string: self.text ?? "", attributes: self.attributes)
attributedString.addAttribute(NSAttributedString.Key.kern, value: letterSpacingForTypographyStyle(for: typograpyStyle), range: NSRange(location: 0, length: self.text?.count ?? 0))
attributedText = attributedString
}
private func letterSpacingForTypographyStyle(for typographyStyle: HuqTypographyStyle) -> CGFloat {
switch typographyStyle {
case .bigTitle, .title, .bodyLight, .bodyWhite, .caption, .bodyBoldWhite, .bodyBoldBlue, .bodyBalck, .bodySmall, .titleBlue, .captionBlack, .bigTitleOrange, .captionMediumBlue:
return 0
}
}
func setText(_ text: String,
withBoldTextSections boldSections: [String], font: UIFont = FontFamily.Ubuntu.bold.font(size: 16),color:UIColor = ColorName.brownGrey.color) {
let attributes = HuqStringAttributes.attributes(for: .bodyLight, alignment: .center)
let attributedFullString = NSMutableAttributedString(string: text,
attributes: attributes)
boldSections.forEach { section in
let rangeOfSection = attributedFullString.mutableString.range(of: section)
attributedFullString.addAttributes([
NSAttributedString.Key.foregroundColor: color,
NSAttributedString.Key.font:font], range: rangeOfSection)
}
attributedText = attributedFullString
}
-
import Foundation
enum HuqTypographyStyle {
case bigTitle
case bigTitleOrange
case title
case titleBlue
case bodyLight
case bodyWhite
case bodyBalck
case caption
case captionBlack
case bodyBoldBlue
case bodyBoldWhite
case bodySmall
case captionMediumBlue
-
import Foundation
import UIKit
struct HuqFonts {
static func font(for typographyStyle: HuqTypographyStyle) -> UIFont {
switch typographyStyle {
case .bigTitle, .bigTitleOrange:
return ubuntuMedium(size: 30)
case .title, .titleBlue:
return ubuntuMedium(size: 18)
case .bodyLight, .bodyBalck:
return ubuntuRegular(size: 16)
case .bodyWhite:
return ubuntuRegular(size: 14)
case .caption, .captionBlack:
return ubuntuRegular(size: 12)
case .bodyBoldBlue, .bodyBoldWhite:
return ubuntuBold(size: 16)
case .bodySmall:
return ubuntuRegular(size: 10)
case .captionMediumBlue:
return ubuntuMedium(size: 12)
}
}
private static func ubuntuMedium(size: CGFloat) -> UIFont {
return FontFamily.Ubuntu.medium.font(size: size)
}
private static func ubuntuRegular(size: CGFloat) -> UIFont {
return FontFamily.Ubuntu.regular.font(size: size)
}
private static func ubuntuBold(size: CGFloat) -> UIFont {
return FontFamily.Ubuntu.bold.font(size: size)
}
}
import UIKit
struct HuqStringAttributes {
static func attributes(for typographyStyle: HuqTypographyStyle,
alignment: NSTextAlignment = .left) -> [NSAttributedString.Key: Any] {
var attributes: [NSAttributedString.Key: Any] = [:]
attributes[.font] = HuqFonts.font(for: typographyStyle).scaled
attributes[.foregroundColor] = colorsForTypographyStyles(for: typographyStyle).color
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = alignment
attributes[.paragraphStyle] = paragraphStyle
return attributes
}
private static func colorsForTypographyStyles(for typographyStyle: HuqTypographyStyle) -> ColorName {
switch typographyStyle {
case .bigTitle, .title, .bodyBalck, .captionBlack:
return ColorName.black
case .bodyLight ,.caption, .bodySmall:
return ColorName.brownGrey
case .bodyWhite, .bodyBoldWhite:
return ColorName.white
case .bodyBoldBlue, .titleBlue, .captionMediumBlue:
return ColorName.turquoiseBlue
case .bigTitleOrange:
return ColorName.bloodOrange
}
}
}
Add this line in viewDidLoad():
self.privacyPolicyLabel.lineBreakMode = .byWordWrapping
Quick testing seems that solves the issue.
EDIT
Give this a try. It uses a standard UILabel - all via code, no #IBOutlet connections - so just assign the custom class of a view controller to LabelLinkViewController. It's also using the UITapGestureRecognizer extension you posted in your question.
class LabelLinkViewController: UIViewController {
var privacyPolicyLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
privacyPolicyLabel.translatesAutoresizingMaskIntoConstraints = false
privacyPolicyLabel.backgroundColor = .yellow
view.addSubview(privacyPolicyLabel)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
privacyPolicyLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
privacyPolicyLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 60.0),
privacyPolicyLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -60.0),
])
self.privacyPolicyLabel.lineBreakMode = .byWordWrapping
self.privacyPolicyLabel.isUserInteractionEnabled = true
let tapgesture = UITapGestureRecognizer(target: self, action: #selector(tappedOnLabel(_ :)))
tapgesture.numberOfTapsRequired = 1
tapgesture.numberOfTouchesRequired = 1
self.privacyPolicyLabel.addGestureRecognizer(tapgesture)
self.privacyPolicyLabel.isUserInteractionEnabled = true
self.privacyPolicyLabel.text = "By clicking \"Signup\", I agree to the Terms and Conditions & Privacy Policy"
//self.privacyPolicyLabel.text = "By clicking Signup, I agree to the Terms and Conditions & Privacy Policy"
}
//MARK:- tappedOnLabel
#objc func tappedOnLabel(_ gesture: UITapGestureRecognizer) {
guard let text = self.privacyPolicyLabel.text else { return }
let privacyPolicyRange = (text as NSString).range(of: "Terms and Conditions & Privacy Policy")
if gesture.didTapAttributedTextInLabel(label: self.privacyPolicyLabel, inRange: privacyPolicyRange) {
print("clicked on terms and conditions")
}else{
print("false")
}
}
}
If that works for you, but it does not work when you use your HuqLabel class, then that's where the problem lies.
I want to change the color of a link within a UILabel. I've found loads of past questions on how to do this for a UITextView, and past questions with answers in Obj-C (but can't translate these to Swift as properties that did exist in Obj-c no longer do such as NSMutableAttributedString.linkTextAttribtues for example).
But I cannot find how to do this for a UILabel and in Swift 4.
For default NSAttributedString.Key.link color will be blue.
If you need custom colors for links you can set the attribute as NSAttributedString.Key.attachment instead of .link and set the foreground and underline colors like this:
let linkCustomAttributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14),
NSAttributedString.Key.foregroundColor: UIColor.red,
NSAttributedString.Key.underlineColor: UIColor.magenta,
NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue,
NSAttributedString.Key.attachment: URL(string: "https://www.google.com")] as [NSAttributedString.Key : Any]
If you need to handle touches on links you can use this custom label class:
import UIKit
public protocol UILabelTapableLinksDelegate: NSObjectProtocol {
func tapableLabel(_ label: UILabelTapableLinks, didTapUrl url: String, atRange range: NSRange)
}
public class UILabelTapableLinks: UILabel {
private var links: [String: NSRange] = [:]
private(set) var layoutManager = NSLayoutManager()
private(set) var textContainer = NSTextContainer(size: CGSize.zero)
private(set) var textStorage = NSTextStorage() {
didSet {
textStorage.addLayoutManager(layoutManager)
}
}
public weak var delegate: UILabelTapableLinksDelegate?
public override var attributedText: NSAttributedString? {
didSet {
if let attributedText = attributedText {
textStorage = NSTextStorage(attributedString: attributedText)
findLinksAndRange(attributeString: attributedText)
} else {
textStorage = NSTextStorage()
links = [:]
}
}
}
public override var lineBreakMode: NSLineBreakMode {
didSet {
textContainer.lineBreakMode = lineBreakMode
}
}
public override var numberOfLines: Int {
didSet {
textContainer.maximumNumberOfLines = numberOfLines
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
isUserInteractionEnabled = true
layoutManager.addTextContainer(textContainer)
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = lineBreakMode
textContainer.maximumNumberOfLines = numberOfLines
}
public override func layoutSubviews() {
super.layoutSubviews()
textContainer.size = bounds.size
}
private func findLinksAndRange(attributeString: NSAttributedString) {
links = [:]
let enumerationBlock: (Any?, NSRange, UnsafeMutablePointer<ObjCBool>) -> Void = { [weak self] value, range, isStop in
guard let strongSelf = self else { return }
if let value = value {
let stringValue = "\(value)"
strongSelf.links[stringValue] = range
}
}
attributeString.enumerateAttribute(.link, in: NSRange(0..<attributeString.length), options: [.longestEffectiveRangeNotRequired], using: enumerationBlock)
attributeString.enumerateAttribute(.attachment, in: NSRange(0..<attributeString.length), options: [.longestEffectiveRangeNotRequired], using: enumerationBlock)
}
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let locationOfTouch = touches.first?.location(in: self) else {
return
}
textContainer.size = bounds.size
let indexOfCharacter = layoutManager.glyphIndex(for: locationOfTouch, in: textContainer)
for (urlString, range) in links where NSLocationInRange(indexOfCharacter, range) {
delegate?.tapableLabel(self, didTapUrl: urlString, atRange: range)
return
}
}
}
Setup label in your code:
customLabel.attributedText = <<Your attributed text with custom links>>
customLabel.delegate = self
Implement delegate:
extension YourClass: UILabelTapableLinksDelegate {
func tapableLabel(_ label: UILabelTapableLinks, didTapUrl url: String, atRange range: NSRange) {
print("didTapUrl: ", url)
}
}
It is easier to use UITextView instead of UILabel and write something like:
textView.linkTextAttributes = [
.foregroundColor: UIColor.red,
.underlineColor: UIColor.red,
.underlineStyle: NSUnderlineStyle.single.rawValue
]
The answers above are correct but setting .attachment as the url doesn't open the url, at least not for me (using iOS 13).
The color of .link is not affected by the .foregroundColor in NSAttributedString, but from the tintColor of your UITextView
let urlAttributes: [NSAttributedString.Key: Any] = [
.link: URL(string:"https://google.com"),
.foregroundColor: textColor,
.underlineColor: textColor,
.underlineStyle: NSUnderlineStyle.single.rawValue
.underlinColor: UIColor.green
]
textView.tintColor = UIColor.green
textView.attributed = urlAttributes
should set the text and the underline of the link to green
I have a custom UILabel class that allows me to attach tapGestureRecognizers to specific parts of a string. Now, I'm trying to animate the text of the UILabel to print one character at a time. My code seems to be adding one character at a time to TextStorage, but the text shows up all at the same time, without animation. Is adding one character at a time to TextStorage the wrong approach? Thanks
class CustomLabel: UILabel {
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
var textStorage = NSTextStorage() {
didSet {
textStorage.addLayoutManager(layoutManager)
}
}
var onCharacterTapped: ((_ label: UILabel, _ characterIndex: Int) -> Void)?
let tapGesture = UITapGestureRecognizer()
override var attributedText: NSAttributedString? {
didSet {
if let attributedText = attributedText {
DispatchQueue.main.async {
for (index, char) in attributedText.string.characters.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5 * Double(index)) {
print("character ch is: \(char) at index: \(index)")
let text = NSAttributedString(string: String(char))
self.textStorage = NSTextStorage(attributedString: text)
}
}
}
} else {
textStorage = NSTextStorage()
}
}
}
}
I have an attributed string set to UILabel with multiple underlines , colors like below image
and I know How to setup a tap gesture for whole label (with enabling user interaction) and below is my code for what I have done including setting up underline and setting up font colors for multiple ranges.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var mylabel: UILabel!
var theString = "I have agree with the terms and conditions and privacy policy"
override func viewDidLoad() {
super.viewDidLoad()
mylabel.text = theString
let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.printme))
mylabel.addGestureRecognizer(tap)
setUnderline(theText: theString)
// Do any additional setup after loading the view, typically from a nib.
}
func printme() {
print("print this")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func setUnderline(theText : String) {
//set up underline
let textRange1 = NSMakeRange(22, 19)
let textRange2 = NSMakeRange(47, (theText.characters.count-47))
let attributedText = NSMutableAttributedString(string : theText)
attributedText.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleSingle.rawValue, range: textRange1)
attributedText.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleSingle.rawValue, range: textRange2)
//setup colors
attributedText.addAttribute(NSForegroundColorAttributeName, value: UIColor.red, range: NSRange(location: 22,length: 20))
attributedText.addAttribute(NSForegroundColorAttributeName, value: UIColor.red, range: NSRange(location: 47,length: (theText.characters.count-47)))
mylabel.attributedText = attributedText
}
The tap gesture work for whole label. what I want is when user tap on "terms and conditions" fire a different function and and when user tap on "privacy policy" fire another different function. how can I do that.
Note : I want to fire two different functions one for "terms and conditions" tap, and other for "privacy policy" tap, and do not want to just
open links
import UIKit
protocol SSGastureDelegate:NSObjectProtocol {
func callBack()
}
class SSUnderLineLabel: UILabel {
var tapGesture: UITapGestureRecognizer?
//Make weak refernece for SSGastureDelegate
weak var delegate:SSGastureDelegate?
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.initialization()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.initialization()
}
func initialization(){
let newsString: NSMutableAttributedString = NSMutableAttributedString(string: self.text!)
let textRange = NSString(string: self.text!)
let substringRange = textRange.range(of: "Terms and Conditions") // You can add here for own specific under line substring
newsString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleSingle.rawValue, range: substringRange)
// self.attributedText = newsString.copy() as? NSAttributedString
self.attributedText = newsString
self.tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapResponse))
self.isUserInteractionEnabled = true
self.addGestureRecognizer(tapGesture!)
}
func tapResponse(recognizer: UITapGestureRecognizer) {
let text = (self.text)!
let termsRange = (text as NSString).range(of: "Terms and Conditions")
if (tapGesture?.didTapAttributedTextInLabel(label: self, inRange: termsRange))! {
print("Tapped terms conditions")
self.delegate?.callBack()
}
else {
print("Tapped none ")
}
}
}
//MARK:UITapGestureRecognizer Extension
//MARK:
extension UITapGestureRecognizer {
func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
let textStorage = NSTextStorage(attributedString: label.attributedText!)
// Configure layoutManager and textStorage
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
// Configure textContainer
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = label.lineBreakMode
textContainer.maximumNumberOfLines = label.numberOfLines
let labelSize = label.bounds.size
textContainer.size = labelSize
// Find the tapped character location and compare it to the specified range
let locationOfTouchInLabel = self.location(in: label)
let textBoundingBox = layoutManager.usedRect(for: textContainer)
let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,y:(labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y)
let locationOfTouchInTextContainer = CGPoint(x:locationOfTouchInLabel.x - textContainerOffset.x,
y:locationOfTouchInLabel.y - textContainerOffset.y);
let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return NSLocationInRange(indexOfCharacter, targetRange)
}
}*`enter code here`
**How to use it:**
Assign this class for your label and follow these steps:
Demo for use class:
ViewController.swift
//===============
class ViewController: UIViewController,SSGastureDelegate {
#IBOutlet var underlineLbl: SSUnderLineLabel!
override func viewDidLoad() {
super.viewDidLoad()
self.underlineLbl.delegate = self
}
//Implement Delegate Method
func callBack()
{
//Open a specific vc for underline tap are`enter code here`a.
let termsconditionsVC = storyboard?.instantiateViewController(withIdentifier: "TermsConditionsVC") as! TermsConditionsVC
self.present(termsconditions, animated: true, completion: nil)
}
}*
I see that you need your terms and privacy links to be clickable, the simple way the ios gives is AttributedString, use following code for that:
let theString = "I have agree with the terms and conditions and privacy policy"
let someAttributedString = NSMutableAttributedString(string: theString)
someAttributedString.addAttribute(NSLinkAttributeName, value: "http://www.google.com", range: NSMakeRange(22, 20))
titleLabel.attributedText = someAttributedString
TTTAttributedLabel could be your best solution: https://github.com/TTTAttributedLabel/TTTAttributedLabel
TTTAttributedLabel has following delegate
- (void)attributedLabel:(__unused TTTAttributedLabel *)label
didSelectLinkWithURL:(NSURL *)url
{
//do whatever you want
}
Also if you need solution from tap gesture you can find the point on gesture tap and do you calculation to check where it is tapped to perform your action by gestureRecognizer.locationInView method
I am trying to subclass a UILabel so that it has a default kerning set to 1.5
then i am going to use that in my app with multiple labels. goal is to have default kern set out of the box so i can avoid repeated code all over the place also labels are set as mix of attributed and regular text
Example:
#IBoutlet weak var myLabel: CustomeLabelWithKern!
myLabel.attributedText = myAttributedText
#IBOutlet weak var myOtherLabelInADifferentViewController: CustomeLabelWithKern!
myOtherLabelInADifferentViewController.text "Foo Bar"
both of these label should have kern of 1.5
here is what i have so far
class CustomLabel: UILabel {
var kerning: CGFloat = 1.5
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setKerning(kerning)
}
private func setKerning(kern: CGFloat) {
guard let text = self.text else { return }
let range = NSRange(location: 0, length: text.characters.count)
let mutableString = NSMutableAttributedString(attributedString: attributedText ?? NSAttributedString())
mutableString.addAttribute(NSKernAttributeName, value: kern, range: range)
attributedText = mutableString
}
}
Here is what i have so far i think i will use this solution for now if anyone comes up with a better one i will be happy to try that as well
class CustomLabel: UILabel {
static var kerning: CGFloat = 1.5
override func awakeFromNib() {
super.awakeFromNib()
setKerning(CustomLabel.kerning)
}
func setKerning(kern: CGFloat) {
let text = self.text ?? ""
let range = NSRange(location: 0, length: text.characters.count)
let mutableString = NSMutableAttributedString(attributedString: attributedText ?? NSAttributedString())
mutableString.addAttribute(NSKernAttributeName, value: kern, range: range)
attributedText = mutableString
}
}
I can use it like this in my viewController
mylabel.text = "Hello World!" // this should be set to 1.5 by default but what if i am setting my label dynamically?
mylabel.setKerning(1.5) // Here i am passing the value so if the label is set dynamically set it will have correct spacing
// This also works if some of my labels have attributed text
myAttibutedLabel.attributedText = myAttributedText
myAttributedLabel.setKerning(1.5)
I think a this can be reduce to just an extension on UILabel class
like so
extension UILabel {
func setKerning(kern: CGFloat) {
let text = self.text ?? ""
let range = NSRange(location: 0, length: text.characters.count)
let mutableString = NSMutableAttributedString(attributedString: attributedText ?? NSAttributedString())
mutableString.addAttribute(NSKernAttributeName, value: kern, range: range)
attributedText = mutableString
}
}
How about subclassing UILabel like this:
class TestKerningLabel: UILabel {
func addKerning(kerningValue: Double) {
let attributedString = self.attributedText as! NSMutableAttributedString
attributedString.addAttribute(NSKernAttributeName, value: kerningValue, range: NSMakeRange(0, attributedString.length))
self.attributedText = attributedString
}
}
And then use this in your VC:
let testLabel = TestKerningLabel()
testLabel.attributedText = NSAttributedString(string: "test")
testLabel.addKerning(kerningValue: 1.5)
view.addSubview(testLabel)