Neomorphismus mit SwiftUI. Teil 2: Was kann mit Barrierefreiheit gemacht werden?

Hallo alle zusammen! Im Vorgriff auf den Start des Fortgeschrittenenkurses „IOS Developer“ veröffentlichen wir eine Übersetzung des zweiten Teils des Artikels über Neomorphismus mit SwiftUI ( lesen Sie den ersten Teil ).





Dunkles Thema


Bevor wir verstehen, wie wir die Verfügbarkeit von Neomorphismus verbessern können, wollen wir uns ansehen, wie wir mit dem Effekt arbeiten können, um andere interessante Stile zu erstellen.

Fügen Sie der Erweiterung zunächst zwei weitere Farben hinzu, Colordamit wir mehrere konstante Werte zur Verfügung haben:

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)

Wir können sie als Hintergrund verwenden ContentView, um die vorhandene zu ersetzen Color.white, wie hier gezeigt:

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

Unsere SimpleButtonStylesieht jetzt fehl am Platz aus, weil sie einem dunklen Hintergrund eine helle Stilisierung auferlegt. Wir werden also einen neuen dunklen Stil erstellen, der hier besser funktioniert, aber dieses Mal werden wir ihn in zwei Teile unterteilen: eine Hintergrundansicht, die wir überall verwenden können, und einen Schaltflächenstil, der ihn mit den Modifikatoren padding und contentShape umschließt. Dies gibt uns mehr Flexibilität, wie Sie später sehen werden.

Mit der neuen Hintergrundansicht, die wir hinzufügen werden, können wir eine beliebige Form für unseren visuellen Effekt festlegen, sodass wir nicht mehr an Kreise gebunden sind. Abhängig von der Eigenschaft isHighlighted, die wir von außen ändern können, wird auch nachverfolgt, ob unser konvexer Effekt (nach innen oder nach außen) konkav sein soll .

Wir beginnen mit dem einfachsten und verwenden einen modifizierten Shadow-Flip-Ansatz, um einen konkaven Effekt zu erzielen. Fügen Sie diese Struktur hinzu:

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)
            }
        }
    }
}

Die Änderung besteht darin, dass beim Drücken der Taste die Größe des Schattens abnimmt - wir verwenden einen Abstand von 5 statt 10 Punkten.

Anschließend können wir ihn mit Hilfe von DarkButtonStylePadding und ContentShape umbrechen, wie hier gezeigt:

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

Schließlich können wir dies auf unseren Button in anwenden, ContentViewindem wir ihn ändern ButtonStyle():

.buttonStyle(DarkButtonStyle())

Mal sehen, was passiert ist - obwohl wir nicht viel Code haben, denke ich, dass das Ergebnis gut genug aussieht.



Einige Experimente


Jetzt ist es an der Zeit, mit dem Effekt zu experimentieren, da Sie so besser verstehen, wozu SwiftUI speziell in der Lage ist.

Zum Beispiel könnten wir eine glatte konvexe Schaltfläche erstellen, indem wir einen linearen Farbverlauf hinzufügen und ihn beim Drücken umdrehen:

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)
}

Wenn Sie dies ausführen, werden Sie feststellen, dass die Schaltfläche beim Drücken und Loslassen reibungslos nach oben und unten animiert wird. Ich denke , die Animation ein wenig störend ist, so dass ich es empfehlen , zu deaktivieren , indem Sie diesen Modifikator auf das Verfahren makeBody()aus DarkButtonStyle, nach dem Modifikator vorhanden dort background():

.animation(nil)



Dieser Effekt des Kissenknopfs ist reizvoll, aber wenn Sie ihn verwenden möchten, würde ich Ihnen raten, die folgenden drei Änderungen zu versuchen, um den Knopf etwas mehr hervorzuheben.

Erstens würde ich trotz der Tatsache, dass dies dem kontrastarmen Prinzip des neomorphen Designs widerspricht, das graue Symbol durch ein weißes ersetzen, um es hervorzuheben. Also ContentViewmüssen Sie die folgende:

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

Zweitens, wenn Sie eine Überlagerung für die Schaltfläche im gedrückten Zustand hinzufügen, sieht sie nicht nur eher wie eine echte physische Taste aus, die gleichmäßig gedrückt wird, sondern hilft auch dabei, ihren gedrückten Zustand von dem nicht gedrückten zu unterscheiden.

Um dies zu implementieren, müssen Sie den Modifikator einzufügen overlay()nach , fill()wenn isHighlightedes wahr ist , wie hier:

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)



Um ein noch schärferes Aussehen zu erzielen, können Sie zwei Modifikatoren shadow()für den gedrückten Zustand entfernen , die sich ansonsten auf die Überlagerung konzentrieren.

Drittens können Sie dem nicht gedrückten Status auch eine Überlagerung hinzufügen, um zu markieren, dass es sich um eine Schaltfläche handelt. Setzen Sie es gleich danach fill()wie folgt ein:

} 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)
}



Hinzufügen eines Switch-Stils


Einer der Vorteile der Trennung eines Schaltflächenstils von einem nicht morphischen Hintergrundstil besteht darin, dass wir jetzt einen Schaltstil mit demselben Effekt hinzufügen können. Dies bedeutet, dass eine neue protokollkonforme Struktur erstellt wird ToggleStyle, die der folgenden ähnelt ButtonStyle:

  1. Wir müssen lesen, configuration.isOnum festzustellen, ob der Schalter eingeschaltet ist.
  2. Wir müssen einen Knopf bereitstellen, um den Vorgang des Schaltens zu handhaben, oder zumindest so etwas onTapGesture()oder so etwas .

Fügen Sie diese Struktur Ihrem Projekt hinzu:

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())
        )
    }
}

