Pembangunan aplikasi pada SwiftUI. Bahagian 1: Aliran Data dan Redux

Pembangunan aplikasi pada SwiftUI. Bahagian 1: Aliran Data dan Redux

Selepas menghadiri sesi State of the Union di WWDC 2019, saya memutuskan untuk mendalami SwiftUI. Saya telah menghabiskan banyak masa bekerja dengannya dan kini telah mula membangunkan aplikasi sebenar yang boleh berguna kepada pelbagai pengguna.

Saya memanggilnya MovieSwiftUI - ini ialah aplikasi untuk mencari filem baharu dan lama, serta mengumpulnya dalam koleksi menggunakan API TMDB. Saya sentiasa menyukai filem dan juga mencipta sebuah syarikat yang bekerja dalam bidang ini, walaupun sudah lama dahulu. Syarikat itu hampir tidak boleh dipanggil keren, tetapi permohonannya adalah!

Kami mengingatkan: untuk semua pembaca "Habr" - diskaun sebanyak 10 rubel apabila mendaftar dalam mana-mana kursus Skillbox menggunakan kod promosi "Habr".

Skillbox mengesyorkan: Kursus dalam talian pendidikan "Pemaju Java Profesion".

Jadi apa yang boleh MovieSwiftUI lakukan?

  • Berinteraksi dengan API - hampir mana-mana aplikasi moden melakukan ini.
  • Memuatkan data tak segerak pada permintaan dan menghuraikan JSON ke dalam model Swift menggunakan Boleh dikodkan.
  • Menunjukkan imej yang dimuatkan atas permintaan dan menyimpannya dalam cache.
  • Apl untuk iOS, iPadOS dan macOS ini menyediakan UX terbaik untuk pengguna OS ini.
  • Pengguna boleh menjana data dan membuat senarai filem mereka sendiri. Aplikasi ini menyimpan dan memulihkan data pengguna.
  • Pandangan, komponen dan model dipisahkan dengan jelas menggunakan corak Redux. Aliran data di sini adalah satu arah. Ia boleh dicache sepenuhnya, dipulihkan dan ditulis ganti.
  • Aplikasi ini menggunakan komponen asas SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal, dll. Ia juga menyediakan paparan tersuai, gerak isyarat, UI/UX.

Pembangunan aplikasi pada SwiftUI. Bahagian 1: Aliran Data dan Redux
Malah, animasinya lancar, GIF ternyata sedikit tersentak

Mengusahakan apl itu memberi saya banyak pengalaman dan secara keseluruhan ia merupakan pengalaman yang positif. Saya dapat menulis aplikasi berfungsi sepenuhnya, pada bulan September saya akan memperbaikinya dan menerbitkannya di AppStore, serentak dengan keluaran iOS 13.

Redux, BindableObject dan EnvironmentObject

Pembangunan aplikasi pada SwiftUI. Bahagian 1: Aliran Data dan Redux

Saya telah bekerja dengan Redux selama kira-kira dua tahun sekarang, jadi saya agak mahir mengenainya. Khususnya, saya menggunakannya di bahagian hadapan untuk Bertindak laman web, serta untuk membangunkan aplikasi iOS (Swift) dan Android (Kotlin) asli.

Saya tidak pernah menyesal memilih Redux sebagai seni bina aliran data untuk membina aplikasi SwiftUI. Bahagian yang paling mencabar apabila menggunakan Redux dalam apl UIKit berfungsi dengan kedai dan mendapatkan serta mendapatkan semula data serta memetakannya ke paparan/komponen anda. Untuk melakukan ini, saya terpaksa mencipta sejenis perpustakaan penyambung (menggunakan ReSwift dan ReKotlin). Berfungsi dengan baik, tetapi cukup banyak kod. Malangnya, ia bukan (belum) sumber terbuka.

Berita baik! Satu-satunya perkara yang perlu dibimbangkan dengan SwiftUI - jika anda bercadang untuk menggunakan Redux - ialah kedai, keadaan dan pengurang. Interaksi dengan kedai dijaga sepenuhnya oleh SwiftUI terima kasih kepada @EnvironmentObject. Jadi, kedai bermula dengan BindableObject.

Saya mencipta pakej Swift yang mudah, SwiftUIFlux, yang menyediakan penggunaan asas Redux. Dalam kes saya ia adalah sebahagian daripada MovieSwiftUI. saya juga menulis tutorial langkah demi langkah, yang akan membantu anda menggunakan komponen ini.

Bagaimana ia berfungsi?

final public class Store<State: FluxState>: BindableObject {
    public let willChange = PassthroughSubject<Void, Never>()
        
    private(set) public var state: State
    
    private func _dispatch(action: Action) {
        willChange.send()
        state = reducer(state, action)
    }
}

