22 janvier 2026

Le problème avec les enums

Avec SwiftUI, il est possible de créer son propre alignement. En voici un qui se positionne verticalement au tiers de la hauteur d’une vue.

extension VerticalAlignment {
    public static let third = VerticalAlignment(ThirdAlignmentId.self)
 
    enum ThirdAlignmentId: AlignmentID {
        static func defaultValue(in context: ViewDimensions) -> CGFloat {
            context.height / 3
        }
    }
}

Il est ensuite possible d’utiliser notre alignment avec une HStack.

HStack(alignment: .third) {
    Text("Hello")
        .frame(height: 300)
    Text("World!")
}

C’est super malin et je trouve que ça expose ici un très bon cas de conception. Pendant des années, j’ai cru que les alignements étaient des enums. Et ce à cause de leur simplicité d’utilisation et de leur syntaxe .something.

En réalité, le système d’alignement de SwiftUI utilise un système de polymorphisme pour bénéficier de la composition, l’extensibilité et la décentralisation.

Les enums ne se composent pas

En SwiftUI, il existe trois types d’alignement :

  • VerticalAlignment
  • HorizontalAlignment
  • Alignment

Ça permet de spécifier uniquement le bon type d’alignement que l’on attend et de détecter des erreurs à la compilation.

// N'a pas de sens
// Sera détecté à la compilation
VStack(alignment: .bottom)

Cependant, les ZStack acceptent un alignment vertical, ou horizontal, ou les deux ! Et la syntaxe .topLeading m’a longtemps laissé penser que Alignment était une enum. Seulement, si ça avait été le cas, il y aurait eu quelque part du code dupliqué car il est impossible de combiner des enums.

enum VerticalAlignment {
    case leading
    // ...
}
 
enum HorizontalAlignment {
    case top
    // ...
}
 
enum Alignment {
    case leading // dupliqué ?
    case topLeading // combiné ?
    // ...
}

À la place, Alignment est une struct qui accepte un alignment vertical et horizontal.

struct Alignment {
    let vertical: VerticalAlignment
    let horizontal: HorizontalAlignment
}

Pour continuer à bénéficier de la syntaxe .topLeading, SwiftUI expose des propriétés statiques qui bénéficient de la même syntaxe.

extension Alignment {
    static let topLeading = Alignment(
        horizontal: .top,
        vertical: .top
    )
}

Les enums ne sont pas extensibles

En Swift, il est impossible d’ajouter de nouveaux case à une enum car, fondamentalement, les enums représentent une séries d’états finis.

Si SwiftUI exposait les ShapeStyle sous forme d’enum, il serait impossible pour les devs de créer leur propre style.

enum ShapeStyle {
    case rect
    case circle
}

Avec le combo struct + protocol, il devient possible de créer de nouvelles formes en dehors de ce qu’expose SwiftUI.

protocol ShapeStyle {}
 
struct Rect: ShapeStyle {}
struct Circle: ShapeStyle {}

Et il est toujours possible de garder la syntaxe des enums .something en utilisant des extensions avec contraintes.

extension ShapeStyle where Self == Rect {
    static var rect: Self { Rect() }
}

Les enums sont centralisées

Pour ajouter de la logique aux enums, on peut utiliser les associated values et des computed properties

enum Shape {
    case rect(width: Double, height: Double)
    case circle(radius: Double)
 
    var area: Double {
        switch shape {
        case let .circle(r):
            return .pi * r * r
        case let .rectangle(w, h):
            return w * h
        }
    }
}

Alors que ça pourrait être ok pour une implémentation aussi simple, les formes de SwiftUI sont bien plus complexes et une telle utilisation conduit à des anti-pattern. On ne peut pas créer de computed property pour calculer le diamètre d’un cercle sans retourner une valeur par défaut pour le rectangle.

enum Shape {
    case rect(width: Double, height: Double)
    case circle(radius: Double)
 
    var diameter: Double {
        switch shape {
        case let .circle(r):
            return r * 2
        case let .rectangle(w, h):
            return 0 // ne fait aucun sens
        }
    }
}

Ce return 0 fait penser à une classe abstraite dans laquelle on retourne une valeur par défaut pour les valeurs prévues pour être override.

En utilisant le polymorphisme, il est tout à fait possible de décentraliser et de créer des cas spécifique pour chaque forme.

protocol Shape {
    var area: Double { get }
}
 
struct Circle: Shape {
    let radius: Double
 
    var area: Double {
        .pi * radius * radius
    }
 
    // C'est possible et ça ne sera pas implémenté dans Rect
    var diameter: Double {
        radius * 2
    }
}

Les extensions avec contraintes fonctionnent ici aussi pour garder la syntaxe .something.

extension Shape where Self == Circle {
    static func circle(radius: Double) -> Self {
        Circle(radius: radius)
    }
}

Les enums sont trop simples

Les enums sont le choix à privilégier dans la majorité des cas. Mais lorsqu’on a besoin de composer, d’étendre ou de décentraliser, il est impératif de considérer une autre approche.

On adore la syntaxe .something des enums. Mais elle ne leur est pas propre ! Les extensions avec contraintes et les propriétés statiques bénéficient aussi de ce comportement.

looping arrowsuivez-moi ici !