SwiftUI – Function Builder

Avec Swift 5.1, il est désormais possible d’utiliser un outil : les Function Builders. C’est un outil pour pouvoir créer des DSL custom comme en SwiftUI (qui l’utilise très largement…).

Cette nouvelle fonctionnalité est basé sur le « Design Pattern » appelé Monteur (Builder Patern) :

Le monteur (builder) est un patron de conception utilisé pour la création d’une variété d’objets complexes à partir d’un objet source. L’objet source peut consister en une variété de parties contribuant individuellement à la création de chaque objet complet grâce à un ensemble d’appels à l’interface commune de la classe abstraite Monteur.

Pour créer un « Function Builder », tout commence par l’introduction d’un nouveau mot-clé : @_functionBuilder ou bien @functionBuilder.

Ensuite, il faut définir une ou plusieurs surcharges de la function « buildBlock ».

Exemple avec SwiftUI (ne compile pas) :

@functionBuilder
struct ViewBuilder {
    static func buildBlock() -> EmptyView {
        return EmptyView()
    }
    static func buildBlock<V: View>(_ view: V) -> some View {
        return view
    }
    static func buildBlock<A: View, B: View>(
        _ viewA: A,
        _ viewB: B
    ) -> some View {
        return TupleView((viewA, viewB))
    }
}

Mais il est aussi possible d’utiliser les variadics quand on ne sait combien de paramètres seront passés en arguments.

Exemple avec SwiftUI (ne compile pas) :

@functionBuilder
struct ViewBuilder {
    static func buildBlock(_ views: View...) -> CombinedView {
        return CombinedView(views: views)
    } 

Ce qui donne pour SwiftUI en réalité :

struct VStack<Content: View>: View {
    init(@ViewBuilder builder: () -> Content) {
        // A function builder closure can be called just like
        // any other, and the resulting expression can then be
        // used to, for instance, construct a container view.
        let content = builder()
        ...
    }
}

struct HeaderView: View {
    let image: UIImage
    let title: String
    let subtitle: String

    var body: some View {
 VStack {
     Image(uiImage: image)
    Text(title)
    Text(subtitle)
}
   }
}

Au lieu de :

struct HeaderView: View {
    let image: UIImage
    let title: String
    let subtitle: String

    var body: some View {
        var builder = VStackBuilder()
        builder.add(Image(uiImage: image))
        builder.add(Text(title))
        builder.add(Text(subtitle))
        return builder.build()
    }
}

Voici un exemple pour créer un « Function Builder »pour agréger plusieurs NSAttributedString (ce qui avouons-le est très pénible la plupart du temps…)

import UIKit

@_functionBuilder
class NSAttributedStringBuilder {
    static func buildBlock(_ components: NSAttributedString...) -> NSAttributedString {
        let result = NSMutableAttributedString(string: "")

        return components.reduce(into: result) { (result, current) in result.append(current) }
    }
}

extension NSAttributedString {
    class func composing(@NSAttributedStringBuilder _ parts: () -> NSAttributedString) -> NSAttributedString {
        return parts()
    }
}

let result = NSAttributedString.composing {
    NSAttributedString(string: "Hello",
                       attributes: [.font: UIFont.systemFont(ofSize: 24),
                                    .foregroundColor: UIColor.red])
    NSAttributedString(string: " world!",
                       attributes: [.font: UIFont.systemFont(ofSize: 20),
                                    .foregroundColor: UIColor.orange])
}

Sources

https://www.swiftbysundell.com/articles/the-swift-51-features-that-power-swiftuis-api/
https://github.com/vincent-pradeilles/swift-tips
https://fr.wikipedia.org/wiki/Monteur_(patron_de_conception)

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *