Add “…Read More” to the end of UITextView - ios

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

How to remove bottom padding of UILabel with attributedText inside UIStackView

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):

Get string from substring

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?

Swift: word wrapping not working? Even when set

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

How to get first two lines text from UILabel

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.

Swift NSAttributedString Trim

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())

Resources