Néomorphisme utilisant SwiftUI. Partie 2: Que peut-on faire avec l'accessibilité?

Bonjour à tous! En prévision du début du cours avancé «Développeur IOS», nous publions une traduction de la deuxième partie de l'article sur le néomorphisme à l'aide de SwiftUI ( lire la première partie ).





Thème sombre


Avant de commencer à comprendre comment nous pouvons améliorer la disponibilité du néomorphisme, voyons comment nous pouvons travailler avec l'effet pour créer d'autres styles intéressants.

Ajoutez d'abord deux autres couleurs à l'extension Colorafin d'avoir plusieurs valeurs constantes sous la main:

static let darkStart = Color(red: 50 / 255, green: 60 / 255, blue: 65 / 255)
static let darkEnd = Color(red: 25 / 255, green: 25 / 255, blue: 30 / 255)

Nous pouvons les utiliser comme arrière-plan pour ContentViewremplacer celui existant Color.white, comme indiqué ici:

var body: some View {
    ZStack {
        LinearGradient(Color.darkStart, Color.darkEnd)

La nôtre SimpleButtonStylesemble désormais déplacée, car elle impose une stylisation lumineuse sur un fond sombre. Nous allons donc créer un nouveau style sombre qui fonctionne mieux ici, mais cette fois-ci, nous le diviserons en deux parties: une vue d'arrière-plan que nous pouvons utiliser n'importe où et un style de bouton qui l'enveloppe avec les modificateurs padding et contentShape. Cela nous donnera plus de flexibilité, comme vous le verrez plus tard.

La nouvelle vue d'arrière-plan que nous allons ajouter nous permettra de spécifier n'importe quelle forme pour notre effet visuel, donc nous ne sommes plus attachés aux cercles. Il gardera également une trace de la concavité de notre effet convexe (vers l'intérieur ou vers l'extérieur), selon la propriété isHighlightedque nous pouvons changer de l'extérieur.

Nous commencerons par le plus simple, en utilisant une approche de retournement d'ombre modifiée pour obtenir un effet concave. Ajoutez cette structure:

struct DarkBackground<S: Shape>: View {
    var isHighlighted: Bool
    var shape: S

    var body: some View {
        ZStack {
            if isHighlighted {
                shape
                    .fill(Color.darkEnd)
                    .shadow(color: Color.darkStart, radius: 10, x: 5, y: 5)
                    .shadow(color: Color.darkEnd, radius: 10, x: -5, y: -5)

            } else {
                shape
                    .fill(Color.darkEnd)
                    .shadow(color: Color.darkStart, radius: 10, x: -10, y: -10)
                    .shadow(color: Color.darkEnd, radius: 10, x: 10, y: 10)
            }
        }
    }
}

La modification est que lorsque le bouton est enfoncé, la taille de l'ombre diminue - nous utilisons une distance de 5 points au lieu de 10.

Ensuite, nous pouvons l'envelopper à l'aide du DarkButtonStyleremplissage et de contentShape, comme indiqué ici:

struct DarkButtonStyle: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .padding(30)
            .contentShape(Circle())
            .background(
                DarkBackground(isHighlighted: configuration.isPressed, shape: Circle())
            )
    }
}

Enfin, nous pouvons appliquer cela à notre bouton en le ContentViewmodifiant ButtonStyle():

.buttonStyle(DarkButtonStyle())

Voyons ce qui s'est passé - bien que nous n'ayons pas beaucoup de code, je pense que le résultat semble assez bon.



Quelques expériences


Il est maintenant temps d'expérimenter l'effet, car cela vous aidera à mieux comprendre de quoi SwiftUI est spécifiquement capable.

Par exemple, nous pourrions créer un bouton convexe lisse en lui ajoutant un dégradé linéaire et en le retournant lorsque vous appuyez dessus:

