Widgets iOS 14 : comment rendre vos applications accessibles en un coup d’oeil ?

Comment mettre en place les widgets d’iOS 14 sur votre application ?
jordan.jpg
Jordan FAVRAYMis à jour le 28 Oct 2020
widgets ios14 tuto accessibilité

Avec iOSSystème d'exploitation des appareils Apple. 14, Apple revoit sa copie concernant les Widgets. Dans les versions antérieures d’iOS, ils étaient tous situés dans une page dédiée, à gauche de l’écran d’accueil. Maintenant, ils peuvent être placés n’importe où sur l’écran d’accueil ! Ce qui laisse aux développeurs, la possibilité de revoir leur copie afin de prendre en compte ce nouveau comportement. Dans ce tuto, nous allons voir comment faire pour implémenter un widget dans notre application iOS.

Avant de démarrer

Nous allons créer un Widget qui va nous permettre de récupérer la dernière release de Swift via l’APIUne API est un programme permettant à deux applications distinctes de communiquer entre elles et d’échanger des données. publique de GitHub.

Avant de démarrer, sachez qu’il existe 2 types de Widgets sur iOS 14 :

  • Les StaticConfiguration qui sont des Widgets statiques, c’est-à-dire qu’il ne peut y avoir aucune configuration utilisateur directement depuis le Widget. Cependant, vous pouvez récupérer des informations depuis l’application parente.
  • Les IntentConfiguration qui eux, à l’inverse, sont dynamiques. Ils peuvent récupérer des informations depuis un ’Intent’, ce qui permet de rendre le widget configurable (par exemple, une localisation précise pour une application de météo).

Dans ce tutoriel, nous allons voir les deux types ! A vos claviers, c’est parti !

1 - Créer un projet iOS

Ouvrez Xcode (Il faut xCode 12 minimum), créez un nouveau projet App. Je vous laisse donner le nom que vous voulez à votre projet, de mon côté, je l’appellerai tout simplement GitWidget.

2 - L’extension Widget iOS 14

Le projet maintenant créé, nous allons directement ajouter à l’application une extension qui va nous permettre de créer un widget.

Pour cela, allez dans le fichier de configuration du projet, et ajoutez une target. Il faut ensuite chercher Widget et ajouter l’extension éponyme. Cliquez sur Next

1-widget-target.jpg

Il faut ensuite nommer votre extension, de mon côté WidgetExtension. Je vous invite à décocher Include Configuration Intent, on va s’en occuper manuellement plus tard. Cliquez sur Finish

2-widget-target.jpg

Une popup vous demandant si vous souhaitez activer le scheme qui a été créé pour l’extension s’affiche, cliquez sur Activate.

Vous pouvez essayer de lancer l’extension, un widget va s’ajouter sur votre écran d’accueil qui contient... l’heure ! 

7-config.jpg
 
3-base-widget.jpg

Au niveau du projet, un dossier au nom de votre extension a été créé. A l’intérieur de celui-ci vous trouverez 3 fichiers :

  • WidgetExtension.swift -> C’est le fichier principal qui contient les structures nécessaires au lancement de notre widget.
  • Assets.xcassets -> Ce sont les assets de notre extension.
  • Info.plist -> C’est le fichier de préférence de notre extension.

Pour la suite du tutoriel, et pour qu’on comprenne bien ce que font les classes, supprimez le fichier WidgetExtension.swift.

Afin de mieux nous organiser pour la suite, je vous invite à créer 2 dossiers à l’intérieur du dossier de votre extension :

  • Class
  • Views

3 - La Timeline

Un widget n’ayant pas besoin d’être actualisé dès qu’il s’affiche, Apple passe par un système de Timeline. Cela veut dire que c’est à vous de demander à iOS de mettre à jour votre Widget, en donnant une date d’expiration à l’instance du Widget.

Cela peut également être utile pour une application météo (pour ne citer qu’elle), car ce genre d’application peut anticiper l’affichage de son widget (avec certes, plus ou moins de fiabilité...).

Dans notre cas, nous allons utiliser un délai d’expiration fixe (toutes les 2 minutes pour le développement), car nos données peuvent changer.

Pour créer la timeline, nous devons créer une classe qui hérite du protocole TimelineEntry. Cette classe a besoin d’une date (la fameuse date d’expiration), on lui passe également les informations nécessaires à la configuration de notre Widget.

Il faut sélectionner la target de l’extension pour les nouveaux fichiers.

Nouveau fichier Class/ReleaseTimelineEntry.swift :

