Czym jest @Binding?
W SwiftUI możesz łatwo tworzyć aplikacje za pomocą kilku widoków. Jeden widok może składać się z kilku innych.
W niektórych przypadkach będzie potrzeba dzielenia się danymi między widokiem: rodzic a widokiem: dziecko. Ale to nie wszystko. Może być taka potrzeba, że wartość przekazana między widokami powinna być modyfikowana przez oba widoki, a ewentualne zmiany, powinny być widoczne w obu widokach.
Można to ująć krócej: @Binding pozwala na stworzenie komunikacji dwukierunkowej między widokami.
Użycie @Binding idzie w parze z użyciem @State. @State używasz w widoku rodzica, a @Binding w widoku dziecka.
Przykład
Pierwszy widok:
import SwiftUI
struct CounterView: View {
@Binding var counter: Int
var body: some View {
VStack {
Text("\(counter)")
.font(.largeTitle)
HStack {
Button("+") {
counter += 1
}.padding()
Button("-") {
counter -= 1
}.padding()
}
}
}
}
Za pomocą @Binding jest tworzone wiązanie dwukierunkowe między widokami.
Drugi widok:
import SwiftUI
struct ContentView: View {
@State private var counter: Int = 0
var body: some View {
VStack {
CounterView(counter: $counter).padding()
Button("RESET") {
counter = 0
}
}
}
}
#Preview {
ContentView()
}
Zmienna counter przekazywana jest do widoku CounterView. Ponieważ ten widok, jako parametr przyjmuje wiązanie, musisz użyć znaku dolara: $counter.
Kiedy stan zmiennej counter zostanie zmieniony w widoku CounterView będzie to widoczne także w widoku rodzica (ContentView).
Wartość licznika można też zmienić w ContentView i ta zmiana także zostanie przekazana do widoku dziecka (CounterView).
Preview i @Binding
Zakładając, że widok przyjmuje parametr jako @Binding, jak należy przekazać wartość w Preview?
Nie możesz zrobić tak:
#Preview {
CounterView(arg: 0)
}
Zostanie zgłoszony błąd!
Żeby rozwiązać ten problem, możesz:
a) Użyć Binding.constant. Tworzy ona wiązanie z niezmienną wartością.
#Preview {
CounterView(arg: .constant(10))
}
Problem z tym rozwiązaniem jest taki, że nie możesz zmienić wartości przekazanej jako constant w widoku.
Możesz to obejść za pomocą drugiego sposobu:
b) Przekazać wartość w zmiennej/stałej używając znaku dolara: $:
#Preview {
struct PreviewWrapper: View {
@State var counter: Int = 1
var body: some View {
CounterView(counter: $counter)
}
}
return PreviewWrapper()
}
Enumeracja
Enumeracja to zbiór wartości zgrupowanych w typie zdefiniowanym przez użytkownika. W Swift enumerację tworzy się za pomocą słowa kluczowego: enum:
enum TimerState {
case start, pause, stop
}
Przy pomocy case określasz jakie wartości będzie mieć Twoja enumeracja.
Przykład:
enum TimerState {
case start, pause, stop
}
var timerState: TimerState = .start
print("timer state: \(timerState)")
Zauważ, że opcja start ma wartość... start. Jest to domyślne zachowanie.
Możesz jednak nadać opcji inną wartość niż ta domyślna. To tzw.: surowe wartości (ang.: raw values)
Surowe wartości
enum ClothesSize: String {
case S = "Small"
case M = "Medium"
case L = "Large"
}
var size: ClothesSize = .M
print(size)
print(size.rawValue)
Dla enumeracji ClothesSize zostały zdefiniowane trzy opcje. Do każdej opcji przypisane są wartości: Small, Medium, Large. To są właśnie wartości surowe.
Żeby się do nich dostać, użyj właściwości rawValue.
Opcjom enumeracji możesz nadać typ:
enum ClothesSize: Int {
case S, M, L
}
Jaką surową wartość będzie mieć opcja S? Będzie to 0, a kolejne 1 i 2.
Możesz też napisać tak:
enum ClothesSize: Int {
case S = 1, M, L
}
print(ClothesSize.S.rawValue)
Swift "domyśli" się, że kolejne wartości będą to 2 i 3.
Iteracja po enum
Możesz także iterować po wszystkich opcjach enumeracji. Żeby to zrobić, musisz rozszerzyć enumerację za pomocą protokołu: CaseIterable:
enum TimerState: CaseIterable {
case start, pause, stop
}
for current in TimerState.allCases {
print(current)
}
Żeby iterować po enumeracji, użyj właściwości allCases.
Możesz także iterować po surowych wartościach:
enum ClothesSize: String, CaseIterable {
case S = "Small"
case M = "Medium"
case L = "Large"
}
for current in ClothesSize.allCases {
print(current.rawValue)
}
Switch i enum
Do obsługi opcji możesz użyć instrukcji switch:
enum TimerState {
case start, pause, stop
}
var timerState: TimerState = .pause
switch timerState {
case .start:
print("Start timer")
case .pause:
print("Pause timer")
case .stop:
print("Stop timer")
}
Właściwości w enum
W enumeracji możesz użyć computed property:
enum TimerState {
case start, pause, stop
var label: String {
switch self {
case .start:
return "Timer s running"
case .pause:
return "Timer is paused"
case .stop:
return "Timer is stopped"
}
}
}
var timerState: TimerState = .start
print(timerState.label)
Za pomocą słowa kluczowego self pobierasz wartość, która przechowuje zmienna przy wywołaniu label.
Metody w enum
Swift umożliwia także używanie metod w enum:
enum TimerState {
case start, pause, stop
func getLabel() -> String {
switch self {
case .start:
return "Timer s running"
case .pause:
return "Timer is paused"
case .stop:
return "Timer is stopped"
}
}
}
var timerState: TimerState = .stop
print(timerState.getLabel())
Powiązane wartości
Enumeracja pozwala dołączyć do każdej opcji dodatkowe informacje. Nazywa się to: powiązane wartości (ang.: associated values).
Składnia wygląda następująco:
case someOption(Int)
Po podaniu nazwy opcji w nawiasach określasz dodatkową informację. Możesz dodać wiele opcji:
case someOption(Int, Int, String)
Dla jednego parametru możesz określić jakiś typ danych, a dla następnego użyć innego typu. Dodatkowo opcje mogą mieć różną liczbę parametrów:
case someOption(Int)
case otherOption(String, Double)
Przykład:
enum TimerState {
case start(String)
case pause(String)
case stop(String)
}
var timerState: TimerState = .start("Timer is starting")
switch timerState {
case .start(let label):
print("Label for start: \(label)")
case .pause(let label):
print("Label for pause: \(label)")
case .stop(let label):
print("Label for stop: \(label)")
}
Możesz także nadać etykiety powiązanym wartościom:
case someOption(value: Int)
Kolejny przykład:
enum ClothesSize {
case S(height: Int)
case M(height: Int)
case L(height: Int)
}
var size: ClothesSize = .S(height: 165)
switch size {
case .S(let height):
print("Height for S:", height)
case .M(let height):
print("Height for M:", height)
case .L(let height):
print("Height for L:", height)
}
Nie możesz używać powiązanych wartości wraz z surowymi wartościami, w tym samym czasie.