Chris Codeblog

Tipps, Tricks & Tutorials rund ums Programmieren

[SwiftUI] Bindings

In SwiftUI gibt es sogenannte Bindings. Am Anfang bin ich nicht so ganz damit „zurecht“gekommen. Mittlerweile habe ich schon ein wenig in SwiftUI programmiert und ich möchte euch eine kleine Übersicht der „verfügbaren“ Bindings geben. Welche es gibt und für was sie verwendet werden.

  • @State – Spiegelt einen Status (z. B. einen Ein-/Ausschalter wieder)
  • @Binding – Ein Objekt, das von einer anderen View kommt und die Änderungen werden „zurückgegeben“
  • @EnvironmentObject – Ein Objekt, welches in verschiedene Views „injiziert“ werden kann
  • @ObservedObject – Ein Objekt, dessen als @Published gekennzeichneten Eigenschaften ein Neuzeichnen der View auslösen

Was das jetzt genau heißt? Das sehen wir uns in einzelnen Beispielen an.

@State

Eine als @State gekennzeichnete Variable kann wie der Name schon vermuten lässt einen „Status“ widerspiegeln. Als einfachstes Beispiel sei dafür ein Schalter genannt.

// ContentView.siwft

import SwiftUI

struct ContentView: View {
    @State var aktiviert = false
    
