I need to add "... Read more" to the end of UITextView. I use this solution Add "...Read More" to the end of UILabel. I tried to use it with UILabel - it works perfectly. But in UITextView "... Read more" position isn't stable. I think the problem is in textViewWidth and textViewHeight. When I change their values to -10 it works better but not really. How can I solve it?
My code:
var visibleTextLength: Int {
guard let text = text else { return 0 }
let mode: NSLineBreakMode = textContainer.lineBreakMode
let textViewWidth: CGFloat = frame.size.width //adding - 10 helps a little
let textViewHeight: CGFloat = frame.size.height //adding - 10 helps a little
let sizeConstraint = CGSize(width: textViewWidth, height: CGFloat.greatestFiniteMagnitude)
let attributes: [AnyHashable: Any] = [NSAttributedString.Key.font: font!]
let attributedText = NSAttributedString(string: text, attributes: attributes as? [NSAttributedString.Key: Any])
let boundingRect: CGRect = attributedText.boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, context: nil)
if boundingRect.size.height > textViewHeight {
var index = 0
var prev = 0
let characterSet = CharacterSet.alphanumerics
repeat {
prev = index
if mode == NSLineBreakMode.byCharWrapping {
index += 1
} else {
index = (text as NSString).rangeOfCharacter(from: characterSet, options: [], range: NSRange(location: index + 1, length: text.utf16.count - index - 1)).location
}
} while index != NSNotFound && index < text.utf16.count && (text as NSString).substring(to: index).boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, attributes: attributes as? [NSAttributedString.Key: Any], context: nil).size.height <= textViewHeight
return prev
}
return text.utf16.count
}
func addTrailing(with trailingText: String, moreText: String, moreTextFont: UIFont?) {
guard self.visibleTextLength > 0 else { return }
let readMoreText: String = trailingText + moreText
let lengthForVisibleString: Int = self.visibleTextLength
if let text = self.text {
let mutableString: String = text
let trimmedString: String? = (mutableString as NSString).replacingCharacters(in: NSRange(location: lengthForVisibleString, length: text.utf16.count - lengthForVisibleString), with: "")
let readMoreLength: Int = (readMoreText.utf16.count)
guard let safeTrimmedString = trimmedString, safeTrimmedString.utf16.count >= readMoreLength else { return }
let trimmedForReadMore: String = (safeTrimmedString as NSString).replacingCharacters(in: NSRange(location: safeTrimmedString.utf16.count - readMoreLength, length: readMoreLength), with: "") + trailingText
let attributedMoreText = NSMutableAttributedString(string: trimmedForReadMore,
attributes: [NSAttributedString.Key.font: font!])
if let moreTextFont = moreTextFont {
let readMoreAttributed = NSMutableAttributedString(string: moreText, attributes: [NSAttributedString.Key.font: moreTextFont])
attributedMoreText.append(readMoreAttributed)
}
self.attributedText = attributedMoreText
}
}
With - 10 in textViewWidth and textViewHeight
Related
I want to remove the bottom padding of a UILabel with attributedText inside a UIStackview.
I found this solution How to remove the extra padding below an one line UILabel. This works with normal text but not with attributed text.
let textLabel = UILabel()
textLabel.translatesAutoresizingMaskIntoConstraints = false
textLabel.text = "What is a chemical property and how can you observe it?"
textLabel.numberOfLines = 0
textLabel.lineBreakMode = .byWordWrapping
textLabel.backgroundColor = .lightGray
mainStackView.addArrangedSubview(textLabel)
let textLabel2 = UILabel()
textLabel2.translatesAutoresizingMaskIntoConstraints = false
let html = "<html lang=\"en\"><head><meta charset=\"UTF-8\"></head><body><div style=\"font-size:36;\"><p>What is a <em>chemical property</em> and how can you observe it?</p></div></body></html>"
let data = Data(html.utf8)
if let attributedString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
let a = NSMutableAttributedString.init(attributedString: attributedString)
let range = (a.string as NSString).range(of: a.string)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .left
paragraphStyle.firstLineHeadIndent = 0.0
let attributes: [NSAttributedString.Key: Any] = [
.foregroundColor: UIColor.black,
.paragraphStyle: paragraphStyle
]
a.addAttributes(attributes, range: range)
textLabel2.attributedText = a
}
textLabel2.numberOfLines = 0
textLabel2.lineBreakMode = .byWordWrapping
textLabel2.backgroundColor = .yellow
mainStackView.addArrangedSubview(textLabel2)
let textLabel3 = UILabel()
textLabel3.translatesAutoresizingMaskIntoConstraints = false
textLabel3.text = "What is a chemical property and how can you observe it?"
textLabel3.numberOfLines = 0
textLabel3.lineBreakMode = .byWordWrapping
textLabel3.backgroundColor = .lightGray
mainStackView.addArrangedSubview(textLabel3)
A working sample project with this code can be found here: https://github.com/Quobject/testUIlabelInStackviewpadding
The "bottom spacing" is not "spacing" ... your converted <p>...</p> html block adds a newline character at the end of the text.
You can use this extension (found here):
extension NSMutableAttributedString {
func trimmedAttributedString() -> NSAttributedString {
let invertedSet = CharacterSet.whitespacesAndNewlines.inverted
let startRange = string.rangeOfCharacter(from: invertedSet)
let endRange = string.rangeOfCharacter(from: invertedSet, options: .backwards)
guard let startLocation = startRange?.upperBound, let endLocation = endRange?.lowerBound else {
return NSAttributedString(string: string)
}
let location = string.distance(from: string.startIndex, to: startLocation) - 1
let length = string.distance(from: startLocation, to: endLocation) + 2
let range = NSRange(location: location, length: length)
return attributedSubstring(from: range)
}
}
and change this line:
textLabel2.attributedText = a
to:
textLabel2.attributedText = a.trimmedAttributedString()
Result (applying that change to your GitHub repo):
I have following string "#[Hema](hema_ramburuth), #[Ilesh P](ilesh.panchal), #[Lewis Murphy](lewis) how are you?". I want to display this screen like this "Hema, Ilesh P, Lewis Murphy how are you?" also I want to identify the screen for the click event.
I have used the ActiveLabel repo for the click.
Hey I have had encountered a similar requirement. So this is how I have handled.
I have created an extension for String
extension String {
/// Returns range of text in the string
func getRange(OfText text: String) -> NSRange {
let nsRepresentation = self as NSString
return nsRepresentation.range(of: text)
}
}
In your View Controller,
var tapPrivacyGesture = UITapGestureRecognizer()
#IBOutlet weak var yourLabel: UILabel!
var displayText = String()
func matchesForRegexInText(regex: String, text: String, firstBracket: String, lastBracket: String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex, options: [])
let nsString = text as NSString
let results = regex.matches(
in: text,
options: [],
range: NSRange(location: 0, length: nsString.length))
return results.map { nsString.substring(with: $0.range) }.map { $0.replacingOccurrences(of: firstBracket, with: "") }.map { $0.replacingOccurrences(of: lastBracket, with: "") }
} catch let error as NSError {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
var givenString = "#[Hema](hema_ramburuth), #[Ilesh P](ilesh.panchal), #[Lewis Murphy](lewis) how are you?"
let nameStrings = matchesForRegexInText(regex: "\\[(.*?)\\]", text: givenString, firstBracket: "[", lastBracket: "]")
let removeForUIStrings = matchesForRegexInText(regex: "\\((.*?)\\)", text: givenString, firstBracket: "(", lastBracket: ")")
removeForUIStrings.forEach {
givenString = givenString.replacingOccurrences(of: "(\($0))", with: "")
}
nameStrings.forEach {
givenString = givenString.replacingOccurrences(of: "[\($0)]", with: $0)
}
givenString = givenString.replacingOccurrences(of: "#", with: "")
print(givenString)
displayText = givenString
tapPrivacyGesture.addTarget(self, action: #selector(self.handlePolicyTap(tap:)))
yourLabel.addGestureRecognizer(tapPrivacyGesture)
yourLabel.isUserInteractionEnabled = true
func handlePolicyTap(tap: UITapGestureRecognizer) {
let storage = NSTextStorage(attributedString: yourLabel.attributedText ?? NSAttributedString())
let layoutManager = NSLayoutManager()
storage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(size: CGSize(width: yourLabel.frame.size.width, height: yourLabel.frame.size.height+100))
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = (yourLabel.lineBreakMode)
textContainer.maximumNumberOfLines = yourLabel.numberOfLines
layoutManager.addTextContainer(textContainer)
let location: CGPoint = tap.location(in: yourLabel)
let characterIndex: Int = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
guard
characterIndex < storage.length,
let question = currentQuestion else {
return
}
nameStrings.forEach {
let range = displayText.getRange(OfText: $0)
if range.contains(characterIndex) {
/// Perform actions on click of this string
}
}
}
As from your question, just hard-code parsing done below.
let fullString = "#[Hema](hema_ramburuth), #[Ilesh P](ilesh.panchal), #[Lewis Murphy](lewis) how are you?"
let allarray = fullString.split(separator: ",")
let messageArray = allarray.last
let message = messageArray?.split(separator: ")")
let correctMessage = message?.last
var allNames : String = ""
for namesString in allarray {
if allNames.count > 0 {
allNames += ", "
}
let name = String(namesString)
allNames += name.slice(from: "#[", to: "]") ?? ""
}
if allNames.count > 0 {
allNames += correctMessage ?? ""
}
print("Name and Message --- > \(allNames)")
Slicing string using String extension
extension String {
func slice(from: String, to: String) -> String? {
return (range(of: from)?.upperBound).flatMap { substringFrom in
(range(of: to, range: substringFrom..<endIndex)?.lowerBound).map { substringTo in
substring(with: substringFrom..<substringTo)
}
}
}
}
I've printed output as below:
Name and Message --- > Hema, Ilesh P, Lewis Murphy how are you?
Ok, this is only happening with one part of my text, with 1 set of labels - I explicitly set, many times, my custom UILabel's line break style to be word wrapping. Yet I have only this second part of text dotting off:
As you see, the part that gets bolded (even when longer) DOES word wrap.
Here is custom label class:
class RSSLinkLabel: UILabel {
var separateSymbol = "^"
var id = String()
var bgView = UIView()
var thinLabel = UILabel()
var hasSetLine = Bool(false)
var str = String()
override init (frame : CGRect) {
super.init(frame : frame)
}
func replace(myString: String, _ index: Int, _ newChar: Character) -> String {
var modifiedString = String()
for (i, char) in myString.characters.enumerated() {
modifiedString += String((i == index) ? newChar : char)
}
return modifiedString
}
func customInit()
{
if txtSources[id] != "" && txtSources.keys.contains(id)
{
str = (txtSources[id]?.capitalizingFirstLetter())!
}
if (txtSources[id] != "" && !hasSetLine && txtSources.keys.contains(id))
{
fixText(string: str)
}
if(Network.reachability?.isReachable == false && self.text == "")
{
noWifiAlternative()
}
self.numberOfLines = 0
self.backgroundColor = UIColor.clear
self.clipsToBounds = false
self.lineBreakMode = .byWordWrapping
}
func fixText(string: String)
{
var str = string
self.textColor = barColorStr
var s = NSMutableAttributedString(string: str)
if let i = str.index(of: separateSymbol)
{
let part1 = str.substring(to: str.distance(from: str.startIndex, to: i))
var part2 = str.substring(from: str.distance(from: str.startIndex, to: i)+1)
part2 = part2.trimmingCharacters(in: .whitespaces)
part2 = "\n".appending(part2)
str = part1 + part2
s = NSMutableAttributedString(string: str)
s.addAttribute(NSFontAttributeName, value: overallFontThin, range: NSRange(location: str.distance(from: str.startIndex, to: i)
,length: (str.characters.count - str.distance(from: str.startIndex, to: i))))
//color
s.addAttribute(NSForegroundColorAttributeName, value: thirdColorStr, range: NSRange(location: str.distance(from: str.startIndex, to: i)
,length: (str.characters.count - str.distance(from: str.startIndex, to: i))))
}
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = -1
if let j = str.index(of: "\n")
{
s.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range:NSMakeRange(0, str.distance(from: str.startIndex, to: j)))
}
else {
s.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range:NSMakeRange(0, s.length))
}
self.attributedText = s
hasSetLine = true
self.numberOfLines = 0
self.clipsToBounds = false
self.lineBreakMode = .byWordWrapping
print(self.preferredMaxLayoutWidth)
}
It says my preferred max width is 0.0. Here I make the labels:
for i in 0...featureLabels.count-1
{
featureLabels[i] = RSSLinkLabel()
featureLabels[i]?.frame = CGRect(x: 0, y: 0, width: ((overallWidth-(imgSpace))/3).rounded(), height: heightForView(text: "n \n f \n dd \n n", font: overallFont, width: ((overallWidth-(imgSpace))/3).rounded()))
featureLabels[i]?.backgroundColor = secondColorStr
featureLabels[i]?.textAlignment = .left
And set from my RSS feed content:
for i in 0...6 {
if(i < xmlInfo.rssFreeList.count)
{
var titleNew = (xmlInfo.rssFreeList[i].title.trimmingCharacters(in: .newlines)).components(separatedBy: " ")[0..<((xmlInfo.rssFreeList[i].title.trimmingCharacters(in: .newlines)).components(separatedBy: " ")).endIndex]
if(((xmlInfo.rssFreeList[i].title.trimmingCharacters(in: .newlines)).components(separatedBy: " ").count) > 4)
{
titleNew = (xmlInfo.rssFreeList[i].title.trimmingCharacters(in: .newlines)).components(separatedBy: " ")[0..<((xmlInfo.rssFreeList[i].title.trimmingCharacters(in: .newlines)).components(separatedBy: " ")).endIndex-4]
}
var txt = "\(titleNew.joined(separator: " "))^\(xmlInfo.rssFreeList[i].description.trimmingCharacters(in: .newlines))"
if(i > 0)
{
self.featureViews[i-1]?.downloadedFrom(link: xmlInfo.rssFreeList[i].imgStr)
self.featureViews[i-1]?.link = xmlInfo.rssFreeList[i].link
self.featureViews[i-1]?.loadCircle.isHidden = true
self.featureLabels[i-1]?.text = txt
self.featureLabels[i-1]?.fixText(string: txt)
featureLabels[i-1]?.lineBreakMode = .byWordWrapping
Whats wrong with this second part of text?
set the constraint to your label so that, the height of label get increased when text increased
I want to get first two lines text from UIlabel.I have searched a lot but not find any solution.Please tell me how to get first two lines text.
Use this extension check if your label's number of line granter then 2 or whatever you target then apply this:
extension UILabel {
func addTrailing(with trailingText: String, moreText: String, moreTextFont: UIFont, moreTextColor: UIColor) {
let readMoreText: String = trailingText + moreText
let lengthForVisibleString: Int = self.vissibleTextLength()
let mutableString: String = self.text!
let trimmedString: String? = (mutableString as NSString).replacingCharacters(in: NSRange(location: lengthForVisibleString, length: ((self.text?.characters.count)! - lengthForVisibleString)), with: "")
let readMoreLength: Int = (readMoreText.characters.count)
let trimmedForReadMore: String = (trimmedString! as NSString).replacingCharacters(in: NSRange(location: ((trimmedString?.characters.count ?? 0) - readMoreLength), length: readMoreLength), with: "") + trailingText
let answerAttributed = NSMutableAttributedString(string: trimmedForReadMore, attributes: [NSFontAttributeName: self.font])
let readMoreAttributed = NSMutableAttributedString(string: moreText, attributes: [NSFontAttributeName: moreTextFont, NSForegroundColorAttributeName: moreTextColor])
answerAttributed.append(readMoreAttributed)
self.attributedText = answerAttributed
}
func vissibleTextLength() -> Int {
let font: UIFont = self.font
let mode: NSLineBreakMode = self.lineBreakMode
let labelWidth: CGFloat = self.frame.size.width
let labelHeight: CGFloat = self.frame.size.height
let sizeConstraint = CGSize(width: labelWidth, height: CGFloat.greatestFiniteMagnitude)
let attributes: [AnyHashable: Any] = [NSFontAttributeName: font]
let attributedText = NSAttributedString(string: self.text!, attributes: attributes as? [String : Any])
let boundingRect: CGRect = attributedText.boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, context: nil)
if boundingRect.size.height > labelHeight {
var index: Int = 0
var prev: Int = 0
let characterSet = CharacterSet.whitespacesAndNewlines
repeat {
prev = index
if mode == NSLineBreakMode.byCharWrapping {
index += 1
} else {
index = (self.text! as NSString).rangeOfCharacter(from: characterSet, options: [], range: NSRange(location: index + 1, length: self.text!.characters.count - index - 1)).location
}
} while index != NSNotFound && index < self.text!.characters.count && (self.text! as NSString).substring(to: index).boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, attributes: attributes as? [String : Any], context: nil).size.height <= labelHeight
return prev
}
return self.text!.characters.count
}
}
Count Label's number of line:
func countLabelLines(label: UILabel) -> Int {
// Call self.layoutIfNeeded() if your view uses auto layout
self.view.layoutIfNeeded()
let myText = label.text! as NSString
let rect = CGSize(width: label.bounds.width, height: CGFloat.greatestFiniteMagnitude)
let labelSize = myText.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: label.font], context: nil)
return Int(ceil(CGFloat(labelSize.height) / label.font.lineHeight))
}
Example:
if self.countLabelLines(label: lblmedicationDetailText) >= numberOflines{
let readmoreFont = UIFont(name: "Montserrat-Regular", size: 15.0)
let readmoreFontColor = UIColor.init(red: 1.0/255.0, green: 100.0/255.0, blue: 157.0/255.0, alpha: 1.0)
DispatchQueue.main.async {
self.lblmedicationDetailText.addTrailing(with: "... ", moreText: "Read More", moreTextFont: readmoreFont!, moreTextColor: readmoreFontColor)
}
}
let numOfLine = self.numberOfLinesInLabel(self.testingLabel.text!, labelWidth: self.testingLabel.frame.width, labelHeight: self.testingLabel.frame.height, font: self.testingLabel.font)
self.getNumberOfLineDict.setValue(numOfLine, forKey: String(i))
let Lines : Int = getNumberOfLineDict.valueForKey(String(indexPath.row)) as! Int // I did it in tableview. So I used indexPath.row
if Lines > 2
{
ReadmoreBtn.isHidden = false
}
else
{
ReadmoreBtn.isHidden = true
}
To get the number of lines in a text
func numberOfLinesInLabel(yourString: String, labelWidth: CGFloat, labelHeight: CGFloat, font: UIFont) -> Int {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.minimumLineHeight = labelHeight
paragraphStyle.maximumLineHeight = labelHeight
paragraphStyle.lineBreakMode = .ByWordWrapping
let attributes: [String: AnyObject] = [NSFontAttributeName: font, NSParagraphStyleAttributeName: paragraphStyle]
let constrain = CGSizeMake(labelWidth, CGFloat(Float.infinity))
let size = yourString.sizeWithAttributes(attributes)
let stringWidth = size.width
let numberOfLines = ceil(Double(stringWidth/constrain.width))
return Int(numberOfLines)
}
My advice is doing something on UI only.
You can set a UIView(such as it has gradual clear to white view and has a label "View more...") to cover the original label except first two lines.
You can get the first two lines height by label.font.lineHeight * 2.
I want to get ride of the white spaces in front and at the end of my NSAttributedString(Trimming it). I can't simply convert it to string and do trimming because there are images(attachments) in it.
How can i do it?
Create extension of NSAttributedString as below.
extension NSAttributedString {
public func attributedStringByTrimmingCharacterSet(charSet: CharacterSet) -> NSAttributedString {
let modifiedString = NSMutableAttributedString(attributedString: self)
modifiedString.trimCharactersInSet(charSet: charSet)
return NSAttributedString(attributedString: modifiedString)
}
}
extension NSMutableAttributedString {
public func trimCharactersInSet(charSet: CharacterSet) {
var range = (string as NSString).rangeOfCharacter(from: charSet as CharacterSet)
// Trim leading characters from character set.
while range.length != 0 && range.location == 0 {
replaceCharacters(in: range, with: "")
range = (string as NSString).rangeOfCharacter(from: charSet)
}
// Trim trailing characters from character set.
range = (string as NSString).rangeOfCharacter(from: charSet, options: .backwards)
while range.length != 0 && NSMaxRange(range) == length {
replaceCharacters(in: range, with: "")
range = (string as NSString).rangeOfCharacter(from: charSet, options: .backwards)
}
}
}
and use in viewController where you want to use. like this
let attstring = NSAttributedString(string: "this is test message. Please wait. ")
let result = attstring.attributedStringByTrimmingCharacterSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
This works even with emoji in the text
extension NSAttributedString {
/** Will Trim space and new line from start and end of the text */
public func trimWhiteSpace() -> NSAttributedString {
let invertedSet = CharacterSet.whitespacesAndNewlines.inverted
let startRange = string.utf16.description.rangeOfCharacter(from: invertedSet)
let endRange = string.utf16.description.rangeOfCharacter(from: invertedSet, options: .backwards)
guard let startLocation = startRange?.upperBound, let endLocation = endRange?.lowerBound else {
return NSAttributedString(string: string)
}
let location = string.utf16.distance(from: string.startIndex, to: startLocation) - 1
let length = string.utf16.distance(from: startLocation, to: endLocation) + 2
let range = NSRange(location: location, length: length)
return attributedSubstring(from: range)
}
}
USAGE
let attributeString = NSAttributedString(string: "\n\n\n Hi 👋 👩👩👧👩👩👦👦👩👩👧👧👨👨👦👩👦👨👨👧👧👨👨👦👦👨👨👧👦👩👧👦👩👦👦👩👧👧👨👦 buddy. ")
let result = attributeString.trimWhiteSpace().string // Hi 👋 👩👩👧👩👩👦👦👩👩👧👧👨👨👦👩👦👨👨👧👧👨👨👦👦👨👨👧👦👩👧👦👩👦👦👩👧👧👨👦 buddy.
Swift 4 and above
extension NSMutableAttributedString {
func trimmedAttributedString() -> NSAttributedString {
let invertedSet = CharacterSet.whitespacesAndNewlines.inverted
let startRange = string.rangeOfCharacter(from: invertedSet)
let endRange = string.rangeOfCharacter(from: invertedSet, options: .backwards)
guard let startLocation = startRange?.upperBound, let endLocation = endRange?.lowerBound else {
return NSAttributedString(string: string)
}
let location = string.distance(from: string.startIndex, to: startLocation) - 1
let length = string.distance(from: startLocation, to: endLocation) + 2
let range = NSRange(location: location, length: length)
return attributedSubstring(from: range)
}
}
use:
let string = "This is string with some space in the end. "
let attributedText = NSMutableAttributedString(string: string).trimmedAttributedString()
It turns out that Unicode strings are hard hahaha! The other solutions posted here are a great starting point, but they crashed for me when using non-latin strings.
Whenever using indexes or ranges in Swift Strings, we need to use String.Index instead of plain Int. Creating an NSRange from a Range<String.Index> has to be done with NSRange(swiftRange, in: String).
That being said, this code builds on the other answers, but makes it unicode-proof:
public extension NSMutableAttributedString {
/// Trims new lines and whitespaces off the beginning and the end of attributed strings
func trimmedAttributedString() -> NSAttributedString {
let invertedSet = CharacterSet.whitespacesAndNewlines.inverted
let startRange = string.rangeOfCharacter(from: invertedSet)
let endRange = string.rangeOfCharacter(from: invertedSet, options: .backwards)
guard let startLocation = startRange?.lowerBound, let endLocation = endRange?.lowerBound else {
return NSAttributedString(string: string)
}
let trimmedRange = startLocation...endLocation
return attributedSubstring(from: NSRange(trimmedRange, in: string))
}
}
I made a swift 3 implementation, just in case anyone is interested:
/**
Trim an attributed string. Can for example be used to remove all leading and trailing spaces and line breaks.
*/
public func attributedStringByTrimmingCharactersInSet(set: CharacterSet) -> NSAttributedString {
let invertedSet = set.inverted
let rangeFromStart = string.rangeOfCharacter(from: invertedSet)
let rangeFromEnd = string.rangeOfCharacter(from: invertedSet, options: .backwards)
if let startLocation = rangeFromStart?.upperBound, let endLocation = rangeFromEnd?.lowerBound {
let location = string.distance(from: string.startIndex, to: startLocation) - 1
let length = string.distance(from: startLocation, to: endLocation) + 2
let newRange = NSRange(location: location, length: length)
return self.attributedSubstring(from: newRange)
} else {
return NSAttributedString()
}
}
Swift 3.2 Version:
extension NSAttributedString {
public func trimmingCharacters(in characterSet: CharacterSet) -> NSAttributedString {
let modifiedString = NSMutableAttributedString(attributedString: self)
modifiedString.trimCharacters(in: characterSet)
return NSAttributedString(attributedString: modifiedString)
}
}
extension NSMutableAttributedString {
public func trimCharacters(in characterSet: CharacterSet) {
var range = (string as NSString).rangeOfCharacter(from: characterSet)
// Trim leading characters from character set.
while range.length != 0 && range.location == 0 {
replaceCharacters(in: range, with: "")
range = (string as NSString).rangeOfCharacter(from: characterSet)
}
// Trim trailing characters from character set.
range = (string as NSString).rangeOfCharacter(from: characterSet, options: .backwards)
while range.length != 0 && NSMaxRange(range) == length {
replaceCharacters(in: range, with: "")
range = (string as NSString).rangeOfCharacter(from: characterSet, options: .backwards)
}
}
}
Following code will work for your requirement.
var attString: NSAttributedString = NSAttributedString(string: " this is att string")
let trimmedString = attString.string.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())