import WidgetKit

struct ReleaseTimelineEntry: TimelineEntry { var date: Date var gitRelease: GitRelease }

Nouveau fichier Class/GitRelease.swift :

struct GitRelease: Codable {
    let id: Int
    let tagName: String
    let targetCommitish: String
    let name: String
    let author: GitAuthor
    let createdAt: String
    let publishedAt: String

    enum CodingKeys: String, CodingKey {
        case id, name, author
        case tagName = "tag_name"
        case targetCommitish = "target_commitish"
        case createdAt = "created_at"
        case publishedAt = "published_at"
    }
}

Nouveau fichier Class/GitAuthor.swift :

struct GitAuthor: Codable {
    let id: Int
    let login: String
}

Il faut également que nous récupérions les informations depuis l’API publique de GitHub. Nous allons donc créer une structure qui fait cela :

Nouveau fichier GitHubServices.swift :

struct GitHubServices {
    static func getAppleSwiftLatestRelease(completion: @escaping ((Result<GitRelease, Error>)?) -> Void) {
        if let lUrl = URL(string: "https://api.github.com/repos/apple/swift/releases/latest") {
            let lTask = URLSession.shared.dataTask(with: lUrl) { (data, response, error) in
                guard error == nil else {
                    completion(.failure(error!))
                    return
                }
                
                guard let lData = data, let lGitRelease = try? JSONDecoder().decode(GitRelease.self, from: lData) else {
                    completion(nil)
                    
                    return
                }
                
                completion(.success(lGitRelease))
            }
            
            lTask.resume()
        } else {
            completion(nil)
        }
    }
}

Le TimelineProvider

Le TimelineProvider est un protocole qui va nous permettre de gérer l’affichage de notre Widget. Il contient 3 méthodes :

  • func placeholder(in context: Context) : Méthode qui fournit une instance de la timeline qui représente le placeholder du Widget.
  • func getSnapshot(in context: Context, completion: @escaping (Entry) -> Void) : Méthode qui fournit une instance de la timeline qui représente le Widget. Utilisé notamment pour l’affichage du Widget dans la galerie.
  • func getTimeline(in context: Context, completion: @escaping (Timeline<LastCommitEntry>) -> Void) : Méthode qui fournit une instance de la timeline qui représente le Widget à une date précise et optionnellement, des dates futures.

Nous allons donc déclarer une structure dans un fichier nommé LastRealeaseTimeline.swift avec les méthodes vues précédemment :

import WidgetKit

struct LastReleaseTimeline: TimelineProvider {
    typealias Entry = ReleaseTimelineEntry
    
    func placeholder(in context: Context) -> ReleaseTimelineEntry {
        
    }
    
    func getSnapshot(in context: Context, completion: @escaping (ReleaseTimelineEntry) -> Void) {
        
    }
    
    func getTimeline(in context: Context, completion: @escaping (Timeline<ReleaseTimelineEntry>) -> Void) {
        
    }
}

Dans la fonction placeholder, nous allons créer de fausses données pour montrer que le Widget charge :

func placeholder(in context: Context) -> ReleaseTimelineEntry {
    let lPlaceholderGitAuthor = GitAuthor(id: 0, login: "Chargement...")
    let lPlaceholderGitRelease = GitRelease(id: 0,
                                            tagName: "Chargement...",
                                            targetCommitish: "Chargement...",
                                            name: "Chargement...",
                                            author: lPlaceholderGitAuthor,
                                            createdAt: "Chargement...",
                                            publishedAt: LastReleaseWidgetView.format(date: Date()))
    
    return ReleaseTimelineEntry(date: Date(), gitRelease: lPlaceholderGitRelease)
}

Dans la fonction getSnapshot, nous allons créer des données afin de montrer à quoi le widget pourrait ressembler :

func getSnapshot(in context: Context, completion: @escaping (ReleaseTimelineEntry) -> Void) {
    let lSnapshotGitAuthor = GitAuthor(id: 0, login: "Tim Cook")
    let lSnapshotGitRelease = GitRelease(id: 0,
                                         tagName: "Swift-6.0",
                                         targetCommitish: "master",
                                         name: "Swift 6.0 Release",
                                         author: lSnapshotGitAuthor,
                                         createdAt: "2020-09-08T09:41:00Z",
                                         publishedAt: LastReleaseWidgetView.format(date: Date()))
    
    completion(ReleaseTimelineEntry(date: Date(), gitRelease: lSnapshotGitRelease))
}