    var body: some View {
        VStack {
            Toggle(isOn: $aktiviert) {
                Text("Aktiviert")
            }
            .padding()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

In Zeile 6 definieren wir eine Variable und deklarieren sie als @State. Diese Variable spiegelt auch den Status des Schalter wieder. In Zeile 10 wird der Status dann übergeben. Wichtig dabei ist, dass ein @State bei der Verwendung mit einem $-Zeichen voran übergeben wird. Wird der Wert in Zeile 4 geändert, ändert sich auch der Status des Schalters. Wird zur Programmlaufzeit der Schalter verändert, wird der entsprechende Wert wieder zurück in die Variable geschrieben.

@Binding

@Binding ist ein @State-Variable, die an eine andere View übergeben wird. So kann z. B. ein Sheet geöffnet werden. Der Status wird als @Binding übergeben. Damit kann das Sheet nach erfolgreicher Aktion wieder geschlossen werden. Hier wieder ein Beispiel, damit ihr es euch besser vorstellen könnt:

// ContentView.wift

import SwiftUI

struct ContentView: View {
    @State var aktiviert = false
    @State var sheetVisible = false
    
    var body: some View {
        VStack {
            Toggle(isOn: $aktiviert) {
                Text("Aktiviert")
            }
            Button(action: {
                self.sheetVisible = true
            }) {
                Text("Sheet anzeigen")
            }
            .padding()
        }
        .sheet(isPresented: $sheetVisible) {
            SampleSheet(sheetVisible: self.$sheetVisible)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

In Zeile 7 habe ich eine weiter @State-Variable hinzugefügt, sheetVisible, die angibt, ob das Sheet angezeigt werden soll oder nicht. In Zeile 21 wird das Sheet definiert. sheetVisible wird für isPresented verwendet. Als anzuzeigende View soll SampleSheet verwendet werden. Auch hier wird sheetVisible übergeben, die Ziel-View nimmt diese Variable als @Binding entgegen, aber dazu gleich mehr. In Zeile 14 wird ein Button definiert. Dieser setzt die Variable sheetVisible auf true, somit wird das Sheet schlussendlich eingeblendet.

Kommen wir zu der View, die aufgerufen wird: SampleSheet:

// SimpleSheet.swift

import SwiftUI

struct SampleSheet: View {
    @Binding var sheetVisible: Bool
    
    var body: some View {
        Button(action: {
            self.sheetVisible = false
        }) {
            Text("Sheet schließen")
        }
    }
}

struct SampleSheet_Previews: PreviewProvider {
    @State static var sheetVisible = true
    
    static var previews: some View {
        SampleSheet(sheetVisible: $sheetVisible)
    }
}

Zeile 6: Die Variable sheetVisible wird als @Binding deklariert. Das ist auch die Variable, die von ContentView in Zeile 22 übergeben wird. In Zeile 9 wird wieder ein Button definiert. Wird dieser betätigt, wird die Variable sheetVisible auf false gesetzt. Da die Änderungen an ContentView „zurücksynchronisiert“ werden, wird auch das Sheet wieder ausgeblendet. Damit Xcode keinen Fehler produziert müsst ihr für die Vorschau ebenfalls die Variable deklarieren und übergeben. Wie in Zeile 18 und 21 zu sehen.

@ObservedObject

Als erstes legen wir uns eine neue Klasse an. Diese leitet von ObservedObject ab. Damit wird SwiftUI gesagt, dass es sich grundsätzlich um eine Klasse handelt, die überwacht werden kann.

// MySampleClass.swift

import Foundation

class MySampleClass: ObservableObject {
    @Published var text1 = "Text 1"
    @Published var text2 = "Text 2"
    var text3 = "Text 3"
}

Die Klasse enthält drei Eigenschaften text1, text2 und text3. Die ersten beiden Eigenschaften werden als @Published gekennzeichnet. Dadurch weiß SwiftUI, dass die Oberfläche neu gezeichnet werden muss, sobald sich an diesen Eigenschaften etwas ändert.

Verwendung in einer View:

import SwiftUI

struct ContentView: View {
    @State var aktiviert = false
    @State var sheetVisible = false
    @ObservedObject var mySampleClass = MySampleClass()
    
    var body: some View {
        VStack {
            Toggle(isOn: $aktiviert) {
                Text("Aktiviert")
            }
            Button(action: {
                self.sheetVisible = true
            }) {
                Text("Sheet anzeigen")
            }
            .padding()
            Text("MySampleClass.text1: \(self.mySampleClass.text1)")
            Text("MySampleClass.text2: \(self.mySampleClass.text2)")
            Text("MySampleClass.text3: \(self.mySampleClass.text3)")
            Button(action: {
                self.mySampleClass.text1 = "SampleClass Text 1"
                self.mySampleClass.text2 = "SampleClass Text 2"
                self.mySampleClass.text3 = "SampleClass Text 3"
            }) {
                Text("Text anpassen")
            }
            Button(action: {
                self.mySampleClass.text3 = "SampleClass Text 3"
            }) {
                Text("Nur Text 3 anpassen")
            }
        }
        .sheet(isPresented: $sheetVisible) {
            SampleSheet(sheetVisible: self.$sheetVisible)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

In Zeile 6 wird eine Instanz der gerade erstellten Klasse erzeugt. @ObservedObject gibt dabei an, dass die Klasse überwacht werden soll und bei Änderung einer @Published gekennzeichneten Eigenschaft ein Neuzeichnen der View durchgeführt werden muss. Wird die Klasse nicht als @Published gekennzeichnet, wird die View bei einer entsprechenden Änderung auch nicht neu gezeichnet.

In Zeile 19, 20 und 21 gebe ich die Texte einfach aus. Der Button in Zeile 22 setzt schließlich die Texte der Eigenschaften text1, text2 und text3 auf einen neuen Wert. Da text1 und text2 als @Published gekennzeichnet sind wird die komplette View neu gezeichnet. Deshalb wird auch der aktualisierte Wert der Eigenschaft text3 richtig angezeigt.

Wird hingegen der Button in Zeile 29 betätigt, wird die View nicht aktualisiert, also auch der neue Wert der Eigenschaft text3 nicht angezeigt, weil diese nicht als @Published gekennzeichnet wurde. Damit diese Änderung korrekt angezeigt wird, muss das Neuzeichnen der View durch ein anderes Ereignis ausgelöst werden.

@EnvironmentObject

Last but not least, wie man so schön sagt. Sehen wir uns das @EnvironmentObject etwas genauer an. Beim @EnvironmentObject handelt es sich um ein Binding, mit welchem Objekte in verschiedene Views injiziert werden können. Also wenn ihr Daten habt, die in vielen Views benötigt werden. Sehen wir uns wieder ein Beispiel an.

Als erstes ein kleines Objekt, das wir erstellen:

// MyEnvironmentObject.swift

import Foundation

class MyEnvironmentObject: ObservableObject {
    @Published var text1 = "Text 1"
    @Published var text2 = "Text 2"
}

Im Prinzip wird die Klasse genauso angelegt, wie bei der Verwendung als @ObservedObject. @Published kennzeichnet dabei wieder Eigenschaften, die bei Änderung ein Neuzeichnen der View auslösen sollen.

// SceneDelegate.swift

        let contentView = ContentView().environment(\.managedObjectContext, context).environmentObject(MyEnvironmentObject())

An die ContentView (bzw. die erste View, die von Swift erstellt wird), wird eine Instanz des gerade erzeugten Objekts übergeben, mit .environmentObject(MyEnvironmentObject()).

// ContentView.swift

import SwiftUI

struct ContentView: View {
    @State var aktiviert = false
    @State var sheetVisible = false
    @ObservedObject var mySampleClass = MySampleClass()
    @EnvironmentObject var myEnvironmentObject: MyEnvironmentObject
    
    var body: some View {
        NavigationView {
            VStack {
                Toggle(isOn: $aktiviert) {
                    Text("Aktiviert")
                }
                Button(action: {
                    self.sheetVisible = true
                }) {
                    Text("Sheet anzeigen")
                }
                Text("MySampleClass.text1: \(self.mySampleClass.text1)")
                Text("MySampleClass.text2: \(self.mySampleClass.text2)")
                Text("MySampleClass.text3: \(self.mySampleClass.text3)")
                Button(action: {
                    self.mySampleClass.text1 = "SampleClass Text 1"
                    self.mySampleClass.text2 = "SampleClass Text 2"
                    self.mySampleClass.text3 = "SampleClass Text 3"
                }) {
                    Text("Text anpassen")
                }
                Button(action: {
                    self.mySampleClass.text3 = "SampleClass Text 3"
                }) {
                    Text("Nur Text 3 anpassen")
                }
                Text("MyEnvironmentObject.text1: \(self.myEnvironmentObject.text1)")
                Text("MyEnvironmentObject.text1: \(self.myEnvironmentObject.text2)")
                NavigationLink("Neuer View", destination: NeuerView())
            }
            .padding()
            .sheet(isPresented: $sheetVisible) {
                SampleSheet(sheetVisible: self.$sheetVisible).environmentObject(self.myEnvironmentObject)
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(MyEnvironmentObject())
    }
}

In Zeile 9 wird das @EnvironmentObject deklariert. Wichtig dabei ist: Es wird nur deklariert, aber keine Instanz davon erzeugt. Und schon kann das Objekt verwendet werden. Damit Xcode keinen Fehler erzeugt müsst ihr das Objekt auch wieder bei der Preview übergeben (Zeile 51). Wollt ihr das Objekt in einem Sheet verwenden, muss es vom Parent explizit übergeben werden (Zeile 43). Wird eine andere View als NavigationLink aufgerufen (Zeile 39) ist das allerdings nicht der Fall.

Änderungen, die an dem Objekt vorgenommen werden, sind übrigens „Global“ gültig, da es sich immer um das selbe Objekt handelt. Hier noch die zwei anderen Views:

// SampleSheet.swift

import SwiftUI

struct SampleSheet: View {
    @Binding var sheetVisible: Bool
    @EnvironmentObject var myEnvironmentObject: MyEnvironmentObject
    
    var body: some View {
        VStack {
            Button(action: {
                self.sheetVisible = false
            }) {
                Text("Sheet schließen")
            }
            Text("MyEnvironmentObject.text1: \(self.myEnvironmentObject.text1)")
            Text("MyEnvironmentObject.text1: \(self.myEnvironmentObject.text2)")
        }
    }
}

struct SampleSheet_Previews: PreviewProvider {
    @State static var sheetVisible = true
    
    static var previews: some View {
        SampleSheet(sheetVisible: $sheetVisible).environmentObject(MyEnvironmentObject())
    }
}
// NeuerView.swift

import SwiftUI

struct NeuerView: View {
    @EnvironmentObject var myEnvironmentObject: MyEnvironmentObject
    
    var body: some View {
        VStack {
            Text("MyEnvironmentObject.text1: \(self.myEnvironmentObject.text1)")
            Text("MyEnvironmentObject.text2: \(self.myEnvironmentObject.text2)")
        }
    }
}

struct NeuerView_Previews: PreviewProvider {
    static var previews: some View {
        NeuerView().environmentObject(MyEnvironmentObject())
    }
}

Damit sind wir auch schon am Ende des Beitrags angelangt. War er hilfreich? Lob, Kritik, Anmerkungen? Darf alles gerne in die Kommentare!

[SwiftUI] Bindings

Schreibe einen Kommentar

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

Nach oben scrollen