Swift: word wrapping not working? Even when set - ios

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

Related

Add “…Read More” to the end of UITextView

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

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?

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.

How do I use subscript and superscript in Swift?

I want my UILabel to display text in following manner 6.022*1023. What functions does Swift have for subscript and superscript?
Most of the answers+examples are in ObjC, but this is how to do it in Swift.
let font:UIFont? = UIFont(name: "Helvetica", size:20)
let fontSuper:UIFont? = UIFont(name: "Helvetica", size:10)
let attString:NSMutableAttributedString = NSMutableAttributedString(string: "6.022*1023", attributes: [.font:font!])
attString.setAttributes([.font:fontSuper!,.baselineOffset:10], range: NSRange(location:8,length:2))
labelVarName.attributedText = attString
This gives me:
In a more detailed explanation:
Get UIFont you want for both the default and superscript style, superscript must be smaller.
Create a NSMutableAttributedString with the full string and default font.
Add an attribute to the characters you want to change (NSRange), with the smaller/subscript UIFont, and the NSBaselineOffsetAttributeName value is the amount you want to offset it vertically.
Assign it to your UILabel
Hopefully this helps other Swift devs as I needed this as well.
As a different approach, I wrote a function that takes in a string where the exponents are prepended with ^ such as 2^2•3•5^2 and returns 2²•3•5²
func exponentize(str: String) -> String {
let supers = [
"1": "\u{00B9}",
"2": "\u{00B2}",
"3": "\u{00B3}",
"4": "\u{2074}",
"5": "\u{2075}",
"6": "\u{2076}",
"7": "\u{2077}",
"8": "\u{2078}",
"9": "\u{2079}"]
var newStr = ""
var isExp = false
for (_, char) in str.characters.enumerate() {
if char == "^" {
isExp = true
} else {
if isExp {
let key = String(char)
if supers.keys.contains(key) {
newStr.append(Character(supers[key]!))
} else {
isExp = false
newStr.append(char)
}
} else {
newStr.append(char)
}
}
}
return newStr
}
It's a bit of a brute force method, but it works if you don't want to deal with attributed strings or you want your string to be independent of a font.
If you can get along with text that doesn't look perfect, and only need a subset of characters you can make use of the unicode superscript and subscript numbers: ⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ₀ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉
This has the advantage of being a lot less cumbersome.
I wrote the following extension or you can use it as a function, it is working well for me . you can modify it by skipping the parts that are not essential to you
extension NSMutableAttributedString
{
enum scripting : Int
{
case aSub = -1
case aSuper = 1
}
func characterSubscriptAndSuperscript(string:String,
characters:[Character],
type:scripting,
fontSize:CGFloat,
scriptFontSize:CGFloat,
offSet:Int,
length:[Int],
alignment:NSTextAlignment)-> NSMutableAttributedString
{
let paraghraphStyle = NSMutableParagraphStyle()
// Set The Paragraph aligmnet , you can ignore this part and delet off the function
paraghraphStyle.alignment = alignment
var scriptedCharaterLocation = Int()
//Define the fonts you want to use and sizes
let stringFont = UIFont.boldSystemFont(ofSize: fontSize)
let scriptFont = UIFont.boldSystemFont(ofSize: scriptFontSize)
// Define Attributes of the text body , this part can be removed of the function
let attString = NSMutableAttributedString(string:string, attributes: [NSFontAttributeName:stringFont,NSForegroundColorAttributeName:UIColor.black,NSParagraphStyleAttributeName: paraghraphStyle])
// the enum is used here declaring the required offset
let baseLineOffset = offSet * type.rawValue
// enumerated the main text characters using a for loop
for (i,c) in string.characters.enumerated()
{
// enumerated the array of first characters to subscript
for (theLength,aCharacter) in characters.enumerated()
{
if c == aCharacter
{
// Get to location of the first character
scriptedCharaterLocation = i
//Now set attributes starting from the character above
attString.setAttributes([NSFontAttributeName:scriptFont,
// baseline off set from . the enum i.e. +/- 1
NSBaselineOffsetAttributeName:baseLineOffset,
NSForegroundColorAttributeName:UIColor.black],
// the range from above location
range:NSRange(location:scriptedCharaterLocation,
// you define the length in the length array
// if subscripting at different location
// you need to define the length for each one
length:length[theLength]))
}
}
}
return attString}
}
examples:
let attStr1 = NSMutableAttributedString().characterSubscriptAndSuperscript(
string: "23 x 456",
characters:["3","5"],
type: .aSuper,
fontSize: 20,
scriptFontSize: 15,
offSet: 10,
length: [1,2],
alignment: .left)
let attStr2 = NSMutableAttributedString().characterSubscriptAndSuperscript(
string: "H2SO4",
characters: ["2","4"],
type: .aSub,
fontSize: 20,
scriptFontSize: 15,
offSet: 8,
length: [1,1],
alignment: .left)
My solution as an extension of String
extension String {
func setAsSuperscript(_ textToSuperscript: String) -> NSMutableAttributedString {
let attributedString = NSMutableAttributedString(string: self)
let foundRange = attributedString.mutableString.range(of: textToSuperscript)
let font = UIFont.systemFont(ofSize: 12)
if foundRange.location != NSNotFound {
attributedString.addAttribute(.font, value: font, range: foundRange)
attributedString.addAttribute(.baselineOffset, value: 3, range: foundRange)
attributedString.addAttribute(.foregroundColor, value: UIColor.red, range: foundRange)
}
return attributedString
}
And usage:
let placeholder = "Required value*".setAsSuperscript("*")
myLabel.attributedText = placeholder
For a simple to use Swift solution, you might want to checkout HandyUIKit. After importing it into your project (e.g. via Carthage – see instructions in README) you can do something like this:
import HandyUIKit
"6.022*10^{23}".superscripted(font: UIFont.systemFont(ofSize: 20, weight: .medium))
This line will return an NSAttributedString which will look exactly like what you're looking for. Just assign it to a UILabels attributedText property and that's it!
If you're looking for subscripting a text, simply use subscripted(font:) instead. It will recognize structures like CO_{2}. There's also superAndSubscripted(font:) if you want to combine both.
See the docs for more information and additional examples.
Here is a simple version that has correct error handling and will compile in playground.
import UIKit
func setMyLabelText(myLabel: UILabel) {
if let largeFont = UIFont(name: "Helvetica", size: 20), let superScriptFont = UIFont(name: "Helvetica", size:10) {
let numberString = NSMutableAttributedString(string: "6.022*10", attributes: [.font: largeFont])
numberString.append(NSAttributedString(string: "23", attributes: [.font: superScriptFont, .baselineOffset: 10]))
myLabel.attributedText = numberString
}
}
let myLabel = UILabel()
setMyLabelText(myLabel: myLabel)
Swift 4+ Version of #Atka's Answer
import UIKit
extension NSMutableAttributedString {
enum Scripting : Int {
case aSub = -1
case aSuper = 1
}
func scripts(string: String,
characters: [Character],
type: Scripting,
stringFont: UIFont,
fontSize: CGFloat,
scriptFont: UIFont,
scriptFontSize: CGFloat,
offSet: Int,
length: [Int],
alignment: NSTextAlignment) -> NSMutableAttributedString {
let paraghraphStyle = NSMutableParagraphStyle()
paraghraphStyle.alignment = alignment
var scriptedCharaterLocation = Int()
let attributes = [
NSAttributedStringKey.font: stringFont,
NSAttributedStringKey.foregroundColor: UIColor.black,
NSAttributedStringKey.paragraphStyle: paraghraphStyle
]
let attString = NSMutableAttributedString(string:string, attributes: attributes)
let baseLineOffset = offSet * type.rawValue
let scriptTextAttributes: [NSAttributedStringKey : Any] = [
NSAttributedStringKey.font: scriptFont,
NSAttributedStringKey.baselineOffset: baseLineOffset,
NSAttributedStringKey.foregroundColor: UIColor.blue
]
for (i,c) in string.enumerated() {
for (theLength, aCharacter) in characters.enumerated() {
if c == aCharacter {
scriptedCharaterLocation = i
attString.setAttributes(scriptTextAttributes, range: NSRange(location:scriptedCharaterLocation,
length: length[theLength]))
}
}
}
return attString
}
}
Here's a Swift 5.1 solution (should work with older versions of Swift too) using recursion, that only focuses outputting a superscript from an Int (i.e. no formatting for display).
extension Int {
func superscriptString() -> String {
let minusPrefixOrEmpty: String = self < 0 ? Superscript.minus : ""
let (quotient, remainder) = abs(self).quotientAndRemainder(dividingBy: 10)
let quotientString = quotient > 0 ? quotient.superscriptString() : ""
return minusPrefixOrEmpty + quotientString + Superscript.value(remainder)
}
}
enum Superscript {
static let minus = "⁻"
private static let values: [String] = [
"⁰",
"¹",
"²",
"³",
"⁴",
"⁵",
"⁶",
"⁷",
"⁸",
"⁹"
]
static func value(_ int: Int) -> String {
assert(int >= 0 && int <= 9)
return values[int]
}
}
Here are some tests to prove correctness:
func testPositiveIntegersSuperscript() {
XCTAssertEqual(0.superscriptString(), "⁰")
XCTAssertEqual(1.superscriptString(), "¹")
XCTAssertEqual(2.superscriptString(), "²")
XCTAssertEqual(3.superscriptString(), "³")
XCTAssertEqual(4.superscriptString(), "⁴")
XCTAssertEqual(5.superscriptString(), "⁵")
XCTAssertEqual(6.superscriptString(), "⁶")
XCTAssertEqual(7.superscriptString(), "⁷")
XCTAssertEqual(8.superscriptString(), "⁸")
XCTAssertEqual(9.superscriptString(), "⁹")
XCTAssertEqual(10.superscriptString(), "¹⁰")
XCTAssertEqual(11.superscriptString(), "¹¹")
XCTAssertEqual(12.superscriptString(), "¹²")
XCTAssertEqual(19.superscriptString(), "¹⁹")
XCTAssertEqual(20.superscriptString(), "²⁰")
XCTAssertEqual(21.superscriptString(), "²¹")
XCTAssertEqual(99.superscriptString(), "⁹⁹")
XCTAssertEqual(100.superscriptString(), "¹⁰⁰")
XCTAssertEqual(101.superscriptString(), "¹⁰¹")
XCTAssertEqual(102.superscriptString(), "¹⁰²")
XCTAssertEqual(237.superscriptString(), "²³⁷")
XCTAssertEqual(999.superscriptString(), "⁹⁹⁹")
XCTAssertEqual(1000.superscriptString(), "¹⁰⁰⁰")
XCTAssertEqual(1001.superscriptString(), "¹⁰⁰¹")
XCTAssertEqual(1234.superscriptString(), "¹²³⁴")
XCTAssertEqual(1337.superscriptString(), "¹³³⁷")
}
func testNegativeIntegersSuperscript() {
XCTAssertEqual(Int(-1).superscriptString(), "⁻¹")
XCTAssertEqual(Int(-2).superscriptString(), "⁻²")
XCTAssertEqual(Int(-3).superscriptString(), "⁻³")
XCTAssertEqual(Int(-4).superscriptString(), "⁻⁴")
XCTAssertEqual(Int(-5).superscriptString(), "⁻⁵")
XCTAssertEqual(Int(-6).superscriptString(), "⁻⁶")
XCTAssertEqual(Int(-7).superscriptString(), "⁻⁷")
XCTAssertEqual(Int(-8).superscriptString(), "⁻⁸")
XCTAssertEqual(Int(-9).superscriptString(), "⁻⁹")
XCTAssertEqual(Int(-10).superscriptString(), "⁻¹⁰")
XCTAssertEqual(Int(-11).superscriptString(), "⁻¹¹")
XCTAssertEqual(Int(-12).superscriptString(), "⁻¹²")
XCTAssertEqual(Int(-19).superscriptString(), "⁻¹⁹")
XCTAssertEqual(Int(-20).superscriptString(), "⁻²⁰")
XCTAssertEqual(Int(-21).superscriptString(), "⁻²¹")
XCTAssertEqual(Int(-99).superscriptString(), "⁻⁹⁹")
XCTAssertEqual(Int(-100).superscriptString(), "⁻¹⁰⁰")
XCTAssertEqual(Int(-101).superscriptString(), "⁻¹⁰¹")
XCTAssertEqual(Int(-102).superscriptString(), "⁻¹⁰²")
XCTAssertEqual(Int(-237).superscriptString(), "⁻²³⁷")
XCTAssertEqual(Int(-999).superscriptString(), "⁻⁹⁹⁹")
XCTAssertEqual(Int(-1000).superscriptString(), "⁻¹⁰⁰⁰")
XCTAssertEqual(Int(-1001).superscriptString(), "⁻¹⁰⁰¹")
XCTAssertEqual(Int(-1234).superscriptString(), "⁻¹²³⁴")
XCTAssertEqual(Int(-1337).superscriptString(), "⁻¹³³⁷")
}
My solution is more than twice as fast as gorillaz' solution(which is string and array based), thanks to mine being math and recursion based. Here is proof:
private typealias SuperscriptVector = (value: Int, expectedSuperstring: String)
private let vector1to9: SuperscriptVector = (123456789, "¹²³⁴⁵⁶⁷⁸⁹")
func performanceTest(times n: Int, function: (Int) -> () -> String) {
func manyTimes(_ times: Int) {
func doTest(vector: SuperscriptVector) {
let result: String = function(vector.value)()
XCTAssertEqual(result, vector.expectedSuperstring)
}
for _ in 0..<times {
doTest(vector: vector1to9)
}
}
manyTimes(n)
}
// 3.244 sec
func testPerformanceMine() {
measure {
performanceTest(times: 1_000_000, function: Int.superscriptString)
}
}
// 7.6 sec
func testPerformanceStackOverflow() {
measure {
performanceTest(times: 1_000_000, function: Int.superscriptStringArrayBased)
}
}
For those using SwiftUI, an option is to use a unicode exception string in Text():
Text("c\u{2082}=a\u{2082}+b\u{2082}") /// c^2 = a^2 + b^2
One benefit of this method is easier inline subs/supers.
If it must absolutely inherit from UILabel (e.g. for native NSAttributedString or native wrapping), you can leverage UIViewRepresentable and use the unicode exception string (which should work in most cases). Here is an option on SO: Stackoverflow. I have not tried the answer.
And for those looking for unicode for common subscripts and superscripts (e.g. for arithmetic):
Superscripts:
0 = 2070
1 = 00B9
2 = 00B2
3 = 00B3
4 = 2074
5 = 2075
6 = 2076
7 = 2077
8 = 2078
9 = 2079
+ = 207A
- = 207B
( = 207D
) = 207E
n = 207F
Subscripts:
0 = 2080
1 = 2081
2 = 2082
3 = 2083
4 = 2084
5 = 2085
6 = 2086
7 = 2087
8 = 2088
9 = 2089
+ = 208A
- = 208B
( = 208D
) = 208E
e = 2091
n = 2099
Reference: unicode.org
A nice simple function that outputs a number as the superscript text.
func exponent(i: Int) -> String {
let powers : [String] = [
"\u{2070}",
"\u{00B9}",
"\u{00B2}",
"\u{00B3}",
"\u{2074}",
"\u{2075}",
"\u{2076}",
"\u{2077}",
"\u{2078}",
"\u{2079}"
]
let digits = Array(String(i))
var string = ""
for d in digits {
string.append("\(powers[Int(String(d))!])")
}
return string
}
In SwiftUI it is possible to achieve superscript effect by using baselineOffset modifier. For example:
Text("$")
.foregroundColor(Color.white)
.font(.custom(AppTheme.getRegularFont(), size: 13))
.baselineOffset(8.0)
Text("20")
.foregroundColor(AppTheme.primaryColor)
.font(.custom(AppTheme.getRegularFont(), size: 25))
Here is how it looks:
I have created a String extension which takes a string and converts all of its superscript into unicode characters. This way you could for example share the resulting string without any hassle.
extension Character {
var unicode: String {
// See table here: https://en.wikipedia.org/wiki/Unicode_subscripts_and_superscripts
let unicodeChars = [Character("0"):"\u{2070}",
Character("1"):"\u{00B9}",
Character("2"):"\u{00B2}",
Character("3"):"\u{00B3}",
Character("4"):"\u{2074}",
Character("5"):"\u{2075}",
Character("6"):"\u{2076}",
Character("7"):"\u{2077}",
Character("8"):"\u{2078}",
Character("9"):"\u{2079}",
Character("i"):"\u{2071}",
Character("+"):"\u{207A}",
Character("-"):"\u{207B}",
Character("="):"\u{207C}",
Character("("):"\u{207D}",
Character(")"):"\u{207E}",
Character("n"):"\u{207F}"]
if let unicode = unicodeChars[self] {
return unicode
}
return String(self)
}
}
extension String {
var unicodeSuperscript: String {
let char = Character(self)
return char.unicode
}
func superscripted() -> String {
let regex = try! NSRegularExpression(pattern: "\\^\\{([^\\}]*)\\}")
var unprocessedString = self
var resultString = String()
while let match = regex.firstMatch(in: unprocessedString, options: .reportCompletion, range: NSRange(location: 0, length: unprocessedString.count)) {
// add substring before match
let substringRange = unprocessedString.index(unprocessedString.startIndex, offsetBy: match.range.location)
let subString = unprocessedString.prefix(upTo: substringRange)
resultString.append(String(subString))
// add match with subscripted style
let capturedSubstring = NSAttributedString(string: unprocessedString).attributedSubstring(from: match.range(at: 1)).mutableCopy() as! NSMutableAttributedString
capturedSubstring.string.forEach { (char) in
let superScript = char.unicode
let string = NSAttributedString(string: superScript)
resultString.append(string.string)
}
// strip off the processed part
unprocessedString.deleteCharactersInRange(range: NSRange(location: 0, length: match.range.location + match.range.length))
}
// add substring after last match
resultString.append(unprocessedString)
return resultString
}
mutating func deleteCharactersInRange(range: NSRange) {
let mutableSelf = NSMutableString(string: self)
mutableSelf.deleteCharacters(in: range)
self = mutableSelf as String
}
}
For example "x^{4+n}+12^{3}".superscripted() produces "x⁴⁺ⁿ+12³"
This was inspired by HandyUIKit and the gist to my code is on Github
Here is what I came up with for a SwiftUI Text view with subscripts and superscripts embedded in the String initialize. Surround a subscript with \\b[text]\\e and a superscript with \\a[text]\\e where [text] are the characters in the sub- or superscript.
//
// FormattedText.swift
//
// Created by Joseph Levy on 8/25/21.
import Foundation
import SwiftUI
enum Attribute { case normal; case sub; case sup }
struct AttributedString {
var attribute: Attribute
var string: String
}
func StringToAttributedStrings(_ string: String) -> [AttributedString] {
//var lastAtt: Attribute = .normal
var splits = string.components(separatedBy: "\\")
var filter = false
var attSplits: [AttributedString] = []
for i in splits.indices {
var a: Attribute = { //() -> Attribute in
let firstchar = splits[i].first
switch firstchar {
case "a": do { a = .sup; filter = true }
case "b": do { a = .sub; filter = true }
case "e": do { a = .normal; filter = true }
default: do {
a = .normal
if i > 0 { splits[i] = "\\" + splits[i] }
filter = false;
}
}
return a
}()
attSplits.append(AttributedString(attribute: a, string: filter ? String(splits[i].dropFirst()) : splits[i] ))
}
return attSplits
}
func FormattedText(_ string: String, up: CGFloat = 8, down: CGFloat = 3) -> Text {
let aStrings = StringToAttributedStrings(string)
var returnText = Text("")
var addedText: Text
for aString in aStrings {
switch aString.attribute {
case .normal: addedText = Text(aString.string)
case .sub: addedText = Text(aString.string).font(.footnote).baselineOffset(-down)
case .sup: addedText = Text(aString.string).font(.footnote).baselineOffset(up)
}
returnText = returnText + addedText
}
return returnText
}
Use
FormattedText("Al\\bx\\eGa\\b1-x\\eAs\\a*\\e")
gives
I created an AmountFormatter class which helped me convert decimal numbers into numbers with raised decimals.
class AmountFormatter {
static func sharedFormatter(
decimalNumber: NSDecimalNumber,
currency: String,
raisedDecimals: Bool) -> NSAttributedString {
let numberFormatter = NumberFormatter()
numberFormatter.usesGroupingSeparator = true
numberFormatter.groupingSeparator = "."
numberFormatter.decimalSeparator = ","
numberFormatter.numberStyle = .decimal
let scale: Int16 = 2
let behavior = NSDecimalNumberHandler(
roundingMode: .plain,
scale: scale,
raiseOnExactness: false,
raiseOnOverflow: false,
raiseOnUnderflow: false,
raiseOnDivideByZero: true)
guard let amountString = numberFormatter.string(
from: decimalNumber.rounding(accordingToBehavior: behavior))
else {
fatalError("Can't convert conversion from 'NSDecimalNumber' to string")
}
let currencyAmountString = currency + amountString
let font = UIFont(name: "Roboto", size: 20)
let fontSuper = UIFont(name: "Roboto", size: 10)
let attributedCurrencyAmountString = NSMutableAttributedString(
string: currencyAmountString,
attributes: [.font: font!])
if raisedDecimals == false {
return attributedCurrencyAmountString as NSAttributedString
}
var array = attributedCurrencyAmountString.string.split(separator: ",")
let lenght = array[0].count
attributedCurrencyAmountString.setAttributes(
[.font: fontSuper!, .baselineOffset: 10],
range: NSRange(location: lenght, length: 3))
attributedCurrencyAmountString.setAttributes(
[.font: fontSuper!],
range: NSRange(location: 0, length: 1))
return attributedCurrencyAmountString as NSAttributedString
}
}
extension String {
func convertToSuperscriptDigits(from start: Int, to end: Int? = nil) - String {
let end = end ?? self.count
let startIndex = self.index(self.startIndex, offsetBy: start)
let endIndex = self.index(self.startIndex, offsetBy: end)
let replaceRange = startIndex..<endIndex
let substring = self[replaceRange]
let supers = [
"0": "\u{2070}",
"1": "\u{00B9}",
"2": "\u{00B2}",
"3": "\u{00B3}",
"4": "\u{2074}",
"5": "\u{2075}",
"6": "\u{2076}",
"7": "\u{2077}",
"8": "\u{2078}",
"9": "\u{2079}"]
let convertString = substring.map { (char) -> Character in
Character(supers[String(char)] ?? String(char))
}
return self.replacingCharacters(in: replaceRange, with: String(convertString))
}
This will superscript all the numbers in a string and remove the ^ character.
Use:
yourstring.addSuper()
code:
extension String {
func addSuper() -> String {
let charset = CharacterSet(charactersIn: "1234567890")
let toSuper: [Character: String] = ["0": "\u{2070}",
"1": "\u{00B9}",
"2": "\u{00B2}",
"3": "\u{00B3}",
"4": "\u{2074}",
"5": "\u{2075}",
"6": "\u{2076}",
"7": "\u{2077}",
"8": "\u{2078}",
"9": "\u{2079}",
"-": "\u{207B}"]
var resultString: String = ""
var index: Int = 0
for charater in self {
if String(charater).rangeOfCharacter(from: charset) != nil {
resultString.append(toSuper[charater] ?? "")
} else if charater != "^" {
resultString.append(charater)
}
index += 1
}
return resultString
}
}
I wrote a fun little algorithm for this as an extension on Int that doesn't require any messy attributed strings.
Usage:
let superscriptString = 8675309.superscriptString
Implementation:
extension Int {
var superscriptString: String {
var input: Int = self
var result: String = ""
while input > 0 {
let lastDigit = input % 10
input /= 10
guard let superscript = lastDigit.superscript else { continue }
result = superscript + result
}
return result
}
private var superscript: String? {
switch self {
case 0:
return "\u{2070}"
case 1:
return "\u{00B9}"
case 2:
return "\u{00B2}"
case 3:
return "\u{00B3}"
case 4:
return "\u{2074}"
case 5:
return "\u{2075}"
case 6:
return "\u{2076}"
case 7:
return "\u{2077}"
case 8:
return "\u{2078}"
case 9:
return "\u{2079}"
default:
return nil
}
}
}
First an extension to get a substring
extension String {
subscript(idx: Int) -> String {
String(self[index(startIndex, offsetBy: idx)])
}
}
Next get the actual superScript
func superScript(_ num: Int) -> String {
var s = ""
let numStr = String(num)
for n in numStr.utf8 {
let i = Int(n) - 48 // utf8 for '0'
s += "⁰¹²³⁴⁵⁶⁷⁸⁹"[i]
}
return s
}
and to test
for i in 0...12 { print(superScript(i), terminator: " ") }
print(superScript(12345), terminator: " ")
yielding output
⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ¹⁰ ¹¹ ¹² ¹²³⁴⁵
in CoreText there is a key for such style: https://developer.apple.com/documentation/coretext/kctsuperscriptattributename
so NSAttributedString has undocumented key
__C.NSAttributedStringKey(_rawValue: NSSuperScript)
so few lines of code can do the job:
extension NSMutableAttributedString {
func applySuperscript(range: NSRange) {
let superScriptKey = NSAttributedString.Key("NSSuperScript")
addAttribute(superScriptKey, value: Int64(1) , range: range)
}
}
to make a subscript - use Int64(-1)
I didn't research when this key appeared, maybe it was exist since even iOS 3 sdk. Also, keep in mind there is no guarantee Apple won't modify this key in future.

Finding word or characters count in uitextview on Swift

i want to learn searching word or characters count on uitextview.
i used: rangeOfIndex(uitextfield.text)
but this is only find me one namely first one. if uitextview contains 4 times same word, rangeOfIndex show me only first.
thank you.
Try this
let searchString = "abc"
let baseString = "This is DEMO APP abc \"abc\" more th. abcabcABCAbc."
let attributed = NSMutableAttributedString(string: baseString)
var error: NSError?
let regex = NSRegularExpression(pattern: searchString, options: .CaseInsensitive, error: &error)
if let regexError = error {
println("Oh no! \(regexError)")
}
else {
for match in regex.matchesInString(baseString, options: NSMatchingOptions.allZeros, range: NSRange(location: 0, length: baseString.utf16Count)) as [NSTextCheckingResult] {
attributed.addAttribute(NSBackgroundColorAttributeName, value: UIColor.yellowColor(), range: match.range)
}
textView.attributedText = attributed
}
For counting characters in UITextField.text, you can use countElements() method.
let textfield:UITextField = UITextField()
textfield.text = "same word same word"
let count = countElements(textfield.text) // count will have value of 19
var count = 0
let text2 = txtArama.text.lowercaseString as NSString
let text = txtMetin.text.lowercaseString as NSString
let attributedText = NSMutableAttributedString(attributedString: txtMetin.attributedText)
println("\(text.length)")
println("\(text2.length)")
var aralik:NSRange
var checker:NSString = ""
for(var i=0 ; i <= text.length - text2.length ; i++)
{
aralik = NSMakeRange(i, text2.length)
checker = text.substringWithRange(aralik)
if(text2 == checker)
{
attributedText.addAttribute(NSBackgroundColorAttributeName, value: UIColor(red: 0, green: 0.5, blue: 1, alpha: 0.2) ,
range: aralik)
txtMetin.attributedText = attributedText
count++
}
}
println("\(count)")
lblArananKelime.text = txtArama.text.lowercaseString
lblArananKelimeSayisi.text = toString(count)
i hope this helps you

Resources