if isHighlighted {
    shape
        .fill(LinearGradient(Color.darkEnd, Color.darkStart))
        .shadow(color: Color.darkStart, radius: 10, x: 5, y: 5)
        .shadow(color: Color.darkEnd, radius: 10, x: -5, y: -5)

} else {
    shape
        .fill(LinearGradient(Color.darkStart, Color.darkEnd))
        .shadow(color: Color.darkStart, radius: 10, x: -10, y: -10)
        .shadow(color: Color.darkEnd, radius: 10, x: 10, y: 10)
}

Si vous l'exécutez, vous verrez que le bouton s'anime en douceur de haut en bas lorsqu'il est enfoncé et relâché. Je trouve l'animation un peu distrayante, je recommande donc de la désactiver en ajoutant ce modificateur à la méthode makeBody()de DarkButtonStyle, après le modificateur présent background():

.animation(nil)



Cet effet du bouton coussin est charmant, mais si vous prévoyez de l'utiliser, je vous conseille d'essayer les trois changements suivants pour faire ressortir un peu plus le bouton.

Premièrement, malgré le fait que cela contredit le principe de faible contraste du design néomorphique, je remplacerais l'icône grise par une blanche pour la faire ressortir. Donc, ContentViewvous auriez besoin des éléments suivants:

Image(systemName: "heart.fill")
    .foregroundColor(.white)

Deuxièmement, si vous ajoutez une superposition pour le bouton à l'état pressé, cela ne fera pas seulement ressembler davantage à un vrai bouton physique appuyé, mais aidera également à distinguer son état pressé de celui non pressé.

Pour mettre en œuvre, vous devez insérer le modificateur overlay()après fill()quand isHighlightedil est vrai, comme ici:

if isHighlighted {
    shape
        .fill(LinearGradient(Color.darkEnd, Color.darkStart))
        .overlay(shape.stroke(LinearGradient(Color.darkStart, Color.darkEnd), lineWidth: 4))
        .shadow(color: Color.darkStart, radius: 10, x: 5, y: 5)
        .shadow(color: Color.darkEnd, radius: 10, x: -5, y: -5)



Pour obtenir un aspect encore plus net, vous pouvez supprimer deux modificateurs shadow()pour l'état enfoncé qui se concentrent sur la superposition autrement.

Troisièmement, vous pouvez également ajouter une superposition à l'état non pressé, juste pour marquer qu'il s'agit d'un bouton. Mettez-le immédiatement après fill(), comme ceci:

} else {
    shape
        .fill(LinearGradient(Color.darkStart, Color.darkEnd))
        .overlay(shape.stroke(Color.darkEnd, lineWidth: 4))
        .shadow(color: Color.darkStart, radius: 10, x: -10, y: -10)
        .shadow(color: Color.darkEnd, radius: 10, x: 10, y: 10)
}



Ajout d'un style de commutateur


L'un des avantages de séparer un style de bouton d'un style d'arrière-plan non morphique est que nous pouvons maintenant ajouter un style de commutateur en utilisant le même effet. Cela signifie créer une nouvelle structure conforme au protocole ToggleStylequi est similaire à ButtonStyle, sauf que:

  1. Nous devons lire configuration.isOnpour déterminer si l'interrupteur est activé.
  2. Nous devons fournir un bouton pour gérer l'acte de commutation, ou au moins quelque chose comme onTapGesture()ou quelque chose comme ça.

Ajoutez cette structure à votre projet:

struct DarkToggleStyle: ToggleStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        Button(action: {
            configuration.isOn.toggle()
        }) {
            configuration.label
                .padding(30)
                .contentShape(Circle())
        }
        .background(
            DarkBackground(isHighlighted: configuration.isOn, shape: Circle())
        )
    }
}

Nous voulons en mettre un ContentViewpour que vous puissiez le tester vous-même, alors commencez par ajouter cette propriété:

@State private var isToggled = false

