Przejdź do treści

W poprzednim artykule dzieliłam się przemyśleniami na temat estetyki komponentów interfejsu użytkownika. Czas więc poukładać je na ekranie. Zadanie wymaga sporo wyobraźni, bo jako osoba niewidoma nie zerknę sobie na makietę i nie przerysuję wymiarów z Figmy. Co więc mogę zrobić?

Alternatywy

Do wyboru mam zasadniczo trzy opcje:

  1. Mogę użyć frameworka SwiftUI i rozpisać sobie każdy komponent za pomocą prostego kodu.
  2. Mogę poukładać każdy ekran z komponentów w pliku .storyboard i przypiąć w odpowiedni sposób.
  3. Mogę wykorzystać elementy UIKit i cały interfejs rozpisać w kodzie.

Zaczynam od SwiftUI

Gdy chcę coś szybko naszkicować, żeby dowiedzieć się, jak się będzie zachowywać na ekranie, moim pierwszym wyborem od momentu pojawienia się jest framework SwiftUI. Jego największymi zaletami są prostota i wieloplatformowość. Tego samego kodu w większości przypadków mogę użyć dla iOS, MacOS, TVOS czy WatchOS.

Co upraszcza mi pracę?

Jest kilka kluczowych aspektów, które sprawiają, że nowe pomysły najszybciej realizuję w SwiftUI. Należą do nich:

  • Modułowość. Framework zaprojektowano w taki sposób, że zachęca do tworzenia większych całości z niedużych komponentów, które łatwo jest przenieść z projektu do projektu. Skraca to czas kodowania i redukuje powtarzalność.
  • Głównym źródłem wiedzy o zawartości komponentu jest kod. Oznacza to, że nawet jeśli zmodyfikuję jakiś element na kanwie podglądu, zmiany natychmiast widoczne są w dostępnym zaraz obok kodzie źródłowym. Gdy więc zmienię zamysł, wystarczy usunąć kilka linijek, by wycofać zmianę.
  • Opisywane powyżej podejście ułatwia odkrywanie nowych funkcjonalności, co wpływa pozytywnie na szybkość uczenia się frameworka. Pozwala też być w miarę na bieżąco z zachodzącymi w nim przez ostatnie lata zmianami.
  • Kod jest wyraźnie krótszy. Zyskuje więc nie tylko na jakości, ale przede wszystkim na czytelności. To dla mnie jedna z jego największych zalet.

A czy SwiftUI ma wady?

Oczywiście, całkiem sporo. Najbardziej uciążliwą i jak dotąd nierozwiązaną przyjaźnie kwestią są enigmatyczne błędy wskazywane przez kompilator. I pojawiają się czasami w najbardziej nieoczywistych momentach. Najbardziej frustrują, gdy zabraknie mi gdzieś nawiasu lub przecinka, a kompilator ogłasza coś tak dziwnego, że mam ochotę wyrwać sobie włosy z głowy. Osobie widzącej zazwyczaj edytor podświetla nawiasy na odpowiedni kolor. Łatwo więc może się zorientować, którego brakuje. Ja muszę przewertować wszystko znak po znaku, a i tak często zdarza mi się usuwać większy fragment i pisać coś od zera.

Mimo wszystko jednak wolę ten poziom frustracji niż pisanie kilkakrotnie więcej kodu w UIKit czy AppKit dla osiągnięcia tego samego efektu. Przedstawię Wam to na przykładzie. Poniżej wstawiam dwa fragmenty kodu, które dadzą taki sam efekt końcowy.

Załóżmy, że chcę stworzyć przykładową listę owoców. W SwiftUI będzie to taki kod:

``` swift
import SwiftUI

struct ContentView: View {
    let fruits = ["Apple", "Banana", "Orange", "Grapes", "Watermelon"]
    var body: some View {
        NavigationView {
            List(fruits, id: \.self) { fruit in
                Text(fruit)
            }
            .navigationTitle("Fruits")
        }
    }
}
```

Taka sama lista w UIKit będzie wymagała znacznie dłuższego kodu. Oto i on:

``` swift
import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    let fruits = ["Apple", "Banana", "Orange", "Grapes", "Watermelon"]

    let tableView: UITableView = {
        let tableView = UITableView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        return tableView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.dataSource = self
        tableView.delegate = self

        view.addSubview(tableView)

        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }

    // MARK: - UITableViewDataSource

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return fruits.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "FruitCell") ?? UITableViewCell(style: .default, reuseIdentifier: "FruitCell")
        cell.textLabel?.text = fruits[indexPath.row]
        return cell
    }

    // MARK: - UITableViewDelegate

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // Handle row selection if needed
    }
}

```

Mój przykład zawiera jedynie prostą listę, a już kod UIKit jest około 3 razy dłuższy. A dobrze wiemy, że przy bardziej złożonych ekranach interfejsu użytkownika zagadnienie znacznie się komplikuje. Tym bardziej, im więcej trzeba ich rozmieścić na ekranie. 

Oczywiście w XCode można na pokrętle VoiceOver wybrać nawigację w kodzie po metodach i funkcjach, czy błędach lub ostrzeżeniach, ale przy dłuższych metodach albo ich dużej liczbie, to nadal wymaga sporej koncentracji i przede wszystkim czasu. Czytnik ekranu zapozna mnie z tym samym kodem znacznie wolniej niż przyswajają go Wasze oczy.

Współpraca

Gdy potrzebuję szkicu konkretnego interfejsu, nie skorzystam z Figmy. Ale istnieją alternatywy dość wygodne w użyciu i dla projektanta, i dla mnie. Na rynku dostępne są na przykład aplikacje, które pomagają wygenerować kod SwiftUI z graficznego projektu. Rozwiązania te nie są stuprocentowo skuteczne i często prowadzą do generowania pewnej ilości nadmiernego kodu, który trzeba potem przerobić i oczyścić, ale zrozumienie, czego oczekuje projektant, jest dużo prostsze.

Do dyspozycji mam też zawsze kanwę z podglądem, więc pewne aspekty, które trzeba przedyskutować, daje się szybko pokazać. Dodatkowo mogę też na przykład wygenerować na symulatorze zrzut ekranu i przesłać projektantowi z pytaniem, czy o to mu mniej więcej chodziło.

Podsumowanie

Nie upieram się przy stosowaniu jednego frameworka, bo tworzenie interfejsu aplikacji wymaga połączenia wielu aspektów, by zapewnić użytkownikom najlepsze możliwe doświadczenia. W najbliższej przyszłości zamierzam też pokazać Wam, jak przy pomocy VoiceOver pracować z plikami .storyboard, jednak z racji czytelności i prostoty, SwiftUI pozostanie na razie moim faworytem.