SwiftUI in der Praxis – Teil 9

Umgang mit der virtuellen Bildschirmtastatur

SwiftUI ist in aller Munde und Entwickler erwarten bereits mit Spannung die Neuerungen, die Apple dem UI-Framework zu seiner Entwicklerkonferenz WWDC Ende Juni bescheren wird.

Ein SwiftUI-Update ist auch wichtig, denn das Framework besitzt noch das ein oder andere Problemchen und ist auch in Sachen Funktionsumfang nicht mit den Möglichkeiten von AppKit und UIKit vergleichbar.

Eines dieser Probleme, das mir bereits des Öfteren zu Ohren gekommen ist, ist der Umgang mit der Bildschirmtastatur bei Texteingaben. Die Bildschirmtastatur überlagert nämlich in diesem Fall einfach die bestehenden Inhalte, was auch das entsprechende Textfeld selbst betreffen kann, wenn es sich eher im unteren Bereich einer View befindet.

Wie geht man also mit der Bildschirmtastatur in SwiftUI um? Wie aktualisiert man die Größe einer View, wenn der Platz durch die Bildschirmtastatur begrenzt ist?

Die Lösung, die ich euch im Folgenden vorstelle, basiert auf einem der wichtigsten Konzept von SwiftUI: dem Status. Als Basis dient entsprechend eine neue Klasse namens KeyboardResponder, die uns darüber informiert, ob die Bildschirmtastatur sichtbar ist oder nicht. Dazu bringt die Klasse eine passende Eigenschaft namens keyboardHeight mit, die die Höhe der Bildschirmtastatur widerspiegelt und einen Standardwert von 0 besitzt (was bedeutet, dass die Tastatur nicht sichtbar ist).

Der Clou der KeyboardResponder-Klasse liegt in ihrer Initialisierung. Sie registriert sich für die Notifications UIResponder.keyboardWillShowNotification und UIResponder.keyboardWillHideNotification. Die werden nämlich auch ausgelöst, wenn die Bildschirmtastatur aus einer SwiftUI-View heraus auftaucht.

Wird eine der Notifications gefeuert, erfolgt eine passende Aktualisierung der keyboardHeight-Property. Bei Erscheinen der Tastatur wird ihr die entsprechende Höhe zugewiesen, beim Ausblenden wird sie auf 0 zurückgesetzt.

Zusätzlich steht eine Hilfsmethode namens bottomInset(includingHeight:) zur Verfügung. Die kann man nutzen, um die Höhe, um die man eine View verkleinern muss, um die Bildschirmtastatur darstellen zu können, entsprechend zu verkleinern. Das ist beispielsweise sinnvoll, wenn eine View Teil einer TabView ist. Die Höhe, die die Tab-Bar einnimmt, kann man beim Verringern der Höhe einer View innerhalb der TabView ignorieren, da sich die Bildschirmtastatur einfach darüber legen kann. Übergibt man der bottomInset(includingHeight:)-Methode eine entsprechende Information, kümmert sie sich automatisch um die korrekte Kalkulation der Höhe, um die eine View verkleinert werden muss.

final class KeyboardResponder: ObservableObject {
    @Published var keyboardHeight: CGFloat = 0

    init(center: NotificationCenter = .default) {
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
    }
    
    func bottomInset(includingHeight includedHeight: CGFloat) -> CGFloat {
        if keyboardHeight == 0 {
            return keyboardHeight
        }
        return keyboardHeight - includedHeight
    }

