51

I want my UILabel to display text in following manner 6.022*1023. What functions does Swift have for subscript and superscript?

TylerP
  • 9,600
  • 4
  • 39
  • 43
Adam Young
  • 1,321
  • 4
  • 20
  • 36
  • See http://stackoverflow.com/questions/8555735/how-to-make-subscripts-and-superscripts-using-nsattributedstring. – Martin R Mar 24 '15 at 06:23

20 Answers20

105

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:

SuperScript Example

In a more detailed explanation:

  1. Get UIFont you want for both the default and superscript style, superscript must be smaller.
  2. Create a NSMutableAttributedString with the full string and default font.
  3. 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.
  4. Assign it to your UILabel

Hopefully this helps other Swift devs as I needed this as well.

Axel Guilmin
  • 11,454
  • 9
  • 54
  • 64
Forrest Porter
  • 1,392
  • 1
  • 10
  • 11
  • 1
    This crashes if Helvetica isn't available in point sizes 10 or 20. Even sample code should be written with a modicum of error handling. Also the variable names make it confusing, and it doesn't compile. – SafeFastExpressive Feb 03 '18 at 00:35
14

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.

Chris
  • 7,270
  • 19
  • 66
  • 110
13

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.

Glenn Howes
  • 5,447
  • 2
  • 25
  • 29
  • Another advantage of this is that when you have multiple lines, this does not mess with the linespacing as the shifting the baseline in the attributed string does. – Carien van Zyl Mar 01 '18 at 11:18
12

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)

enter image description here

let attStr2 = NSMutableAttributedString().characterSubscriptAndSuperscript(
           string: "H2SO4", 
           characters: ["2","4"], 
           type: .aSub, 
           fontSize: 20, 
           scriptFontSize: 15, 
            offSet: 8, 
           length: [1,1], 
           alignment: .left)

enter image description here

Atka
  • 547
  • 4
  • 7
5

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
zeeshan
  • 4,913
  • 1
  • 49
  • 58
rastislv
  • 290
  • 3
  • 7
  • This is the correct answer. All these other answers with static numeric values and which are unnecessarily long will fail you in a tech interview. – zeeshan Nov 23 '22 at 14:21
4

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.

Jeehut
  • 20,202
  • 8
  • 59
  • 80
4

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)
SafeFastExpressive
  • 3,637
  • 2
  • 32
  • 39
4

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
    }
}
Community
  • 1
  • 1
Abhishek Thapliyal
  • 3,497
  • 6
  • 30
  • 69
4

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)

        }
    }
Sajjon
  • 8,938
  • 5
  • 60
  • 94
  • `assert(int >= 0 && int <= 9)` would be better expressed as `assert((0...9).contains(int))`, or even better as `assert(values.indices.contains(int))`. But there's no point to doing that if you don't also include a helpful user message, because it wouldn't provide any value over the array subscript's usual crash-on-out-of-bounds behaviour. – Alexander Oct 22 '19 at 16:29
3

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

Community
  • 1
  • 1
user1909186
  • 1,124
  • 2
  • 12
  • 26
2

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
}
WhatsMyPurpose
  • 143
  • 1
  • 6
2

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:

enter image description here

selma.suvalija
  • 675
  • 9
  • 14
1

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

henrik-dmg
  • 1,448
  • 16
  • 23
1

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

Text View on Screen

Joseph Levy
  • 157
  • 8
1

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
        }
    }
    
}
swiftyboi
  • 2,965
  • 4
  • 25
  • 52
0

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
    }
}
TheParam
  • 10,113
  • 4
  • 40
  • 51
sejmy
  • 11
  • 1
0
 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))
    }
  • While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. – Piotr Labunski Apr 07 '20 at 10:03
0

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
    }
}
aaronmbmorse
  • 359
  • 3
  • 10
0

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

⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ¹⁰ ¹¹ ¹² ¹²³⁴⁵ 
Warren Stringer
  • 1,692
  • 1
  • 17
  • 14
0

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.

Victor Do
  • 2,237
  • 1
  • 13
  • 9