iosxcodeswiftuiaccessibilitydynamic-type-feature

Keeping texts of different sizes aligned at top as the dynamic type size increases


I'm trying to align texts of different sizes at the top so that they stay aligned when the Dynamic Type size changes.

import SwiftUI

struct ContentView: View {
    var body: some View {
      HStack(alignment: .center, spacing: 0) {
        Text("$")
          .font(Font.custom("Helvetica", size: 24, relativeTo: .footnote))
          .baselineOffset(7)
        Text("123")
          .font(Font.custom("Helvetica", size: 36, relativeTo: .footnote))
        Text("45")
          .font(Font.custom("Helvetica", size: 24, relativeTo: .footnote))
          .baselineOffset(7)
      }
    }
}

This is aligned at top at Regular Size

But it loses the top alignment as the Dynamic Type gets bigger: Dynamic Type Size ExtraExtraExtraLarge

Any ideas on how to keep them aligned at top even as the Dynamic Type size changes?


Solution

  • Any ideas on how to keep them aligned at top even as the Dynamic Type size changes?

    I never needed this kind of configuration but your question arose my curiosity. 🤔
    (I keep the same context you provided: 3 Text elements with the same text styles/font and two of them having the same font size)

    enter image description here If these elements are aligned on top in a HStack view, it's the top edge that's concerned, not the letters nor the numbers inside. So, I have to move vertically the biggest one to make its top content aligned to the smallest one.

    To reach this goal, I used the alignmentGuide method that returns a view modified with respect to its horizontal alignment according to the computation performed in the method's closure by using the ascender and capHeight metrics difference.

    I implement this information with Xcode 13.4 and iOS 15:🤓

    import SwiftUI
    import Foundation
    
    struct ContentView: View {
    
        @ScaledMetric(relativeTo: .footnote) var sizeMax: CGFloat=60
        @ScaledMetric(relativeTo: .footnote) var sizeMin: CGFloat=24
    
        var body: some View {
            HStack(alignment: .top){
                    Text("$1&")
                      .font(Font.custom("Helvetica", size: 24,
                                        relativeTo: .footnote))
                      .background(Color.blue)
            
                    Text("a2A")
                    .font(Font.custom("Helvetica", size: 60,
                                      relativeTo: .footnote))
                    .alignmentGuide(VerticalAlignment.top){ d in
                        let customFontDescriptor = UIFontDescriptor.init(fontAttributes: [
                                UIFontDescriptor.AttributeName.family: "Helvetica",
                                UIFontDescriptor.AttributeName.textStyle:"footnote"
                            ])
                    
    //                  Values for the font with min size.
                        let fontMin=UIFont(descriptor: customFontDescriptor,
                                           size: sizeMin)
                        let ascMin = fontMin.ascender
                        let capHeightMin = fontMin.capHeight
                    
    //                  Values for the font with max size.
                        let fontMax=UIFont(descriptor: customFontDescriptor,
                                           size: sizeMax)
                        let ascMax = fontMax.ascender
                        let capHeightMax = fontMax.capHeight
                    
                        return abs((ascMax-capHeightMax)-(ascMin-capHeightMin))
                    }
                    .background(Color.gray)
            
                    Text("b3B")
                      .font(Font.custom("Helvetica", size: 24,
                                        relativeTo: .footnote))
                      .background(Color.red)
            }
        }
    }
    

    Following this rationale, I get the following result to keep texts of different sizes aligned at top as the dynamic type size changes. 🎉🥳🎊 enter image description here Don't hesitate to adapt this code snippet that fits the example you provided and that's definitely not generic. 😉