Sii Polska

SII UKRAINE

SII SWEDEN

  • Szkolenia
  • Kariera
Dołącz do nas Kontakt
Wstecz

Sii Polska

SII UKRAINE

SII SWEDEN

Wstecz

28.07.2025

Wykorzystanie najnowszych funkcji SwiftUI w developmencie iOS

28.07.2025

Wykorzystanie najnowszych funkcji SwiftUI w developmencie OS

W ostatnim czasie możemy zaobserwować, że tempo rozwoju technologii znacząco przyspieszyło. Apple nie przestaje nas zadziwiać szybkością wprowadzania coraz to nowszych i ciekawszych API do swojego ekosystemu, a sam Swift UI jest bardzo mocno promowany przez Apple jako zalecany sposób tworzenia aplikacji mobilnych.

W krótkim artykule nie sposób opisać wszystkich bibliotek, które pojawiały się w iOS od wersji 15 do 18, dlatego chciałbym się skupić zaledwie na kilku ciekawych zagadnieniach, na które udało mi się natrafić zarówno w codziennej pracy jak i w trakcie przeglądania najnowszych materiałów szkoleniowych oraz oficjalnej dokumentacji Apple.

W dzisiejszym wpisie opiszę pokrótce takie zagadnienia jak AGA (Automatic Grammar Agreement) i String Catalog, czyli nowe podejście Apple do lokalizacji swoich aplikacji, oraz jak stworzyć i wykorzystać w praktyce swój własny EnvironmentKey().

Zatem – nie przedłużając przydługiego już i tak wstępu, przejdźmy do meritum. Link do repozytorium znajdziecie na końcu artykułu.

AGA – Automatic Grammar Agreement

Myślę, że każdy z developerów spotkał się z problemem poprawnego odmieniania rzeczowników w zależności od ilości lub przynajmniej zastanawiał się nad takim problemem. Łatwo wyobrazić sobie przypadek, kiedy mamy za zadanie stworzyć widok, w którym, korzystając przykładowo ze Stepper(), zwiększamy ilość np. filiżanek kawy, które klient może zamówić przy użyciu tworzonej przez nas aplikacji.

Nawet w języku angielskim, gdzie sprawa sprowadza się najczęściej do dodania literki „s” do danego słowa, w określonych przypadkach poprawna odmiana może się mocno skomplikować. Przykładowo z one goose robi się two geese, a z one mouse – two mice.

W języku polskim sprawa wyglądałaby jeszcze ciekawiej, chociażby na przykładzie pączków – jeden pączek, dwa pączki, 11 pączków, 23 pączki… i prawdopodobnie z tego prostego powodu, Apple nie zdecydowało się na objęciem AGA naszego ojczystego języka 🧐

AGA w praktyce

Odstawmy na bok zawiłości gramatyczne i spróbujmy zobrazować na prostym przykładzie kodu, z czym przychodzi nam się mierzyć. Poniżej mamy zdefiniowane dwa przyciski do zwiększania i zmniejszania wartości oraz prosty tekst ze słowem „Bottle” powiązanym z ilością:

Code snippet 1: Kod początkowy
Ryc. 1 Code snippet 1: Kod początkowy

Efekt omawianego przykładu nietrudno przewidzieć – bez względu na wybraną ilość butelek otrzymamy dokładnie ten sam String – „Bottle” poprzedzony oczywiście właściwą ilością, którą wybraliśmy, korzystając z dwóch prostych przycisków.

W zrzucie ekranu prezentowanym poniżej możecie zobaczyć, jak to prezentuje się wizualnie – raczej nie jest to coś, co chcielibyśmy pokazać w czasie prezentacji na koniec sprintu naszym product ownerom.

Screen 1: Ekran początkowy
Ryc. 2 Screen 1: Ekran początkowy

W aplikacji chcielibyśmy uzyskać taki efekt, gdzie zdefiniowane przez nas słowo „Bottle” lub jakiekolwiek inne zostanie poprawnie odmienione zgodnie z obowiązującymi zasadami gramatyki.

Pojawia się zatem pytanie: W jaki sposób możemy osiągnąć nasz cel bez konieczności tworzenia dodatkowych funkcji zwracających odpowiednią wartość String dla odpowiedniej wartości liczbowej?

