change the fontsize of the decimal number in Swift/ SwiftUI

Viewed 468

So i need to make the decimal places smaller than the actual number i will show you what i mean below.

this is the code i have:

import SwiftUI

struct BalanceDetailsView: View {
    //MARK: - PROPERTIES
    var balance: Float
    var balanceString: String {
        return balance.formattedWithSeparator
    }
    //MARK: - BODY
    var body: some View {
        VStack{
            HStack{
                    Text(balanceString)
                        .font(.custom(K.fonts.gilroyBold, size: 24))
                        .multilineTextAlignment(.center)
                        .padding(.top)
             
            }//:VSTACK
        }//:HSTACK
    }
}

struct BalanceDetailsView_Previews: PreviewProvider {
    static var previews: some View {
        BalanceDetailsView(balance: 43678)
            .previewLayout(.sizeThatFits)
    }
}



//Formatter extension i used to get this code
extension Formatter {
    static let withSeparator: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal

        // minimum decimal digit, eg: to display 2 as 2.00
        formatter.minimumFractionDigits = 2

        // maximum decimal digit, eg: to display 2.5021 as 2.50
        formatter.maximumFractionDigits = 2
        
        return formatter
    }()
}

extension Numeric {
    var formattedWithSeparator: String { Formatter.withSeparator.string(for: self) ?? "" }
}
Result I get Result I need
This is the result I get This is the result I need
4 Answers

When you know exact format of your string, like in this case minimum string length will be 4("0.00") , you can safely use dropLast and dropFirst.

I suggest moving 2 to priceFractionDigits constant to reduce constants usage in your code.

Then you can use string concatenation, it'll align Text by baseline.

struct BalanceText: View {
    var balance: Float
    var balanceString: String {
        return balance.formattedWithSeparator
    }
    
    var body: some View {
        Text(balanceString.dropLast(priceFractionDigits))
            .font(.system(size: 24))
            +
            Text(balanceString.dropFirst(balanceString.count - priceFractionDigits))
            .font(.system(size: 18))
    }
}

private let priceFractionDigits = 2

extension Formatter {
    static let withSeparator: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal

        // minimum decimal digit, eg: to display 2 as 2.00
        formatter.minimumFractionDigits = priceFractionDigits

        // maximum decimal digit, eg: to display 2.5021 as 2.50
        formatter.maximumFractionDigits = priceFractionDigits
        
        return formatter
    }()
}

Usage

BalanceText(balance: balance)

enter image description here

Here is an example using the new AttributedString in SwiftUI 3 (iOS 15, macOS 12 etc)

var balanceString: AttributedString {
    var attributedString = AttributedString(balance.formattedWithSeparator)
    guard let separator = Formatter.withSeparator.decimalSeparator else { return attributedString }

    if let range = attributedString.range(of: separator) {
        attributedString[attributedString.startIndex...attributedString.index(beforeCharacter: range.lowerBound)]
            .font = Font.largeTitle
        attributedString[attributedString.index(afterCharacter: range.lowerBound)..<attributedString.endIndex]
            .font = Font.caption
    }

    return attributedString
}

I used some built in font styles here but that should be easy to replace. Also note that since we set the .font attribute here it should be removed from Text

You can concatenate Text together with the + operator.

I slightly change the way you use NumberFormatter, so it's split by the correct decimal character for the locale.

struct BalanceDetailsView: View {
    private let wholeNumberPart: String
    private let decimalNumberPart: String

    init(balance: Double) {
        (wholeNumberPart, decimalNumberPart) = balance.formattedSplittingBySeparator
    }

    var body: some View {
        Text(wholeNumberPart).font(.system(size: 24)) +
        Text(decimalNumberPart).font(.system(size: 16))
    }
}


extension Numeric {
    var formattedSplittingBySeparator: (whole: String, decimal: String) {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.minimumFractionDigits = 2
        formatter.maximumFractionDigits = 2
        let str = formatter.string(for: self) ?? "0\(formatter.decimalSeparator!)00"
        let split = str.components(separatedBy: formatter.decimalSeparator)
        let whole = (split.first ?? "0") + formatter.decimalSeparator
        let decimal = split.count == 2 ? split[1] : "00"
        return (whole: whole, decimal: decimal)
    }
}

Usage:

BalanceDetailsView(balance: 43678)

Result:

Result

You could split the string by the decimal separator and display the parts in a zero-spacing HStack.

struct BalanceView : View {
    let balance : Float
    
    var body: some View {
        let components = balance
                        .formattedWithSeparator
                        .components(separatedBy: Formatter.withSeparator.decimalSeparator)
        HStack(alignment: .firstTextBaseline, spacing: 0) {
            Text(components[0])
            if components.count > 1 {
                Text(".")
                Text(components[1])
                    .font(.title)
            }
        }
        .font(.largeTitle)
        .multilineTextAlignment(.center)
        .padding(.top)
    }
}

Replace the fonts with your custom fonts


In your environment you could use it

struct BalanceDetailsView: View {
    //MARK: - PROPERTIES
    var balance: Float
    
    //MARK: - BODY
    var body: some View {
        VStack{
           BalanceView(balance: balance)
        }//:VSTACK
    }
}
Related