admaDIC App Development & IT Solutions

Fonts and Bounds

by Annett Schwarze | 2025-11-21

With `NSAttributedString` one can calculate the bounding box of an attributed string when it is rendered. In the example a `UILabel` is used to show the `NSAttributedString` and a `Rectangle` with the size of the string's bounding box is layered under the label.

To use the `UILabel` in SwiftUI, it is wrapped in a `UIViewRepresentable`. A `Coordinator` is used keep track of the label instance for subsequent updates.

        
import SwiftUI

struct SampleFontView: View {
    let attributedText: NSAttributedString = {
        let str = NSMutableAttributedString(
            string: """
                The bounds of a
                rendered string can
                be determined
                using sizeThatFits
                """)
        str.addAttribute(.font, value: UIFont.italicSystemFont(ofSize: 22), range: NSRange(location: 0, length: str.length))
        return str
    }()

    var body: some View {
        VStack(alignment: .center, spacing: 16) {
            Text("Fonts & Bounds")
                .font(.largeTitle)

            GeometryReader { geo in
                let width: CGFloat = geo.size.width - 40
                let textSize = AttributedLabel.boundingSize(for: attributedText, width: width)

                VStack(alignment: .center) {
                    Spacer()
                    VStack(spacing: 0) {
                        ZStack {
                            Rectangle()
                                .stroke(Color.red, lineWidth: 1)
                                .frame(width: textSize.width, height: textSize.height)

                            AttributedLabel(attributedText: attributedText)
                                .border(Color.blue, width: 1)
                        }
                        .padding()

                        Text("Box of AttributedLabel")
                            .foregroundStyle(Color.blue)
                            .font(.footnote)

                        Text("Calculated Size of String: \(Int(textSize.width)) × \(Int(textSize.height))")
                            .font(.footnote)
                            .foregroundColor(.gray)
                    }
                    .padding(4)
                    .clipShape(RoundedRectangle(cornerRadius: 8))
                    .background {
                        Color.white
                            .clipShape(RoundedRectangle(cornerRadius: 8))
                            .shadow(radius: 4)
                    }
                    .frame(width: 300, height: 300)
                    Spacer()
                }
                .frame(maxWidth: .infinity)
            }
            .background {
                Color(white: 0.95)
                    .ignoresSafeArea(edges: [.leading, .trailing, .bottom])
            }
        }
    }

    struct AttributedLabel: UIViewRepresentable {
        let attributedText: NSAttributedString

        func makeCoordinator() -> C {
            return C()
        }

        func makeUIView(context: Context) -> UIView {
            let view = UIView()
            let label = UILabel()
            context.coordinator.label = label
            label.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(label)

            let cons: [NSLayoutConstraint] = [
                view.centerXAnchor.constraint(equalTo: label.centerXAnchor),
                view.leadingAnchor.constraint(lessThanOrEqualTo: label.leadingAnchor),
                view.trailingAnchor.constraint(greaterThanOrEqualTo: label.trailingAnchor),
                view.topAnchor.constraint(equalTo: label.topAnchor),
                view.bottomAnchor.constraint(equalTo: label.bottomAnchor),
            ]
            NSLayoutConstraint.activate(cons)

            label.numberOfLines = 0
            label.lineBreakMode = .byWordWrapping
            return view
        }

        func updateUIView(_ uiView: UIView, context: Context) {
            context.coordinator.label?.attributedText = attributedText
        }

        static func boundingSize(for attributedText: NSAttributedString, width: CGFloat) -> CGSize {
            let label = UILabel()
            label.numberOfLines = 0
            label.attributedText = attributedText
            let size = label.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
            return size
        }
    }

    class C {
        var label: UILabel?
    }
}

#Preview {
    SampleFontView()
}
    
Font and Bounds

 

www.admadic.de | webmaster@admadic.de | Legal Notice and Trademarks | Privacy
© 2005-2007 - admaDIC | All Rights Reserved
All other trademarks and/or registered trademarks are the property of their respective owners
Last Change: Fri Nov 21 08:17:09 2025 GMT