static let black: Font.Weight
static let bold: Font.Weight
static let heavy: Font.Weight
static let light: Font.Weight
static let medium: Font.Weight
static let regular: Font.Weight
static let semibold: Font.Weight
static let thin: Font.Weight
static let ultraLight: Font.Weight
(For this I want ["black", "bold", "heavy", "light", "medium", "regular", "semibold", "thin", "ultraLight"]
As far as ListStyle goes, that's no different than actual enums with associated values, in that CaseIterable can't be automatically synthesized.
The others are more like enums without associated values.
I don't know SourceKit. If you figure another solution out, please post here.
Implementing it manually could be worse, but I don't know why Apple didn't provide the following:
// MARK: - CaseIterable
extension Font.Weight: CaseIterable {
@available(macOS, deprecated: 14, message: "It's that time of year—check for new cases!")
public static var allCases: [Self] {
[ ultraLight,
thin,
light,
regular,
medium,
semibold,
bold,
heavy,
black
]
}
}
// MARK: - CustomStringConvertible
extension Font.Weight: CustomStringConvertible {
@available(macOS, deprecated: 14, message: "It's that time of year—check for new cases!")
public var description: String {
switch self {
case .ultraLight: return "ultraLight"
case .thin: return "thin"
case .light: return "light"
case .regular: return "regular"
case .medium: return "medium"
case .semibold: return "semibold"
case .bold: return "bold"
case .heavy: return "heavy"
case .black: return "black"
default: return "\(self)"
}
}
}
These are not enumerations (and UnitMass is not even a value type). Until Apple has this implemented you are on your own and you will need to maintain your own array(s) of values / names.
BTW, under the hood Font.Weight is just a Double, and if you wish so you can define your own weight in between, say, regular and medium (although as this is undocumented it can change, so do this on your own risk). You can also define your own UnitMass – UnitMass(symbol: "mine")
They are all set of statically defined constants. And the reason I'm asking about this is that usually you need to present to the user this list and let them pick a value out of these, such as font weight: let the user choose. Same thing with ListStyle, SwiftUI colors.
I usually use an enum to represent this set of "enumeration-like" values, for example, for UnitMass, I define the enum:
enum MyUnitMass: Hashable, Identifiable, CaseIterable {
case kilograms
case grams
case decigrams
case centigrams
case milligrams
case micrograms
case nanograms
case picograms
case ounces
case pounds
case stones
case metricTons
case shortTons
case carats
case ouncesTroy
case slugs
var id: Self { self }
var localizedName: String {
switch self {
case .kilograms:
return String(localized: "Killograms")
case .grams:
return String(localized: "Grams")
case .decigrams:
return String(localized: "Decigrams")
case .centigrams:
return String(localized: "Centigrams")
case .milligrams:
return String(localized: "Milligrams")
case .micrograms:
return String(localized: "Micrograms")
case .nanograms:
return String(localized: "Nanograms")
case .picograms:
return String(localized: "Picograms")
case .ounces:
return String(localized: "Ounces")
case .pounds:
return String(localized: "Pounds")
case .stones:
return String(localized: "Stones")
case .metricTons:
return String(localized: "MetricTons")
case .shortTons:
return String(localized: "ShortTons")
case .carats:
return String(localized: "Carats")
case .ouncesTroy:
return String(localized: "OuncesTroy")
case .slugs:
return String(localized: "Slugs")
}
}
var unit: UnitMass {
switch self {
case .kilograms:
return .kilograms
case .grams:
return .grams
case .decigrams:
return .decigrams
case .centigrams:
return .centigrams
case .milligrams:
return .milligrams
case .micrograms:
return .micrograms
case .nanograms:
return .nanograms
case .picograms:
return .picograms
case .ounces:
return .ounces
case .pounds:
return .pounds
case .stones:
return .stones
case .metricTons:
return .metricTons
case .shortTons:
return .shortTons
case .carats:
return .carats
case .ouncesTroy:
return .ouncesTroy
case .slugs:
return .slugs
}
}
static func picker(@Binding selected: Self) -> some View {
Picker("Unit Mass", selection: $selected) {
ForEach(Self.allCases) {
Text($0.localizedName).tag($0)
}
}
}
}
For FontWeight, it's the same:
Summary
enum MyListStyle: String, CaseIterable, Hashable, Identifiable {
case automatic = "Automatic"
// case bordered = "Bordered" // 'bordered' is unavailable in iOS
// I don't know how to handle case with associated value :(
// case borderAlternatesRowBackgrounds // bordered(alternatesRowBackgrounds:)
case grouped = "Grouped"
case inset = "Inset"
case insetGrouped = "InsetGrouped"
case plain = "Plain"
case sidebar = "Sidebar"
var id: Self { self }
// map to SwiftUI ListStyle
var style: any ListStyle {
switch self {
case .automatic:
return .automatic
case .grouped:
return .grouped
case .inset:
return .inset
case .insetGrouped:
return .insetGrouped
case .plain:
return .plain
case .sidebar:
return .sidebar
}
}
var displayName: String {
String(localized: String.LocalizationValue(rawValue))
}
static func picker(@Binding selected: Self) -> some View {
let v = Picker("List Style", selection: $selected) {
ForEach(Self.allCases) {
Text($0.displayName).tag($0)
}
}
dump(v)
return v
}
}
I'm looking for ways to automate the creation of these types of enum. The first thing I need is the list of all the "cases".
Yep, see my edit with video above. For a font like RobotoFlex †you can reveal a couple of extra weights (like extraBlack and ultraBlack) but to get a true varying font weight you'll need to use CoreFont + UIFont (which UIFont you can later use with SwiftUI's font modifier). Hope this can be improved in SwiftUI in the future.
†RobotoFlex is a variable font, it was used with both SwiftUI and UIKit texts in my video above.
Of the currently listed 5 fonts on this page Fonts - Apple Developer all fonts but "SF Compact" are variable fonts.
The font obtained via "UIFont.systemFont(ofSize: fontSize)" (whose family name is currently revealed as ".AppleSystemUIFont") is also a variable font. It might be a version of "SF Pro".
SwiftUI.Font.Weight currently has 9 weights.
Fonts may have a number of weights (9, less than or more than 9, or a continuum for a variable font)
Using the above undocumented hack you can have an arbitrary weight in SwiftUI.Font.Weight and that can indeed select some extra weights (e.g. extraBlack + ultraBlack in "Roboto Flex")
However SwiftUI.Font.Weight can not be used to select an arbitrary weight (of a variable font).
Down the road it would be reasonable to have a new API that allows controlling any font "axis" variable, something like this:
let someFont = SwiftUI.Font...
...
Text("Hello")
.font(someFont.axis(identifier, value))
Along with ability to enumerate axes:
someFont.axes.forEach { axis in
print(axis.id)
print(axis.name)
print(axis.minValue)
print(axis.maxValue)
print(axis.defaultValue)
}
You can obtain this functionality today via CTFont -> UIFont route - and use the resulting UIFont in SwiftUI via .font(_ v: UIFont) modifier.
Once we have that API the fate of older API like .weight(_ v: Font.Weight) is not clear. They will probably stay the way they are (for compatibility reasons) and the new API is introduced, e.g. .weight(_ v: CGFloat) which would be a shortcut for some newer:
.axis(Font.Axis.weight, _ v: CGFloat)
with a fallback to older method for fixed fonts or fonts with no wght axis.
All in all, I won't recommend spending too much time today on trying to automatically generate those tables. Just do what ChatGPT apparently did – build those tables manually. In two year's time you'll need to revise the whole thing anyway.
You can fully control all the axis of a variable font:
import SwiftUI
public enum FontVariations: Int, CustomStringConvertible {
case weight = 2003265652
case width = 2003072104
case opticalSize = 1869640570
case grad = 1196572996
case slant = 1936486004
case xtra = 1481921089
case xopq = 1481592913
case yopq = 1498370129
case ytlc = 1498696771
case ytuc = 1498699075
case ytas = 1498693971
case ytde = 1498694725
case ytfi = 1498695241
public var description: String {
switch self {
case .weight:
return "Weight"
case .width:
return "Width"
case .opticalSize:
return "Optical Size"
case .grad:
return "Grad"
case .slant:
return "Slant"
case .xtra:
return "Xtra"
case .xopq:
return "Xopq"
case .yopq:
return "Yopq"
case .ytlc:
return "Ytlc"
case .ytuc:
return "Ytuc"
case .ytas:
return "Ytas"
case .ytde:
return "Ytde"
case .ytfi:
return "Ytfi"
}
}
}
public extension Font {
static func variableFont(_ size: CGFloat, axis: [Int: Int] = [:]) -> Font {
let uiFontDescriptor = UIFontDescriptor(fontAttributes: [.name: "Roboto Flex", kCTFontVariationAttribute as UIFontDescriptor.AttributeName: axis])
let newUIFont = UIFont(descriptor: uiFontDescriptor, size: size)
return Font(newUIFont)
}
}
I found this I think on GitHub but I forgot.
Does anyone know where to download the “downloadable” Apple System Fonts?