Tutaj z pomocą przychodzi nam właśnie Automatic Grammar Agreement, które działa na ten moment dla 6 najbardziej popularnych języków. Dla waszej wygody zebrałem całość w tabelę zbiorczą zaprezentowanej w dalszej części wpisu.

Wracając do tematu w przedmiotowej dyskusji oraz możliwości rozwiązania problemu, to w naszym przypadku wystarczy, że zamienimy poprzedni zapis na:

Text("^[\(count) Bottle](inflect: true)")

I uzyskamy efekt, jaki chcieliśmy uzyskać – angielskie słowo „BOTTLE” zostanie poprawnie odmienione w zależności od wybranej ilości.

AGA w praktyce – kolejne przykłady

Zmieńmy nieco nasz kod, aby pokazać więcej przykładów i przy okazji wystylizujmy nieco widok, korzystając z natywnych elementów Swift UI, dodając m.in. gradientowe tło, stylizując kontener, w którym znajdują się przykłady oraz wykorzystując Stepper() zamiast wcześniej wykorzystanego Button().

Code snippet 2: AGM Kod końcowy
Ryc. 3 Code snippet 2: AGM Kod końcowy

Uzyskany efekt:

Screen 2 i 3: AGM Ekran końcowy, liczba pojedyncza (po lewej) i mnoga (po prawej
Ryc. 4 Screen 2 i 3: AGM Ekran końcowy, liczba pojedyncza (po lewej) i mnoga (po prawej)

W tym miejscu warto wspomnieć, że w warunkach produkcyjnych często otrzymujemy wartości z innych miejsc w aplikacji np. z View Modelu. W tym przypadku konieczne jest wskazanie typu LocalizedStringKey, czyli dla naszego przykładu, jeśli chcielibyśmy zdefiniować wartość tekstową np. w view modelu, to musielibyśmy zapisać nasz parametr w ten sposób:

var test: LocalizedStringKey {("^[\(count) Bottle](inflect: true)")}
}

AGM jest dostępne dla iOS wg tabeli poniżej i może okazać się pomocne przy lokalizacji aplikacji, nad którą pracujecie lub będziecie pracować w przyszłości. Nawet jeśli nie przyjdzie Wam skorzystać z tego rozwiązania, to przynajmniej już będziecie wiedzieć, jak zbudować poprawnie liczbę mnogą od słowa Goose w języku angielskim 🙂

iOS 15Angielski
Hiszpański
iOS 16Francuski
Włoski
Portugalski (Brazylia)
iOS 17Portugalski (Europa)
Niemiecki
Tab. 1 Wsparcie AGM wg wersji iOS

String Catalog – nowe podejście do lokalizacji w XCode

W ostatnim czasie Apple wprowadziło nowocześniejszą wersję lokalizacji, która można wykorzystać od wersji XCode 15.0. Zmianie uległo podejście do samej lokalizacji – wcześniej wykorzystywaliśmy Localizable.strings, w którym mozolnie wpisywaliśmy kolejne klucze lokalizacji.

W nowym wydaniu pojawiło się narzędzie, którego możemy użyć jako alternatywy do wymienionych powyżej Localizable.strings, jeśli oczywiście chcielibyśmy, aby nasza aplikacja obsługiwała więcej niż jeden język. Rozwiązanie to działa Out of the box, jeśli język aplikacji jest zgodny z ustawieniami systemowymi.

String Catalog w praktyce

Pozwólcie zatem przedstawić Wam, jak to rozwiązanie działa w praktyce. Przejdźmy zatem do rzeczy i na przykładzie kilku prostych tłumaczeń dodajmy lokalizację aplikacji oraz obsługę zmiany języka wykorzystującą natywne narzędzia SwiftUI.

W pierwszym kroku musimy dodać nowy katalog stringów do naszego projektu, który domyślnie dostaje nazwę Localizable.xcstring

Dodanie String Catalog do projektu
Ryc. 5 Dodanie String Catalog do projektu

Standardowo przechodzimy dalej i wybieramy nazwę dla naszego katalogu, a ja, na potrzeby artykułu, zostawiam wartość domyślną.

Jak możecie zauważyć – lista słów jest początkowo pusta.

Wybranie nazwy katalogu
Ryc. 6 Wybranie nazwy katalogu
Startowy String Catalog
Ryc. 7 Startowy String Catalog