Enveloppez ensuite le bouton existant VStackavec un espacement égal à 40 et placez-le ci-dessous:

Toggle(isOn: $isToggled) {
    Image(systemName: "heart.fill")
        .foregroundColor(.white)
}
.toggleStyle(DarkToggleStyle())

Votre structure ContentViewdevrait ressembler à ceci:

struct ContentView: View {
    @State private var isToggled = false

    var body: some View {
        ZStack {
            LinearGradient(Color.darkStart, Color.darkEnd)

            VStack(spacing: 40) {
                Button(action: {
                    print("Button tapped")
                }) {
                    Image(systemName: "heart.fill")
                        .foregroundColor(.white)
                }
                .buttonStyle(DarkButtonStyle())

                Toggle(isOn: $isToggled) {
                    Image(systemName: "heart.fill")
                        .foregroundColor(.white)
                }
                .toggleStyle(DarkToggleStyle())
            }
        }
        .edgesIgnoringSafeArea(.all)
    }
}

Et c'est tout - nous avons appliqué notre conception néomorphique commune à deux endroits!

Amélioration de l'accessibilité


Nous avons eu assez de temps pour jouer avec différents styles néomorphes, mais maintenant je veux m'attarder sur les problèmes de cette conception: un manque de contraste extrême signifie que les boutons et autres commandes importantes ne suffisent pas à se démarquer de leur environnement, ce qui rend difficile l'utilisation de nos applications pour les personnes avec vision imparfaite.

C'est le moment autour duquel j'observe un malentendu, donc je veux dire quelques choses à l'avance:

  1. Oui, je comprends que les boutons Apple standard ressemblent au texte bleu et ne ressemblent donc pas aux boutons familiers, du moins à première vue, mais ils ont un rapport de contraste élevé.
  2. « , , » — — « », , , - .
  3. - SwiftUI, , VoiceOver Apple.

Vous avez déjà vu comment nous avons changé l'icône grise en blanche pour obtenir une amélioration instantanée du contraste, mais les boutons et les commutateurs ont encore besoin de beaucoup plus de contraste si vous voulez les rendre plus accessibles.

Nous allons donc réfléchir aux changements que nous pourrions apporter pour que les éléments se démarquent vraiment.

Tout d'abord, je voudrais que vous ajoutiez deux nouvelles couleurs à notre extension:

static let lightStart = Color(red: 60 / 255, green: 160 / 255, blue: 240 / 255)
static let lightEnd = Color(red: 30 / 255, green: 80 / 255, blue: 120 / 255)

Deuxièmement, dupliquez l'existant DarkBackgroundet nommez la copie ColorfulBackground. Nous y reviendrons dans un instant, mais encore une fois, nous devons d'abord nous préparer.

Troisièmement, dupliquer le style sombre du bouton et commutateur, les renommer ColorfulButtonStyleet ColorfulToggleStyle, puis faire les utiliser le nouveau ColorfulBackgroundcomme l'arrière - plan.

Ils devraient donc ressembler à ceci:

struct ColorfulButtonStyle: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .padding(30)
            .contentShape(Circle())
            .background(
                ColorfulBackground(isHighlighted: configuration.isPressed, shape: Circle())
            )
            .animation(nil)
    }
}

struct ColorfulToggleStyle: ToggleStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        Button(action: {
            configuration.isOn.toggle()
        }) {
            configuration.label
                .padding(30)
                .contentShape(Circle())
        }
        .background(
            ColorfulBackground(isHighlighted: configuration.isOn, shape: Circle())
        )
    }
}

Et enfin, modifiez le bouton et basculez ContentViewpour utiliser le nouveau style:

Button(action: {
    print("Button tapped")
}) {
    Image(systemName: "heart.fill")
        .foregroundColor(.white)
}
.buttonStyle(ColorfulButtonStyle())

Toggle(isOn: $isToggled) {
    Image(systemName: "heart.fill")
        .foregroundColor(.white)
}
.toggleStyle(ColorfulToggleStyle())