Et enfin, la fonction principale : getTimeline. Ici, nous allons donc récupérer les informations via GitHub. À noter que comme nous ne pouvons pas anticiper la sortie de la prochaine release de SwiftLangage de programmation créé par Apple, pour le développement sur leur différents périphériques., nous allons mettre une date d’expiration du Widget toutes les 2 minutes (pour la démo, sinon 1x par jour suffirait amplement :)). Voici le rendu :

func getTimeline(in context: Context, completion: @escaping (Timeline<ReleaseTimelineEntry>) -> Void) {
    GitHubServices.getAppleSwiftLatestRelease { (lResult) in
        let lGitRelease: GitRelease
        
        switch lResult {
        case .success(let lRetreivedGitRelease):
            lGitRelease = lRetreivedGitRelease
        default:
            let lSnapshotGitAuthor = GitAuthor(id: 0, login: "Tim Cook")
            lGitRelease = GitRelease(id: 0,
                                     tagName: "Swift-6.0",
                                     targetCommitish: "master",
                                     name: "Swift 6.0 Release",
                                     author: lSnapshotGitAuthor,
                                     createdAt: "2020-09-08T09:41:00Z",
                                     publishedAt: LastReleaseWidgetView.format(date: Date()))
        }
        
        let lCurrentDate = Date()
        let lExpireDate = Calendar.current.date(byAdding: .minute, value: 2, to: lCurrentDate)!
        
        let lReleaseTimelineEntry = ReleaseTimelineEntry(date: Date(), gitRelease: lGitRelease)
        let lTimeline = Timeline(entries: [lReleaseTimelineEntry], policy: .after(lExpireDate))
        
        completion(lTimeline)
    }
}

A noter : Pour la création de la variable lTimeline, au niveau du paramètre Policy, on a accès à plusieurs types :

  • .after(date) : Le widget se réactualise après une date donnée.
  • .atEnd : Le widget se réactualise après la dernière date de la timeline.
  • .never : Le widget se réactualise dès qu’une nouvelle timeline est disponible.

La WidgetView

Maintenant que notre Widget est fonctionnel, il est temps de passer à l’interface !

A noter, les widgets fonctionnent uniquement avec SwiftUI.

Voici un exemple de ce qu’on pourrait utiliser (Fichier Views/LastReleaseWidgetView.swift) :

import SwiftUI
import WidgetKit

struct LastReleaseWidgetView: View {
    let entry: LastReleaseTimeline.Entry
    
    var body: some View {
        VStack(alignment: .leading) {
            VStack(spacing: 4, content: {
                Text("apple/swift")
                    .underline()
                    .bold()
                    .italic()
                    .font(.system(.title))
                    .foregroundColor(.white)
                    .baselineOffset(1)
                    .frame(maxWidth: .infinity, alignment: .center)
            })
            Spacer()
            HStack(spacing: 10, content: {
                VStack(content: {
                    Text("Dernière release :")
                        .underline()
                        .baselineOffset(1)
                        .font(.system(.caption))
                        .foregroundColor(.white)
                        .bold()
                    Text(entry.gitRelease.name)
                        .font(.system(.title3))
                        .foregroundColor(.white)
                        .bold()
                        .frame(maxWidth: .infinity, alignment: .center)
                })
                VStack(content: {
                    Text("par \(entry.gitRelease.author.login) le \(Self.format(string: entry.gitRelease.publishedAt))")
                        .font(.system(.caption))
                        .foregroundColor(.white)
                        .frame(maxWidth: .infinity, alignment: .center)
                })
            }).frame(maxWidth: .infinity, maxHeight: .infinity)
            Text("Mise à jour le \(Self.format(date:entry.date))")
                .font(.system(.caption2))
                .italic()
                .foregroundColor(.white)
                .frame(maxWidth: .infinity, alignment: .center)
                .padding(.top, 5)
        }.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
        .padding(8)
        .background(LinearGradient(gradient: Gradient(colors: [Color(red: 0.93, green: 0.27, blue: 0.24), Color(red: 0.89, green: 0.66, blue: 0.30)]), startPoint: .topLeading, endPoint: .bottomTrailing))
    }
    
    static func format(date pDate: Date) -> String {
        let lDateFormatter = DateFormatter()
        lDateFormatter.dateFormat = "dd-MM-yyyy HH:mm"
        return lDateFormatter.string(from: pDate)
    }
    