Wir möchten eine davon ContentVieweinfügen, damit Sie sie selbst testen können. Fügen Sie zunächst diese Eigenschaft hinzu:

@State private var isToggled = false

Wickeln Sie dann die vorhandene Schaltfläche VStackmit einem Abstand von 40 ein und platzieren Sie sie unten:

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

Ihre Struktur ContentViewsollte folgendermaßen aussehen:

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)
    }
}

Und das ist alles - wir haben unser gemeinsames neomorphes Design an zwei Stellen angewendet!

Verbesserung der Zugänglichkeit


Wir hatten genug Zeit, um mit verschiedenen neomorphen Stilen zu spielen, aber jetzt möchte ich auf die Probleme dieses Designs eingehen: Ein extremer Mangel an Kontrast bedeutet, dass Tasten und andere wichtige Steuerelemente nicht ausreichen, um sich von ihrer Umgebung abzuheben, was es schwierig macht, unsere Anwendungen für Menschen mit zu verwenden unvollkommene Sicht.

Dies ist der Moment, um den ich ein Missverständnis beobachte, deshalb möchte ich im Voraus einige Dinge sagen:

  1. Ja, ich verstehe, dass Standard-Apple-Tasten wie blauer Text aussehen und daher zumindest auf den ersten Blick nicht wie bekannte Tasten aussehen, aber ein hohes Kontrastverhältnis aufweisen.
  2. « , , » — — « », , , - .
  3. - SwiftUI, , VoiceOver Apple.

Sie haben bereits gesehen, wie wir das graue Symbol in Weiß geändert haben, um eine sofortige Kontrastverbesserung zu erzielen. Die Schaltflächen und Schalter benötigen jedoch noch viel mehr Kontrast, wenn Sie sie leichter zugänglich machen möchten.

Wir werden uns also überlegen, welche Änderungen wir vornehmen könnten, damit die Elemente wirklich hervorstechen.

Zunächst möchte ich, dass Sie unserer Erweiterung zwei neue Farben hinzufügen:

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)

Zweitens duplizieren Sie die vorhandene DarkBackgroundund benennen Sie die Kopie ColorfulBackground. Wir werden uns gleich darum kümmern, aber zuerst müssen wir uns wieder vorbereiten.

Drittens duplizieren Sie den dunklen Stil der Schaltfläche und wechseln Sie, benennen Sie sie in ColorfulButtonStyleund um ColorfulToggleStyleund lassen Sie sie dann den neuen ColorfulBackgroundals Hintergrund verwenden.

Sie sollten also so aussehen:

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())
        )
    }
}

Und schließlich bearbeiten Sie die Schaltfläche und wechseln Sie ContentView, um den neuen Stil zu verwenden:

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

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

Sie können die Anwendung ausführen, wenn Sie möchten, aber es macht nicht viel Sinn - sie hat sich nicht geändert.

Um unsere farbenfrohe Version des Lebens zu bringen, werden wir die Modifikatoren fill()und overlay()die gepressten und nicht gepressten Zustände ändern . Also, wenn isHighlightedwahr ist , ändern darkStartbeide darkEndauf lightStartund lightEnd, wie folgt aus :

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)

Wenn Sie die Anwendung erneut ausführen, werden Sie feststellen, dass sie sich bereits erheblich verbessert hat: Der gedrückte Zustand hat jetzt eine hellblaue Farbe, sodass klar wird, wenn die Tasten gedrückt werden und die Schalter aktiv sind. Aber wir können noch etwas anderes tun - wir können der Taste dieselbe Farbe hinzufügen, wenn sie nicht gedrückt wird, um die Aufmerksamkeit darauf zu lenken.



Ändern Sie dazu den vorhandenen overlay()nicht gestressten Zustand wie folgt:

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


Der fertige Schaltflächenstil sollte also folgendermaßen aussehen:

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)
    }
}

Führen Sie nun die Anwendung erneut aus, und Sie werden sehen, dass ein blauer Ring um die Schaltflächen und Schalter angezeigt wurde. Wenn Sie darauf klicken, wird er mit Blau gefüllt - er ist viel leichter zugänglich.



Fazit


In der Praxis werden Sie nicht mehrere verschiedene Schaltflächenstile in einem Projekt haben, zumindest wenn Sie Ihren Benutzern keine Kopfschmerzen bereiten möchten. Aber dies ist ein interessanter Ort zum Experimentieren, und ich hoffe, dass Sie diese Idee meines Artikels verstehen, denn Sie können wirklich schöne Effekte erzielen, ohne sich den Rücken zu brechen.

Ich habe wiederholt gesagt, dass Sie immer die Verfügbarkeit Ihrer Anwendung überwachen sollten, und das bedeutet mehr als nur sicherzustellen, dass VoiceOver mit Ihrer Benutzeroberfläche funktioniert. Stellen Sie sicher, dass Ihre Schaltflächen interaktiv aussehen, dass Ihre Textbeschriftungen und Symbole ein ausreichendes Kontrastverhältnis zum Hintergrund aufweisen (mindestens 4,5: 1, aber tendenziell 7: 1), und stellen Sie sicher, dass Ihre anklickbaren Bereiche komfortabel sind groß (mindestens 44x44 Pixel).

Und um Himmels willen, nutzen Sie das neomorphe Design, um zu experimentieren und Ihr Wissen über SwiftUI zu erweitern. Vergessen Sie jedoch nie, dass Sie nichts gewonnen haben, wenn Sie die Benutzerfreundlichkeit für einen neuen Modedesign-Trend opfern.

Den vollständigen Quellcode für dieses Projekt erhalten Sie auf GitHub. W

Lesen Sie den ersten Teil.



Erfahren Sie mehr über den Kurs.



All Articles