Apprentissage de SwiftUI

C’est bien plus propre avec .map ! Je pensais que ça ne s’appliquait qu’à un Array.

Merci !

Bah non, il te suffit de mettre tous ces éléments dans une Stack et d’appliquer le .padding une seule fois.


Je pensais la même chose, j’ai été étonné de voir ce code dans le tutoriel sur la manière de simuler un if let dans une View SwiftUI. Mais c’est bien pratique.

Dans la foulée, j’ai essayé un if let dans une View avec SwiftUI 2.0 (avec la Beta d’Xcode 12). Cela ne fonctionne toujours pas. Dommage …

Je persiste à penser qu’un honnête dragon est bleu ou métallique !

@MonsieurSaucisse félicitations pour ton app UI !

Par contre, je trouve que la structure de ton code est à revoir. Tu as tout mis dans un seul fichier, alors que tu pourrais séparer tes éléments graphiques.

Ça te permettrait aussi de debugger plus facilement, en cliquant sur les éléments graphiques pour sélectionner le code ou vis versa.

1 J'aime

Je m’étais fait la réflexion aussi… ton commentaire m’a motivé pour le faire :slight_smile:

J’ai donc séparé en plusieurs fichiers, mais j’avoue avoir du mal à trouver comment les structurer, choisir un nom adéquat, etc.

J’en ai aussi profité pour utiliser deux extensions, pour Color et Image, ça rend le code nettement plus léger. Et encore, je n’ai pas fait ça très proprement :grimacing:

J’ai trouvé comment ajouter une bordure en dégradé autour des stories, j’ai étendu les stories et les photos sur toute la largeur grâce à @s0ta, j’ai ajouté la colorisation des icônes cœur et marque-page, et j’ai ajouté une actionSheet sur l’ellipse à droite de l’auteur de la photo… je suis assez content du résultat.

Définitivement, j’aime beaucoup SwiftUI :heart_eyes:

1 J'aime

Pourtant c’est à priori permis dans Xcode 12 :

struct ContentView: View {
    @State var username: String?

    var body: some View {
        Group {
            if let username = username {
                Text("Welcome, \(username)")
            } else {
                Image(systemName: "questionmark.circle")
            }
        }
        .font(.largeTitle)
    }
}

Oups, tu as raison. J’avais tapé un petit test en vitesse, a 2h00 du matin, avec une grosse erreur dans le code (tellement ridicule que je n’ose pas la reproduire ici).

J’ai corrigé et ça fonctionne maintenant. Merci.

Merci @ThonyF pour la pull request ! :slight_smile:

C’est effectivement plus lisible comme ça.

1 J'aime

@MonsieurSaucisse Service !

J’ai corrigé le bug de l’iPad. Ça vient de la NavigationView qui est, en SwiftUI, par défaut, comme sur l’app Mail d’Apple.
La première vue de la NavigationView n’est pas affichée automatiquement, c’est la seconde vue qui l’est et quand tu cliques sur le bouton de navigation, c’est un bandeau qui apparaît.

D’ailleurs, ce comportement est aussi valable sur les iPhone en mode paysage.

Donc, j’ai modifier le style par défaut du NavigationView en ajoutant cette ligne : .navigationViewStyle(StackNavigationViewStyle())

Et je ne sais pas, si tu as remarqué, mais dans ta barre des stories, il n’y a pas le même espacement entre les photos de profils, qui n’y est pas dans l’App officielle.

2 J'aime

Merci pour l’astuce avec StackNavigationViewStyle !

Pour information, tu avais ajouté le mode sombre sur la prévisualisation avec .colorScheme(.dark), qui est déprécié. Il faut utiliser preferredColorScheme(.dark) à la place (et il n’est plus nécessaire de spécifier le fond en noir).

J’ai un peu revu l’architecture et le code, et le résultat me convient. J’ai notamment remplacé List par ScrollView. Les fonctionnalités apportées par List n’étaient pas utiles dans ce cas.

Sauf deux détails qui me posent problème encore :

  1. Comme souligné par @ThonyF, l’espacement entre les photos de profil des stories n’est pas toujours le même. Je ne sais pas comment corriger ça :frowning:

  2. En zoomant, on voit des imperfections graphiques sur la première photo de profil des stories (cf. ci-dessous), autour de la photo et du « + ». J’ai essayé en utilisant ZStack ou .overlay(), le résultat est le même. Je ne sais pas d’où ça vient ni comment m’en débarrasser.

Voici le code concerné :

Image("profile00")
    .resizable()
    .clipShape(Circle())
    .frame(width: 65, height: 65)
    .overlay(Circle()
        .strokeBorder(Color.systemBackground, lineWidth: 5))
Image(systemName: "plus.circle.fill")
    .resizable()
    .foregroundColor(.blue)
    .background(Color.systemBackground)
    .clipShape(Circle())
    .frame(width: 17, height: 17)
    .overlay(Circle()
        .strokeBorder(Color.systemBackground, lineWidth: 2))

Pas facile à reproduire, sans connaître Color.systemBackground.

Le problème n’existant pas avec la version que tu as mise en ligne, je présume que cela viens de ta couleur de fond.

Cela ressemble beaucoup à un problème d’anti-aliasing, entre deux couleurs proches mais différentes quand même.