    static func format(string pString: String) -> String {
        let lDateFormatter = DateFormatter()
        lDateFormatter.locale = Locale(identifier: "en_US_POSIX")
        lDateFormatter.dateFormat = "yyyy-MM-dd’T’HH:mm:ssZ"
        if let lDate = lDateFormatter.date(from: pString) {
            return Self.format(date: lDate)
        } else {
            return pString
        }
    }
}

struct LastReleaseWidgetView_Previews: PreviewProvider {
    static var previews: some View {
        let lSnapshotGitAuthor = GitAuthor(id: 0, login: "Tim Cook")
        let lSnapshotGitRelease = GitRelease(id: 0,
                                             tagName: "Swift-6.0",
                                             targetCommitish: "master",
                                             name: "Swift 6.0 Release",
                                             author: lSnapshotGitAuthor,
                                             createdAt: "2020-09-08T09:41:00Z",
                                             publishedAt: LastReleaseWidgetView.format(date: Date()))
        
        let lLastReleaseWidgetView = LastReleaseWidgetView(entry: ReleaseTimelineEntry(date: Date(), gitRelease: lSnapshotGitRelease))
        
        lLastReleaseWidgetView.previewContext(WidgetPreviewContext(family: .systemMedium))
    }
}

Vous êtes bien entendu libre pour le design ! Néanmoins, je vous invite à utiliser cette vue si vous voulez juste tester les Widgets iOS 14 :)

Déclaration du Widget

Nous avons tout fait ! Il ne nous reste plus qu’a déclarer le Widget afin qu’iOS le detecte (fichier LastReleaseWidget.swift) :

@main
struct LastReleaseWidget: Widget {
    private let kind: String = "LastReleaseWidget"
    public var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: LastReleaseTimeline()) { entry in
            LastReleaseWidgetView(entry: entry)
        }
        .configurationDisplayName("Swift : Dernière release")
        .description("Affiche la dernière release du repo Swift.")
    }
}

Et voilà notre widget ! 

7-config.jpg

Rendre le Widget personnalisable

Maintenant, on pourrait imaginer rendre notre Widget personnalisable afin que l’utilisateur puisse consulter les releases du projet GitHub qu’il souhaite.

Pour cela, nous allons rajouter un Siri Intent. Faites un clic droit sur votre dossier WidgetExtension > New File... et ajoutez un fichier SiriKit Intent Definition File

8-widget-update.jpg

ATTENTION : Il faut bien sélectionner les deux targets de votre application (ici GitWidget & WidgetExtensionExtension), sinon l’intent ne fonctionnera pas.

Ce fichier va nous permettre de créer des paramètres pour notre Widget en utilisant SiriKit.

Pour cela, créez un nouvel intent LastRelease dans le fichier que l’on vient de créer, changez sa catégorie en Information/View et cochez uniquement la check-box Intent is eligible for widgets. Ajoutez ensuite un paramètre nommé GitRepo de type String et ajoutez lui comme Default Value "apple/swift". 

9-double-widget.jpg

Et voilà, notre Widget est désormais paramétrable. Nous allons maintenant mettre à jour le code de notre Widget afin que l’on récupère le paramètre saisi. Afin de bien séparer les choses et de conserver notre Widget de base, nous allons recréer les fichiers avec la dénomination Intent à la fin.

Créez un fichier Class/ReleaseTimelineEntryIntent.swift afin de mettre à jour notre timeline pour qu’elle prenne en compte le paramètre :

import WidgetKit

struct ReleaseTimelineEntryIntent: TimelineEntry {
    var date: Date
    var gitRelease: GitRelease
    var gitRepo: String
}

Nous allons ensuite modifier notre fonction getAppleSwiftLatestRelease() afin de la rendre paramétrable (fichier GitHubService.swift) :

