SwiftUI: Do czego służy @EnvironmentObject?

Opublikowano: 18.01.2024 - tagi: SwiftUI Swift Widok Wrapper Dane

Czym jest @EnvironmentObject?

Ten wrapper służy do wymiany danych między wieloma widokami. Jest to szczególnie przydatne kiedy jeden widok składa się z wielu innych i na przykład do niektórych z nich musimy przekazać jakieś dane.

Przekazywanie danych z jednego widoku do drugiego, aż w końcu trafimy na ten, w którym te dane są potrzebne, staje się męczące. Ten problem rozwiązuje @EnvironmentObject.

Masz także gwarancję, że jeśli dane się zmienią, widok zostanie automatycznie o tym poinformowany.

Aby przejąć dane oznaczone przez @EnvironmentObject, będziesz musiał w widoku, który znajduje się wyżej w hierarchii wywołać metodę environmentObject i przekazać do niej obiekt, którym chcesz się dzielić z innymi widokami.

Najlepiej wszystko zobrazuje poniższy przykład.

Przykład

Mamy jeden model o nazwie Counter.

Trzy widoki w takiej hierarchii: ContentView -> InformationView -> CounterLabelView.

Widok CounterLabelView potrzebuje mieć dostęp do obiektu klasy Counter. Dlatego w tym widoku zostanie użyty @EnvironmentObject, aby przechwycić przesłane dane.

W widoku InformationView niepotrzebny jest obiekt klasy Counter. Zawiera on widok CounterLabelView.

W widoku głównym ContentView wywołujemy metodę environmentObject, żeby przekazać instancję klasy Counter do widoków potomnych.

Model:

import SwiftUI

class Counter: ObservableObject {
    @Published var value: Int = 0
    
    func increment() {
        value += 1
    }
    
    func decrement() {
        value -= 1
    }
    
    func reset() {
        value = 0
    }
}

Pierwszy widok:

import SwiftUI

struct CounterLabelView: View {
    @EnvironmentObject var counter: Counter
    
    var body: some View {
        Text("\(counter.value)")
            .font(.largeTitle)
    }
}

Drugi widok:

import SwiftUI

struct InformationView: View {
    var body: some View {
        VStack {
            CounterLabelView()
            Text("Click '+' to increment counter").padding()
            Text("Click '-' to decrement counter").padding()
        }
    }
}

Główny widok:

import SwiftUI

struct ContentView: View {
    @StateObject var counter = Counter()
    
    var body: some View {
        VStack {
            InformationView()
            HStack {
                Button("+") {
                    counter.increment()
                }.padding()
                
                Button("-") {
                    counter.decrement()
                }.padding()
            }
        }
        .environmentObject(counter)
    }
}

SwiftUI: Jak sprawdzić stan aplikacji?

Opublikowano: 16.01.2024 - tagi: iOS SwiftUI Swift Stan Widok Aplikacja

Stan aplikacji w SwiftUI

Żeby sprawdzić, w jakim stanie jest aplikacja, wystarczy użyć zmiennej: scenePhase za pomocą @Environment

Przykład:

import SwiftUI

struct ContentView: View {
    @Environment(\.scenePhase) private var scenePhase
    
    var body: some View {
        VStack {
                Text("App State")
        }
        .onChange(of: scenePhase) {
            print("State: \(scenePhase)")
        }
    }
}

Za pomocą onChange nasłuchiwana jest wartość zmiennej scenePhase. Wystarczy w symulatorze zmienić stan aplikacji i podejrzeć zmianę na konsoli.


SwiftUI: Do czego służy @Environment?

Opublikowano: 13.01.2024 - tagi: SwiftUI Swift Widok Wrapper Dane System

Czym jest @Environment?

SwiftUI pozwala widokowi na pobranie informacji o urządzeniu. Na przykład: jaki motyw graficzny ma ustawiony na swoim telefonie: jasny czy ciemny? Jakiego język jest ustawiony na urządzeniu?

Pozyskując takie informacje, możesz wykorzystać je w swojej aplikacji.

Za pobranie informacji systemowych odpowiedzialny jest wrapper: @Environment.

Pełną listę informacji, jakie możesz pobrać znajdziesz w EnvironmentValues

Przykład

import SwiftUI

struct ContentView: View {
    @Environment(\.colorScheme) private var colorScheme
    
    var body: some View {
        VStack {
            Button(colorScheme == .light ? "Light" : "Dark") {
                
            }
            .frame(width: 100, height: 50)
            .foregroundColor(colorScheme == .light ? .white : .black)
            .background(colorScheme == .light ? .black : .white)
        }
    }
}

W tym przykładzie za pomocą @Environment pobierana jest wartość z colorScheme, która zawiera informację, jaki motyw graficzny jest ustawiony w urządzeniu.

Na podstawie tego stylizowany jest odpowiednio przycisk.


iOS: Wszystkie stany aplikacji