Vous pouvez exécuter l'application si vous le souhaitez, mais cela n'a aucun sens - elle n'a pas réellement changé.

Pour apporter notre version colorée de la vie, nous allons changer les modificateurs fill()et overlay()les états pressés et non pressés. Donc, quand c'est isHighlightedvrai, changez à la darkStartfois darkEnden lightStartet lightEnd, comme ceci:

if isHighlighted {
    shape
        .fill(LinearGradient(Color.lightEnd, Color.lightStart))
        .overlay(shape.stroke(LinearGradient(Color.lightStart, Color.lightEnd), lineWidth: 4))
        .shadow(color: Color.darkStart, radius: 10, x: 5, y: 5)
        .shadow(color: Color.darkEnd, radius: 10, x: -5, y: -5)

Si vous exécutez à nouveau l'application, vous verrez qu'elle s'est déjà considérablement améliorée: l'état enfoncé a maintenant une couleur bleu vif, il devient donc clair lorsque les boutons sont enfoncés et que les commutateurs sont actifs. Mais nous pouvons faire autre chose - nous pouvons ajouter la même couleur autour du bouton lorsqu'il n'est pas enfoncé, ce qui permet d'attirer l'attention sur lui.



Pour ce faire, changez l'état overlay()non stressé existant en ceci:

.overlay(shape.stroke(LinearGradient(Color.lightStart, Color.lightEnd), lineWidth: 4))


Ainsi, le style de bouton fini devrait ressembler à ceci:

ZStack {
    if isHighlighted {
        shape
            .fill(LinearGradient(Color.lightEnd, Color.lightStart))
            .overlay(shape.stroke(LinearGradient(Color.lightStart, Color.lightEnd), lineWidth: 4))
            .shadow(color: Color.darkStart, radius: 10, x: 5, y: 5)
            .shadow(color: Color.darkEnd, radius: 10, x: -5, y: -5)
    } else {
        shape
            .fill(LinearGradient(Color.darkStart, Color.darkEnd))
            .overlay(shape.stroke(LinearGradient(Color.lightStart, Color.lightEnd), lineWidth: 4))
            .shadow(color: Color.darkStart, radius: 10, x: -10, y: -10)
            .shadow(color: Color.darkEnd, radius: 10, x: 10, y: 10)
    }
}

Maintenant, exécutez à nouveau l'application, et vous verrez qu'un anneau bleu est apparu autour des boutons et des commutateurs, et lorsque vous cliquez dessus, il est rempli de bleu - il est beaucoup plus accessible.



Conclusion


En pratique, vous n'aurez pas plusieurs styles de boutons différents dans un même projet, du moins si vous n'aimez pas créer un casse-tête pour vos utilisateurs. Mais c'est un espace d'expérimentation intéressant, et j'espère que vous saisirez cette idée de mon article, car vous pouvez créer de très beaux effets sans vous déchirer le dos.

J'ai dit à plusieurs reprises que vous devriez toujours surveiller la disponibilité de votre application, et cela signifie plus que simplement vous assurer que VoiceOver fonctionne avec votre interface utilisateur. Assurez-vous que vos boutons sont interactifs, assurez-vous que vos étiquettes de texte et vos icônes ont un rapport de contraste suffisant avec l'arrière-plan (au moins 4,5: 1, mais ont tendance à 7: 1), et assurez-vous que vos zones cliquables sont confortables et grand (au moins 44x44 pixels).

Et pour l'amour du ciel, utilisez le design néomorphe pour expérimenter et élargir vos connaissances sur SwiftUI, mais n'oubliez jamais que si vous sacrifiez la convivialité à une nouvelle tendance de design de mode, vous ne gagnez rien.

Vous pouvez obtenir le code source complet de ce projet sur GitHub .

Lisez la première partie.



En savoir plus sur le cours.



All Articles