static func getGitRepoLatestRelease(forGitRepo pGitRepo: String, completion: @escaping ((Result<GitRelease, Error>)?) -> Void) {
    if let lUrl = URL(string: "https://api.github.com/repos/\(pGitRepo)/releases/latest") {

Il va ensuite falloir modifier le TimelineProvider afin que notre Widget build :

func getTimeline(in context: Context, completion: @escaping (Timeline<ReleaseTimelineEntry>) -> Void) {
    GitHubServices.getGitRepoLatestRelease(forGitRepo: "apple/swift") { (lResult) in
    ...
}

Nous allons aussi cependant quand même en créer un nouveau afin de le rentre compatible avec l’intent. Pour cela, créez un nouveau fichier LastReleaseTimelineIntent.swift :

import WidgetKit

struct LastReleaseTimelineIntent: IntentTimelineProvider {
    typealias Entry = ReleaseTimelineEntryIntent
    typealias Intent = LastReleaseIntent
    
    func placeholder(in context: Context) -> ReleaseTimelineEntryIntent {
        let lPlaceholderGitAuthor = GitAuthor(id: 0, login: "Chargement...")
        let lPlaceholderGitRelease = GitRelease(id: 0,
                                                tagName: "Chargement...",
                                                targetCommitish: "Chargement...",
                                                name: "Chargement...",
                                                author: lPlaceholderGitAuthor,
                                                createdAt: "Chargement...",
                                                publishedAt: LastReleaseWidgetView.format(date: Date()))
        
        return ReleaseTimelineEntryIntent(date: Date(), gitRelease: lPlaceholderGitRelease, gitRepo: "apple/swift")
    }
    
    func getSnapshot(for configuration: LastReleaseIntent, in context: Context, completion: @escaping (ReleaseTimelineEntryIntent) -> Void) {
        let lSnapshotGitAuthor = GitAuthor(id: 0, login: "Tim Cook")
        let lSnapshotGitRelease = GitRelease(id: 0,
                                             tagName: "Swift-6.0",
                                             targetCommitish: "master",
                                             name: "Swift 6.0 Release",
                                             author: lSnapshotGitAuthor,
                                             createdAt: "2020-09-08T09:41:00Z",
                                             publishedAt: LastReleaseWidgetView.format(date: Date()))
        
        completion(ReleaseTimelineEntryIntent(date: Date(), gitRelease: lSnapshotGitRelease, gitRepo: configuration.GitRepo ?? "apple/swift"))
    }
    
    func getTimeline(for configuration: LastReleaseIntent, in context: Context, completion: @escaping (Timeline<ReleaseTimelineEntryIntent>) -> Void) {
        GitHubServices.getGitRepoLatestRelease(forGitRepo: configuration.GitRepo ?? "apple/swift") { (lResult) in
            let lGitRelease: GitRelease
            
            switch lResult {
            case .success(let lRetreivedGitRelease):
                lGitRelease = lRetreivedGitRelease
            default:
                let lSnapshotGitAuthor = GitAuthor(id: 0, login: "Tim Cook")
                lGitRelease = GitRelease(id: 0,
                                         tagName: "Swift-6.0",
                                         targetCommitish: "master",
                                         name: "Swift 6.0 Release",
                                         author: lSnapshotGitAuthor,
                                         createdAt: "2020-09-08T09:41:00Z",
                                         publishedAt: LastReleaseWidgetView.format(date: Date()))
            }
            
            let lCurrentDate = Date()
            let lExpireDate = Calendar.current.date(byAdding: .minute, value: 2, to: lCurrentDate)!
            
            let lReleaseTimelineEntry = ReleaseTimelineEntryIntent(date: Date(), gitRelease: lGitRelease, gitRepo: configuration.GitRepo ?? "apple/swift")
            let lTimeline = Timeline(entries: [lReleaseTimelineEntry], policy: .after(lExpireDate))
            
            completion(lTimeline)
        }
    }
}

Il faut aussi mettre à jour la vue afin qu’elle affiche le repo que l’utilisateur aura choisi. Pour cela, créez un fichier Views/LastReleaseWidgetViewIntent.swift :

import SwiftUI
import WidgetKit

struct LastReleaseWidgetViewIntent: View {
    let entry: LastReleaseTimelineIntent.Entry
    
    var body: some View {
        VStack(alignment: .leading) {
            VStack(spacing: 4, content: {
                Text(entry.gitRepo)
                    .underline()
                    .bold()
                    .italic()
                    .font(.system(.title))
                    .foregroundColor(.white)
                    .baselineOffset(1)
                    .frame(maxWidth: .infinity, alignment: .center)
            })
            Spacer()
            HStack(spacing: 10, content: {
                VStack(content: {
                    Text("Dernière release :")
                        .underline()
                        .baselineOffset(1)
                        .font(.system(.caption))
                        .foregroundColor(.white)
                        .bold()
                    Text(entry.gitRelease.name)
                        .font(.system(.title3))
                        .foregroundColor(.white)
                        .bold()
                        .frame(maxWidth: .infinity, alignment: .center)
                })
                VStack(content: {
                    Text("par \(entry.gitRelease.author.login) le \(Self.format(string: entry.gitRelease.publishedAt))")
                        .font(.system(.caption))
                        .foregroundColor(.white)
                        .frame(maxWidth: .infinity, alignment: .center)
                })
            }).frame(maxWidth: .infinity, maxHeight: .infinity)
            Text("Mise à jour le \(Self.format(date:entry.date))")
                .font(.system(.caption2))
                .italic()
                .foregroundColor(.white)
                .frame(maxWidth: .infinity, alignment: .center)
                .padding(.top, 5)
        }.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
        .padding(8)
        .background(LinearGradient(gradient: Gradient(colors: [Color(red: 0.93, green: 0.27, blue: 0.24), Color(red: 0.89, green: 0.66, blue: 0.30)]), startPoint: .topLeading, endPoint: .bottomTrailing))
    }
    
    static func format(date pDate: Date) -> String {
        let lDateFormatter = DateFormatter()
        lDateFormatter.dateFormat = "dd-MM-yyyy HH:mm"
        return lDateFormatter.string(from: pDate)
    }
    
    static func format(string pString: String) -> String {
        let lDateFormatter = DateFormatter()
        lDateFormatter.locale = Locale(identifier: "en_US_POSIX")
        lDateFormatter.dateFormat = "yyyy-MM-dd’T’HH:mm:ssZ"
        if let lDate = lDateFormatter.date(from: pString) {
            return Self.format(date: lDate)
        } else {
            return pString
        }
    }
}

struct LastReleaseWidgetViewIntent_Previews: PreviewProvider {
    static var previews: some View {
        let lSnapshotGitAuthor = GitAuthor(id: 0, login: "Tim Cook")
        let lSnapshotGitRelease = GitRelease(id: 0,
                                             tagName: "Swift-6.0",
                                             targetCommitish: "master",
                                             name: "Swift 6.0 Release",
                                             author: lSnapshotGitAuthor,
                                             createdAt: "2020-09-08T09:41:00Z",
                                             publishedAt: LastReleaseWidgetView.format(date: Date()))
        
        let lLastReleaseWidgetViewIntent = LastReleaseWidgetViewIntent(entry: ReleaseTimelineEntryIntent(date: Date(), gitRelease: lSnapshotGitRelease, gitRepo: "apple/swift"))
        
        lLastReleaseWidgetViewIntent.previewContext(WidgetPreviewContext(family: .systemMedium))
    }
}

Et enfin, il faut créer l’instance du Widget. Pour cela, nous allons créer une classe LastReleaseWidgetIntent dans un nouveau fichier LastReleaseWidgetIntent.swift :

import WidgetKit
import SwiftUI

@main
struct LastReleaseWidgetIntent: Widget {
    private let kind: String = "LastReleaseWidgetIntent"
    public var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, intent: LastReleaseIntent.self, provider: LastReleaseTimelineIntent()) { entry in
            LastReleaseWidgetViewIntent(entry: entry)
        }
        .configurationDisplayName("GitHubRepo : Dernière release")
        .description("Affiche la dernière release d’un repo GitHub.")
    }
}

Avant de build, il va falloir commenter le @main du fichier LastReleaseWidget.swift, sinon, on ne pourra pas build car il y aura deux instances du Widget, ce qui n’est pas possible pour le moment. Une fois cela fait, vous pouvez lancer le Widget sur votre téléphone. Le rendu est le même, mais si vous restez appuyer sur le Widget, vous pouvez modifier le Widget et vous aurez ceci : 

10-double-widget.jpg

Si je modifie pour un autre repo GitHub (par microsoft/vscode par exemple), mon Widget se mettra à jour

Plusieurs widgets ?

Il est possible, pour la même application, et la même extension, d’avoir plusieurs Widgets ! Et cela est assez simple, il suffit de créer un WidgetBundle. Pour cela, créez un fichier SwiftWidgetsBundle.swift :

import WidgetKit
import SwiftUI

@main
struct SwiftWidgetsBundle: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        LastReleaseWidget()
        LastReleaseWidgetIntent()
    }
}

Il faut aussi que vous commentiez le @main du fichier LastReleaseWidgetIntent.swift.

Et maintenant, vous aurez les 2 widgets que nous avons créé dans l’écran de configuration de notre Widget sur iOS : le Widget classique qui check sur le GitHub apple/swift et le Widget personnalisable

Conclusion

Vous savez maintenant comment ajouter un Widget dans votre application !

C’est un moyen assez pratique pour diffuser des informations à vos utilisateurs via le HomeScreen d’iOS. Vous pouvez également aller plus loin en ajoutant des liens sur les vues de votre Widget, qui ouvrent directement votre application au bon endroit.