Opublikowano: 11.01.2024 - tagi: iOS Stan

Stan aplikacji

Aplikacja napisana pod iOS może być w następującym stanie:

Non-running

Ten stan oznacza, że aplikacja nie jest uruchomiona.

Inactive

Oznacza, ze aplikacja jest uruchomiona na pierwszym planie (ang.: foreground), ale użytkownik nie ma z nią interakcji. Aplikacja w takim stanie nie odbiera zdarzeń.

Program wchodzi w ten stan na krótko przed przejściem do innego stanu na przykład w tryb background.

Przykład: Użytkownika używa aplikacji i chce się przełączyć do innego programu — aplikacja najpierw wchodzi w tryb inactive, a następnie background.

Active

Aplikacja działa na pierwszym planie, użytkownik ma z nią interakcję i odbiera ona zdarzenia.

Background

Aplikacja jest wprawdzie uruchomiona, ale jest "schowana" - działa w tle. Użytkownik nie ma z nią interakcji. Kod aplikacji jest wykonywany.

W ten stan aplikacja przechodzi na przykład, gdy użytkownik przełącza się na inny program.

Suspended

Aplikacja jest uruchomiona w tle, ale jej kod nie jest wykonywany.


SwiftUI: Do czego służy @ObservedObject?

Opublikowano: 09.01.2024 - tagi: SwiftUI Swift Widok Stan Wrapper Klasa Obiekt

Czym jest @ObservedObject?

Ten wrapper jest bardzo podobny do @StateObject .

Przyjmuje typy referencyjne jak klasy.

Różnica między nimi jest taka, że @ObservedObject powinieneś użyć, w przypadku wymiany danych między widokami. Kiedy chcesz przekazać z jednego widoku typ referencyjny do innego.

Przykłady

Przykład 1

Model:

class Counter: ObservableObject {
    @Published var value: Int = 0
    
    func increment() {
        value += 1
    }
    
    func decrement() {
        value -= 1
    }
    
    func reset() {
        value = 0
    }
}

Widok (dziecko):

import SwiftUI

struct CounterView: View {
    @ObservedObject var counter: Counter
    
    var body: some View {
        VStack {
            Text("\(counter.value)")
                .font(.largeTitle)
            HStack {
                Button("+") {
                    counter.increment()
                }.padding()
                
                Button("-") {
                    counter.decrement()
                }.padding()
            }
        }
    }
}

Widok (rodzic):

import SwiftUI

struct ContentView: View {
    @StateObject private var counter = Counter()
		
    var body: some View {
        VStack {
            CounterView(counter: counter).padding()
            Button("RESET") {
                counter.reset()
            }
        }
    }
}

#Preview {
    ContentView()
}

Przykład 2: Uwaga na @ObservedObject!

Ponieważ @ObservedObject jest bardzo podobne do @StateObject często są ze sobą mylone. Niepoprawne użycie @ObservedObject może powodować nieoczekiwane wyniki.

Przykład:

import SwiftUI

struct CounterView: View {
    @ObservedObject var counter = Counter()
    
    var body: some View {
        VStack {
            Text("\(counter.value)")
                .font(.largeTitle)
            HStack {
                Button("+") {
                    counter.increment()
                }.padding()
                
                Button("-") {
                    counter.decrement()
                }.padding()
            }
        }
    }
}

W widoku CounterView tworzymy instancję obiektu klasy Counter. Do CounterView nie będzie przekazywany żaden parametr.

Teraz drugi widok:

import SwiftUI

struct ContentView: View {
    @State private var text: String = ""
    
    var body: some View {
        VStack {
            Text("Your text: \(text)")
            TextField("Type something", text: $text)
        }.padding()
				
        CounterView()
    }
}

#Preview {
    ContentView()   
}

Widok ContentView zawiera CounterView nic do niego nie jest przekazywane.

Jeśli użytkownik zmieni wartość licznika, a następnie wpisze jakiś tekst w pole tekstowe, to licznik... zostanie zresetowany do 0.

Dlaczego tak się dzieje?

Po pierwsze widok ContentView zawiera zmienną text. Jeśli wartość tej zmiennej zostanie zmieniona, stan widoku także ulegnie zmianie. To znaczy, że widok zostanie przerysowany.

Przerysowanie powoduje, że dla zmiennej oznaczonej jako @ObservedObject (w tym przypadku w widoku CounterView) poprzednia instancja klasy Counter zostanie zniszczona i stworzona nowa. To właśnie powoduje ten reset.

Można temu zaradzić. Wystarczy, że zamienisz w widoku CounterView linijkę:

@ObservedObject var counter = Counter()

na:

@StateObject var counter = Counter()

lub zaimplementujesz, w taki sposób, jak pokazane jest w pierwszym przykładzie.

Wystarczy trzymać się zasady: jeśli potrzebujesz przekazać do jakiegoś widoku typ referencyjny (na przykład obiekt klasy) użyj @ObservedObject.