Il faut plus de code pour reproduire le problème et voir comment y remédier.

Oui pardon, Color.systemBackground est en fait Color(UIColor.systemBackground). Je n’ai pas trouvé comment faire autrement pour prendre la couleur par défaut du fond.

Finalement, j’ai eu une idée juste après avoir envoyé le message… qui fonctionne :slight_smile:

J’ai utilisé plusieurs ZStack imbriqués :

ZStack(alignment: .bottomTrailing) {
    ZStack {
        Circle().fill(Color.systemBackground)
            .frame(width: 65, height: 65)
        Image("profile00")
            .resizable()
            .clipShape(Circle())
            .frame(width: 55, height: 55)
    }
    ZStack {
        Circle().fill(Color.systemBackground)
            .frame(width: 19, height: 19)
        Image(systemName: "plus.circle.fill")
            .resizable()
            .foregroundColor(.blue)
            .background(Color.systemBackground)
            .clipShape(Circle())
            .frame(width: 17, height: 17)
        }
    }

Tout le code est sur mon dépôt GitHub.

Merci pour l’info, je ne l’ai pas fait sous la version bêta de Xcode 12 mais, sous Xcode 11 donc, je n’ai pas remarqué de problème.

Indice : Ce n’est pas la photo le problème, mais le texte :wink:

Trouvé !

Un cercle est composé d’un contour d’une épaisseur de 1 point, et du remplissage de l’intérieur.

Tu dessines une image de 65x65 pixels, puis tu y superposes un cercle de même taille, avec une épaisseur de 5 points. Le problème c’est l’utilisation de l’opérateur .strokeBorder() qui n’agit que sur le remplissage, donc sur un cercle de 64x64 pixels ! Le contour de l’image n’est pas modifiée, ce qui laisse sur l’écran une aura visible.

Pour régler cela, il ne faut pas utiliser .strokeBorder(), mais .stroke() qui lui dessine sur l’écran en partant du contour et non du bord intérieur.

Une reprise de ton code initial, en utilisant .stroke() :

var couleurFond1 = Color.init(UIColor.systemBackground)

struct Version0 : View {
  var body: some View {
    ZStack(alignment: .bottomTrailing) {
      Image("chat")
        .resizable()
        .clipShape(Circle())
        .frame(width: 65, height: 65)
        .overlay(Circle()
          .stroke(couleurFond1, lineWidth: 5))
      Image(systemName: "plus.circle.fill")
        .resizable()
        .foregroundColor(.blue)
        .background(couleurFond1)
        .clipShape(Circle())
        .frame(width: 17, height: 17)
        .overlay(Circle()
          .stroke(couleurFond1, lineWidth: 2))
    }
  }
}

Pas d’effet d’aura sur le résultat :


1 J'aime

Moi non plus, mais j’ai l’impression que la dépréciation est déjà d’actualité. C’est la nécessité de mettre explicitement le fond en noir qui m’a mis la puce à l’oreille. J’ai trouvé ça redondant.

J’ai ajouté .scaledToFit() aux noms des utilisateurs. Ce n’est pas parfait, mais c’est mieux qu’avant. Je n’arrive cependant pas à avoir exactement le même espacement entre chaque élément.

Bien joué, je n’avais pas compris que .strokeBorder() conserve le contour d’un pixel extérieur de l’image. Le problème avec .stroke() est que j’avais du mal ensuite à aligner avec la première image qui elle n’en comporte pas.

En cherchant, j’avais trouvé une explication assez visuelle. Il faut imaginer un cercle tracé sur une feuille, et on a un gros feutre en main. Avec .strokeBorder(), c’est comme si ton traçait le contour du cercle à l’intérieur de ce dernier, sans recouvrir son trait. Alors qu’avec .stroke(), le milieu du feutre suit le trait du cercle : la moitié déborde à l’intérieur, l’autre à l’extérieur.

La solution avec plusieurs ZStack imbriqués me parait plus « propre », mais je me trompe peut-être :slight_smile: L’avantage est aussi qu’on ne cache pas une partie de l’image.

Oui, ta seconde solution est meilleure. Si j’ai réfléchi au problème, c’est pour comprendre d’où venait l’effet d’aura. Cela m’intriguais vraiment.

1 J'aime

@MonsieurSaucisse Essaye comme ça :

Text(profile.name)
                .font(.footnote).lineLimit(1)
                .frame(width: 65, alignment: .center)
1 J'aime

J’y avais pensé, sans essayer :sweat_smile: Je m’attendais plus à appliquer un modificateur sur le parent.

J’ai un peu avancé, en ajoutant un splash screen qui disparait après une seconde. Les logos sont faits en SwiftUI, il n’y a pas d’image (le code source pour ceux que ça intéresse).

1 J'aime

Il faut toujours essayer ! :wink:

1 J'aime

Je pense que tu devrais essayer de regarder du côté des modifiers afin d’organiser encore ton code, et de remplacer plusieurs modifiers par un de ta composition, et ce qui en rendrait la maintenance plus facile, car lorsqu’un d’entre eux changerait de syntaxe, par exemple, tu n’auras pas à courir dans tout ton code à la recherche de toutes ses occurrences, tu n’auras qu’à passer en revue les quelques custom modifiers que tu auras écrits, et qui seront ainsi réutilisables dans toutes tes applis.