W kolejnym kroku możemy dodać dodatkowe języki, które chcemy, aby były objęte lokalizacją. W moim przypadku dodałem język polski i hiszpański.

Dodanie dodatkowego języka
Ryc. 8 Dodanie dodatkowego języka
Widok katalogu z kilkoma językami
Ryc. 9 Widok katalogu z kilkoma językami

W przeciwieństwie do Localized.strings poszczególne wpisy uzupełniają się automatycznie w fazie budowania się naszego projektu. Jeśli wprowadzona wartość tekstowa będzie zgodna z typem LocalizedStringKey, już przy pierwszym uruchomieniu projektu, nawet w wersji z jednym widokiem, który zrobiliśmy do tej pory, uzyskamy coś takiego:

Klucze dodane do projektu przy pierwszym zbudowaniu projektu
Ryc. 10 Klucze dodane do projektu przy pierwszym zbudowaniu projektu

Jak możecie zauważyć, do tłumaczenia po uruchomieniu projektu automatycznie pojawiły się wszystkie wpisy z pól Text(). W moim przypadku nie widzę potrzeby tłumaczenia dwóch pierwszych rekordów, które pojawiły się na tej liście, więc z menu kontekstowego mogę wybrać opcję „Don’t translate”, co z automatu wyłączy lokalizację dla wszystkich dodanych języków w katalogu.

Dla przykładu przetłumaczmy sobie na początek przynajmniej ten jeden string odpowiednio dla języka polskiego i hiszpańskiego. Po wprowadzeniu tej wartości możemy zauważyć, że XCode oznaczył nam wszystkie tłumaczenia jako uzupełnione, natomiast jeśli nie jesteśmy pewni, czy tłumaczenie jest ok lub chcemy poprosić o sprawdzenie, możemy oznaczyć dany wiersz jako „Mark for review”.

Na tym etapie warto wspomnieć, że String Catalog posiada funkcjonalność różnicowania wpisów w zależności od wariantu urządzenia, czy też dodawania odpowiedniej formy dla liczby mnogiej danego wpisu.

Uzupełniony początkowy katalog
Ryc. 11 Uzupełniony początkowy katalog

Implementacja lokalizacji w aplikacji

Skoro już wiemy, jak posługiwać się katalogiem, to możemy przejść do implementacji lokalizacji
w aplikacji. W tym celu wykorzystamy @AppStorage oraz zmienne środowiskowe environment(). Dodamy też NavigationStack() oraz proste menu kontekstowe pozwalające na zmianę języka aplikacji w czasie rzeczywistym.

Na początek stwórzmy klasę AppData, gdzie będziemy przechować ustawienia użytkownika.
W naszym przypadku na tym etapie będzie to tylko jedna zmienna „language”, którą dodatkowo oznaczymy jako @AppStorage.

@AppStorage to nic innego jak Property Wrapper, który działa w sposób podobny do User Defaults, przechowując podstawowe ustawienia użytkownika.

Code snippet 3: AppData aplikacji
Ryc. 12 Code snippet 3: AppData aplikacji

Ten krok nie jest wymagany, jeśli nie chcemy mieć pełnej kontroli nad ustawieniami języka w aplikacji, ponieważ domyślnie aplikacja będzie działała w oparciu o ustawienia systemowe.

Aby osiągnąć zamierzony cel, dodatkowo wykorzystamymodyfikatory environment() oraz environmentObject(), które należy dodać do root view naszej aplikacji.

Code snippet 4: Root view aplikacji uwzględniający parametry lokalizacji
Ryc. 13 Code snippet 4: Root view aplikacji uwzględniający parametry lokalizacji

Następnie tworzymy widok pomocniczy zawierający Menu() do wyboru odpowiedniego języka:

Code snippet 5: Language Options Menu
Ryc. 14 Code snippet 5: Language Options Menu

Potrzebujemy jeszcze jeden dodatkowy widok pomocniczy, gdzie zaprezentujemy kilka prostych słów. Podobnie jak poprzednio po zbudowaniu projektu te słowa od razu pojawią się w katalogu stringów, gdzie będzie można je przetłumaczyć analogicznie do zrzutów ekranu pokazanych wcześniej.

Przykładowy kod dla omawianego widoku:

Code snippet 6: Widok pomocniczy zawierający proste stringi
Ryc. 15 Code snippet 6: Widok pomocniczy zawierający proste stringi

W przykładzie powyżej użyłem typu LocalizedStringKey. Nie jest to jednak niezbędne, aby zachować oczekiwaną przez nas funkcjonalność. Chciałem jedynie pokazać, jaki typ wykorzystywany jest przez Swift do procesowania lokalizacji.

Ostatnim etapem jest umieszczenie naszego widoku w NavigationStack(), oraz dodanie przygotowanego wcześniej przycisku menu z opcjami zmiany języka. W przykładzie poniżej dodatkowo umieściłem widoki w TabView, aby urozmaicić prezentację i zobrazować wykorzystanie tego API w aplikacjach mobilnych.

Code snippet 7: Widok w TabView
Ryc. 16 Code snippet 7: Widok w TabView

Ważne:

Zwróćcie uwagę, że jeśli chcemy korzystać z obiektów środowiskowych i Preview, koniecznie musimy dodać environmentObject() również w Preview. W innym przypadku nie będzie działać i nie będziemy mogli skorzystać z tej funkcjonalności przy projektowaniu widoku, który wykorzystuje dany element środowiskowy.

Efekt końcowy prezentuję się następująco – zmiana języka następuje natychmiastowo po kliknięciu w wybrany przycisk menu. Zmieniają się jednocześnie wszystkie słowa, które zostały zdefiniowane wcześniej w katalogu, uwzględniając również elementy menu.

Screen 3: Efekt końcowy
Ryc. 17 Screen 3: Efekt końcowy

Podsumowanie

W mojej ocenie nowe rozwiązanie to czytelna alternatywa dla poprzedniego rozwiązania Apple, która ma szereg ciekawych funkcjonalności. Dodatkowo, nawet jeśli w Waszym projekcie wykorzystywana jest poprzednia wersja lokalizacji, to migracja do katalogu stringów jest bardzo prosta.

Na sam koniec tej sekcji warto wspomnieć, że jeśli zajrzycie głębiej do projektu, to zauważycie, że String Catalog jest generowany w formacie JSON, co daje dodatkowe możliwości na etapie developmentu samej aplikacji, a sam katalog można łatwo wygenerować w XCode (Xcode, Product -> Export Localizable) i przesłać do tłumaczenia.

Environment Key – jak stworzyć i wykorzystać customowe klucze środowiskowe

Pracując ze SwiftUI, zapewne spotkaliście się z wykorzystaniem różnego rodzaju obiektów środowiskowych, które definiujemy jako @Environment(\.key) var yourKeyName. W Swift UI znajdziemy ich mnóstwo, wystarczy zacząć wpisywanie i pojawi się cała lista:

Code snippet 7: Przykłady zmiennych enviromnent
Ryc. 18 Code snippet 7: Przykłady zmiennych enviromnent

Przykładem użycia może być chociażby colorScheme:

@Environment(\.colorScheme) var colorScheme

Który możemy wykorzystać do np. zdefiniowania koloru teksu czy jego wielkość w zależności od ustawień preferencji systemowych:

.foregroundStyle(colorScheme == .dark ? .primary : .secondary)

Oczywiście, w tym przypadku możemy zdefiniować, jaki kolor zostanie użyty odpowiednio dla dark mode i light mode w Assetach. Niemniej, warto mieć w swoim arsenale umiejętności również taką wiedzę, zwłaszcza, że wykorzystanie innych zmiennych środowiskowych, jak np.  .horizontalSizeClass, może okazać się już nie tak samo oczywiste, ale równie pomocne.

W mojej ocenie umiejętność wykorzystanie zmiennych środowiskowych obok tworzenia własnych styli oraz Extension do praktycznie dowolnego typu jest czymś, co podnosi umiejętności Swift UI na wyższy poziom.

Tworzenie nowych kluczy

Ale co możemy zrobić w sytuacji, kiedy interesujący nas klucz nie istnieje lub jego funkcjonalność nie spełnia naszych oczekiwań?

O ile tworzenie nowych styli dla elementów UI w SwiftUI opiera się na stworzeniu struct zgodnego z odpowiednim protokołem, jak np. ButtonStyle(), DatePickeStyle(), ProgressBarStyle() TextFieldStyle(), to w przypadku kluczy środowiskowych po prostu musimy stworzyć nowy.

Na potrzeby niniejszego wpisu omówimy sobie przykład pola, które jest wymagane do wprowadzenia, aby pójść dalej w danym formularzu, czyli coś, co nietrudno nam będzie sobie wyobrazić w codziennym użyciu.

Zacznijmy od stworzenia nowego klucza o nazwie isRequired:

Code snippet 8: Tworzenie nowego klucza
Ryc. 19 Code snippet 8: Tworzenie nowego klucza

Następnie, jako że chcemy wykorzystać nasz klucz przy tworzeniu nowych pól tekstowych i zakładamy, że będziemy wykorzystywać ten styl więcej niż raz, zdefiniujmy sobie CustomTextFieldStyle(), który wykorzystamy do stworzenia naszego widoku. Jak już wspomniałem wcześniej wykorzystanie styli to dosyć częsta praktyka przy tworzeniu aplikacji mobilnych w Swift UI, więc warto wiedzieć, jak można to zrobić.

Code snippet 9: Custom Text Field Style z parametrem isRequired
Ryc. 20 Code snippet 9: Custom Text Field Style z parametrem isRequired

Przy okazji tego widoku wykorzystałem dodatkowy Extension do View z funkcją – .if(), który ułatwia wykorzystanie różnych elementów UI. Bardzo łatwo można go znaleźć w sieci, ale pokażę go również tutaj. Jednocześnie dodaję funkcję isRequired, która ułatwi nam użycie zmiennej środowiskowej isRequired:

Code snippet 10: Pomocnicze funkcje .if oraz isRequired
Ryc. 21 Code snippet 10: Pomocnicze funkcje .if oraz isRequired

Następnie tworzymy widok pomocniczy CustomInputField(), w którym wykorzystamy nasz customowy styl oraz nasz nowy klucz środowiskowy, który przekażemy do stylu

@Environment(\.isRequired) var isRequired
Code snippet 11: Custom Input Field
Ryc. 22 Code snippet 11: Custom Input Field

Na sam koniec pozostaje nam jedynie zbudowanie widoku, który możemy wykorzystać w aplikacji. Dodatkowo w przykładzie pokazałem, jak można wykorzystać @FocusState do zarządzaniem focusem dla poszczególnych pól, co jest bardzo przydatne, jeśli mamy więcej jak jedno pole tekstowe.

Code snippet 12: FocusState
Ryc. 23 Code snippet 12: FocusState

Efekt końcowy naszych prac przedstawia się następująco:

Efekt końcowy
Ryc. 24 Efekt końcowy
oferty pracy

Podsumowanie

Na zakończenie powyższej sekcji warto zaznaczyć, że przy wykorzystaniu istniejących czy stworzonych przez nas kluczy środowiskowych należy zachować czujność i rozwagę. Może się bowiem okazać, że aplikacja zacznie nam generować trudne do zdiagnozowania problemy, jeśli zapomnieliśmy użyć odpowiedniego klucza w odpowiednim miejscu i we właściwy sposób. Do minusów tego rozwiązania można zaliczyć trudniejsze testowanie naszej aplikacji.

Mam nadzieję, że udało mi się choćby w niewielkim stopniu przykuć Waszą uwagę i że omawiane tematy przydadzą się Wam w codziennej pracy.

Poniżej znajdziecie link do repozytorium z omawianymi przykładami oraz bibliografię, która pomogła mi w napisaniu tego artykułu.

***

Link do repozytorium z przykładami przestawianymi w blogu: https://github.com/Theordius/SwiftUIBlogExamples

***

Bibliografia

5/5
Ocena
5/5
Avatar

O autorze

Rafał Gęsior

iOS Developer i pasjonat nowoczesnych technologii. Specjalizuje się w tworzeniu aplikacji mobilnych przy użyciu SwiftUI, z naciskiem na wdrażanie standardów dostępności (WCAG) w środowisku mobilnym. Posiada doświadczenie na różnych etapach cyklu życia projektu, a background managerski pozwala mu lepiej rozumieć potrzeby biznesowe. Obecnie rozwija aplikację mobilną dla jednego z największych banków w Polsce. Prywatnie interesuje się fotografią, psychologią, muzyką oraz sztukami walki

Wszystkie artykuły autora

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Może Cię również zainteresować

Dołącz do nas

Sprawdź oferty pracy

Pokaż wyniki
Dołącz do nas Kontakt

This content is available only in one language version.
You will be redirected to home page.

Are you sure you want to leave this page?