Setiap kali anda mencetuskan tindakan, anda mengaktifkan kotak gear. Ia akan menilai tindakan mengikut keadaan semasa permohonan. Ia kemudiannya akan mengembalikan keadaan diubah suai baharu mengikut jenis tindakan dan data.

Oleh kerana kedai ialah BindableObject, ia akan memberitahu SwiftUI apabila nilainya berubah menggunakan sifat willChange yang disediakan oleh PassthroughSubject. Ini kerana BindableObject mesti menyediakan PublisherType, tetapi pelaksanaan protokol bertanggungjawab untuk mengurusnya. Secara keseluruhan, ini adalah alat yang sangat berkuasa daripada Apple. Sehubungan itu, dalam kitaran pemaparan seterusnya, SwiftUI akan membantu memaparkan isi paparan mengikut perubahan keadaan.

Sebenarnya, ini semua adalah hati dan keajaiban SwiftUI. Kini, dalam mana-mana paparan yang melanggan keadaan, paparan akan diberikan mengikut data yang diterima daripada negeri dan perkara yang telah berubah.

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
 
    var window: UIWindow?
 
 
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            
            let controller = UIHostingController(rootView: HomeView().environmentObject(store))
            window.rootViewController = controller
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}
 
struct CustomListCoverRow : View {
    @EnvironmentObject var store: Store<AppState>
    
    let movieId: Int
    var movie: Movie! {
        return store.state.moviesState.movies[movieId]
    }
    
    var body: some View {
        HStack(alignment: .center, spacing: 0) {
            Image(movie.poster)
        }.listRowInsets(EdgeInsets())
    }
}

The Store disuntik sebagai EnvironmentObject apabila aplikasi bermula dan kemudian boleh diakses dalam sebarang paparan menggunakan @EnvironmentObject. Tiada penalti prestasi kerana sifat terbitan cepat diambil atau dikira dari keadaan aplikasi.

Kod di atas menukar imej jika poster filem berubah.

Dan ini sebenarnya dilakukan dengan hanya satu baris, dengan bantuan pandangan yang disambungkan ke negeri. Jika anda telah bekerja dengan ReSwift pada iOS atau pun menyambung dengan React, anda akan memahami keajaiban SwiftUI.

Kini anda boleh cuba mengaktifkan tindakan dan menerbitkan keadaan baharu. Berikut ialah contoh yang lebih kompleks.

struct CustomListDetail : View {
    @EnvironmentObject var store: Store<AppState>
 
    let listId: Int
    
    var list: CustomList {
        store.state.moviesState.customLists[listId]!
    }
    
    var movies: [Int] {
        list.movies.sortedMoviesIds(by: .byReleaseDate, state: store.state)
    }
    
    var body: some View {
        List {
            ForEach(movies) { movie in
                NavigationLink(destination: MovieDetail(movieId: movie).environmentObject(self.store)) {
                    MovieRow(movieId: movie, displayListImage: false)
                }
            }.onDelete { (index) in
               self.store.dispatch(action: MoviesActions.RemoveMovieFromCustomList(list: self.listId, movie: self.movies[index.first!]))
            }
        }
    }
}

Dalam kod di atas, saya menggunakan tindakan .onDelete daripada SwiftUI untuk setiap IP. Ini membolehkan baris dalam senarai memaparkan leret iOS biasa untuk dipadamkan. Jadi apabila pengguna menyentuh butang padam, ia mencetuskan tindakan yang sepadan dan mengalih keluar filem daripada senarai.

Oleh kerana harta senarai diperoleh daripada keadaan BindableObject dan disuntik sebagai EnvironmentObject, SwiftUI mengemas kini senarai kerana ForEach dikaitkan dengan harta yang dikira filem.

Berikut ialah sebahagian daripada pengurang MoviesState:

func moviesStateReducer(state: MoviesState, action: Action) -> MoviesState {
    var state = state
    switch action {
    
    // other actions.
    
    case let action as MoviesActions.AddMovieToCustomList:
        state.customLists[action.list]?.movies.append(action.movie)
        
    case let action as MoviesActions.RemoveMovieFromCustomList:
        state.customLists[action.list]?.movies.removeAll{ $0 == action.movie }
        
    default:
        break
    }
    return state
}

Pengurang dilaksanakan apabila anda menghantar tindakan dan mengembalikan keadaan baharu, seperti yang dinyatakan di atas.

Saya tidak akan menerangkan secara terperinci lagi - bagaimana SwiftUI sebenarnya tahu perkara yang hendak dipaparkan. Untuk memahami perkara ini dengan lebih mendalam, ia berbaloi lihat sesi WWDC tentang aliran data dalam SwiftUI. Ia juga menerangkan secara terperinci mengapa dan bila untuk digunakan Negeri, @Binding, ObjectBinding dan EnvironmentObject.

Skillbox mengesyorkan:

Sumber: www.habr.com

Tambah komen