    @objc private func keyboardWillShow(notification: Notification) {
        if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
            keyboardHeight = keyboardSize.height
        }
    }

    @objc private func keyboardWillHide(notification: Notification) {
        keyboardHeight = 0
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

Praxiseinsatz von KeyboardResponder

Mithilfe der KeyboardResponder-Klasse können wir nun aus jeder beliebigen SwiftUI-View heraus das Ein- und Ausblenden der Bildschirmtastatur abfangen. Dazu müssen wir lediglich eine Instanz der Klasse als Status einbinden.

Ein komplexes, dafür aber auch umso anschaulicheres Beispiel zum Einsatz von KeyboardResponder findet ihr im nachfolgenden Listing. Es basiert auf einer Form-View, die sich aus insgesamt 30 Textfeldern zusammensetzt. Die Form-View ist Teil einer NavigationView und die wiederum Teil einer TabView.

Das Wichtigste zuerst: Um die Höhe der Form-View bei Erscheinen der Bildschirmtastatur anzupassen, nutze ich den padding()-Modifier. Über den Parameter .bottom lege ich fest, dass die View nur vom unteren Bereich her zugeschnitten werden soll. Die Höhe dieser Anpassung zu ermitteln ist jedoch etwas kompliziert, da ich die Höhe der zugrundeliegenden Tab-Bar ignorieren möchte (die Bildschirmtastatur legt sich ja sowieso darüber, entsprechend verkleinert sich die Höhe, um die ich die Form-View verringern muss).

Um die Höhe der Tab-Bar zu ermitteln, nutze ich zwei GeometryReader. Der erste mit dem Parameter tabViewGeometryReader liefert mir die Höhe der TabView zurück, während ich über den zweiten Parameter (navigationViewGeometry) die Höhe der NavigationView (ohne die Tab-Bar) ermittle. Die Subtraktion von TabView und NavigationView liefert mir so die Höhe der Tab-Bar zurück, und genau die schließe ich bei der Reduzierung der Höhe der Form-View aus. Zu diesem Zweck nutze ich die zuvor beschriebene Hilfsmethode bottomInset(includingHeight:) der KeyboardResponder-Klasse.

Wichtig: Ich berücksichtige bei der genannten Kalkulation auch die Safe Area. Deren Wert ist bei Geräten mit abgerundetem Display wie dem iPhone 11 Pro relevant, da diese auch noch einmal einen Platz beansprucht, den ich beim Verkleinern der Form-View ignorieren kann.

struct ContentView: View {
    @State private var someValue = ""
    
    @ObservedObject private var keyboardResponder = KeyboardResponder()
    
    var body: some View {
        GeometryReader { tabViewGeometryProxy in
            TabView {
                GeometryReader { navigationViewGeometryProxy in
                    NavigationView {
                        Form {
                            ForEach(0 ..< 30) { value in
                                TextField("TextField \(value)", text: self.$someValue)
                            }
                        }
                        .navigationBarTitle("Keyboard Test")
                        .padding(.bottom, self.keyboardResponder.bottomInset(includingHeight: (tabViewGeometryProxy.size.height + tabViewGeometryProxy.safeAreaInsets.bottom - navigationViewGeometryProxy.size.height)))
                    }
                }
                .tabItem {
                    Image(systemName: "book.fill")
                    Text("Keyboard Test")
                }
            }
        }
    }
}

Fazit

Mithilfe der passenden Notifications aus dem UIKit-Framework lässt sich auch unter SwiftUI das Ein- und Ausblenden der virtuellen Bildschirmtastatur steuern. Verknüpft man diese Notifications mit einem Status, aktualisieren sich automatisch alle Views, für die diese Information relevant ist.

Wichtig ist nur, sich bei der Kalkulation der Höhe, um die eine View zu verkleinern ist, genaue Gedanken zu machen. Ist eine View wie im gezeigten Fall Teil einer Tab-Bar, zieht man deren Höhe typischerweise von der Höhe der Bildschirmtastatur ab; schließlich wird die Tab-Bar sowieso von der Bildschirmtastatur überlagert.

Nichtsdestotrotz kann man sich so eine komfortable Lösung für das beschriebene Problem bauen, die darüber hinaus dynamisch für verschiedene Szenarien einsetzbar ist.

Euer Thomas


Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert