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.


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

Opublikowano: 06.01.2024 - tagi: SwiftUI Swift Widok Stan Protokół

Czym jest @StateObject?

Klasy w Swift są typami referencyjnymi. Jeśli potrzebujesz w swoim widoku mieć właściwość, która jest obiektem klasy powinieneś oznaczyć go jako @StateObject.

Właściwość oznaczona jako @StateObject sprawia, że ten obiekt będzie obserwowany. A właściwie to stan obiektu. Kiedy stan obiektu się zmieni, widok zostanie zaktualizowany. Dodatkowo używając tego wrappera, informujesz w ten sposób, że widok jest właścicielem tego obiektu.

Używając @StateObject masz pewność obiekt nie zostanie zniszczony, kiedy widok jest aktualizowany.

Obiekt, który jest oznaczony jako @StateObject musi być zgodny z protokołem ObservableObject.

class MyClass: ObservableObject {
	...
}	

Dzięki temu informujesz SwiftUI o ewentualnej zmianie stanu obiektu tej klasy w wyniku czego widok zostanie przerysowany.

To jeszcze nie wszystko. Potrzebujesz oznaczyć właściwość klasy jako @Published:

class MyClass: ObservableObject {
	@Published var value = 0
}	

Za pomocą @Published wskazujesz konkretnie, które właściwości klasy mają być monitorowane.

Przykład

Model:

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

Widok:

struct ContentView: View {
    @StateObject private var counter = Counter(value: 0)
    
    var body: some View {
        VStack {
            Text("\(counter.value)")
                .font(.largeTitle)
            HStack {
                Button("+") {
                    counter.increment()
                }.padding()
                
                Button("-") {
                    counter.decrement()
                }.padding()
            }
            
            Button("RESET") {
                counter.reset()
            }
        }
    }
}

#Preview {
    ContentView()
}

Tworzenie obiektu w init

Patrząc na powyższy przykład, załóżmy, że potrzebujesz w widoku ContentView stworzyć instancję klasy Counter w funkcji init ContentView. Bo na przykład nie wiesz, jaka ma być wartość początkowa.

Jak można to zrobić?

Może tak?:

struct ContentView: View {
    @StateObject private var counter: Counter
    
    init(counter: Int) {
        counter = Counter(value: counter)
    }
}

#Preview {
    ContentView(counter: 7)    
}

Niestety nie. Edytor zgłosi błąd.

Można to jednak obejść:

struct ContentView: View {
    @StateObject private var counter: Counter
    
    init(counter: Int) {
        _counter = StateObject(wrappedValue: Counter(value: counter))
    }
}

#Preview {
    ContentView(counter: 7)    
}

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

Opublikowano: 04.01.2024 - tagi: SwiftUI Swift Widok Stan

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

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

Opublikowano: 02.01.2024 - tagi: SwiftUI Swift Widok Stan

Czym jest @State?

W SwiftUI widoki są tworzone za pomocą struktur. Struktury w Swift domyślnie są niezmienne (ang.: immutable). Dzięki @State możesz określić, która właściwość będzie mogła zmienić stan widoku.

Używa się tego do właściwości widoku:

struct SomeView: View {
	@State private someProperty: String = ""
}

Kiedy oznaczysz jakąś właściwość jako @State, informujesz w ten sposób SwiftUI, że ma ona być nasłuchiwana i przy każdej zmianie wartości widok ma być odświeżony.

@State powinno się używać dla typów prostych jak: stringi, liczby lub tablice.

Przykład

import SwiftUI

struct ContentView: View {
    @State private var counter: Int = 0
    var body: some View {
        VStack {
            Text("\(counter)")
                .font(.largeTitle)
            HStack {
                Button("+") {
                    counter += 1
                }
                
                Button("-") {
                    counter -= 1
                }
            }
        }
    }
}