SwiftUI-da ilovalarni ishlab chiqish. 1-qism: Dataflow va Redux

SwiftUI-da ilovalarni ishlab chiqish. 1-qism: Dataflow va Redux

WWDC 2019-da Ittifoqning holati sessiyasida qatnashganimdan so'ng, men SwiftUI-ga chuqur sho'ng'ishga qaror qildim. Men u bilan ishlash uchun ko'p vaqt sarfladim va endi keng foydalanuvchilar uchun foydali bo'lishi mumkin bo'lgan haqiqiy dasturni ishlab chiqishni boshladim.

Men uni MovieSwiftUI deb nomladim - bu yangi va eski filmlarni qidirish, shuningdek ularni to'plamda to'plash uchun dastur. TMDB API. Men har doim filmlarni yaxshi ko'raman va hatto uzoq vaqt oldin bo'lsa ham, bu sohada ishlaydigan kompaniya yaratganman. Kompaniyani zo'rg'a zo'r deb atash mumkin edi, ammo dastur shunday edi!

Sizga eslatib o'tamiz: "Habr" ning barcha o'quvchilari uchun - "Habr" promo-kodidan foydalangan holda har qanday Skillbox kursiga yozilishda 10 000 rubl chegirma.

Skillbox tavsiya qiladi: Ta'lim onlayn kurs "Java dasturchi kasbi".

Xo'sh, MovieSwiftUI nima qila oladi?

  • API bilan o'zaro ishlaydi - deyarli har qanday zamonaviy dastur buni amalga oshiradi.
  • So'rovlar bo'yicha asinxron ma'lumotlarni yuklaydi va JSON-ni Swift modeliga ajratadi Kodlanishi mumkin.
  • So'rov bo'yicha yuklangan rasmlarni ko'rsatadi va ularni keshlaydi.
  • iOS, iPadOS va macOS uchun ushbu ilova ushbu OS foydalanuvchilari uchun eng yaxshi UXni taqdim etadi.
  • Foydalanuvchi ma'lumotlarni yaratishi va o'z filmlar ro'yxatini yaratishi mumkin. Ilova foydalanuvchi ma'lumotlarini saqlaydi va tiklaydi.
  • Ko'rinishlar, komponentlar va modellar Redux naqsh yordamida aniq ajratilgan. Bu erda ma'lumotlar oqimi bir tomonlama. Uni to'liq keshlash, qayta tiklash va ustiga yozish mumkin.
  • Ilova SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal va boshqalarning asosiy komponentlaridan foydalanadi. Shuningdek, u maxsus ko'rinishlar, imo-ishoralar, UI/UX bilan ta'minlaydi.

SwiftUI-da ilovalarni ishlab chiqish. 1-qism: Dataflow va Redux
Aslida, animatsiya silliq, GIF biroz jirkanch bo'lib chiqdi

Ilova ustida ishlash menga katta tajriba berdi va umuman olganda bu ijobiy tajriba bo‘ldi. Men to'liq ishlaydigan dastur yozishga muvaffaq bo'ldim, sentyabr oyida men uni takomillashtiraman va iOS 13-ning chiqarilishi bilan bir vaqtda AppStore-da nashr etaman.

Redux, BindableObject va EnvironmentObject

SwiftUI-da ilovalarni ishlab chiqish. 1-qism: Dataflow va Redux

Men Redux bilan ikki yildan beri ishlayapman, shuning uchun men uni nisbatan yaxshi bilaman. Xususan, men uni frontend uchun ishlataman munosabat veb-sayt, shuningdek, mahalliy iOS (Swift) va Android (Kotlin) ilovalarini ishlab chiqish uchun.

SwiftUI ilovasini yaratish uchun maʼlumotlar oqimi arxitekturasi sifatida Reduxni tanlaganimdan hech qachon afsuslanmadim. UIKit ilovasida Redux-dan foydalanishning eng qiyin qismlari do'kon bilan ishlash va ma'lumotlarni olish va olish va ularni ko'rinishlar/komponentlaringizga solishtirishdir. Buning uchun men ulagichlar kutubxonasini yaratishim kerak edi (ReSwift va ReKotlin yordamida). Yaxshi ishlaydi, lekin juda ko'p kod. Afsuski, u (hali) ochiq manba emas.

Yaxshi xabarlar! SwiftUI bilan tashvishlanadigan yagona narsa - agar siz Redux-dan foydalanishni rejalashtirmoqchi bo'lsangiz - bu do'konlar, shtatlar va reduktorlar. Do'kon bilan o'zaro aloqa @EnvironmentObject tufayli SwiftUI tomonidan to'liq hal qilinadi. Shunday qilib, do'kon BindableObject bilan boshlanadi.

Men oddiy Swift paketini yaratdim, SwiftUIFlux, bu Redux-dan asosiy foydalanishni ta'minlaydi. Mening holimda bu MovieSwiftUI ning bir qismi. Men ham bosqichma-bosqich o'quv qo'llanmasini yozdi, bu sizga ushbu komponentdan foydalanishga yordam beradi.

U qanday ishlaydi?

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

Har safar harakatni boshlaganingizda, siz vites qutisini faollashtirasiz. U amallarni ilovaning joriy holatiga qarab baholaydi. Keyin u harakat turi va ma'lumotlariga muvofiq yangi o'zgartirilgan holatni qaytaradi.

Doʻkon BindableObject boʻlgani uchun PassthroughSubject tomonidan taqdim etilgan willChange xususiyatidan foydalanib, qiymati oʻzgarganda u SwiftUI haqida xabar beradi. Buning sababi, BindableObject PublisherType ni taqdim etishi kerak, ammo protokolni amalga oshirish uni boshqarish uchun javobgardir. Umuman olganda, bu Apple kompaniyasining juda kuchli vositasi. Shunga ko'ra, keyingi renderlash siklida SwiftUI holat o'zgarishiga qarab ko'rinishlar tanasini ko'rsatishga yordam beradi.

Aslida, bu SwiftUI-ning qalbi va sehridir. Endi davlatga obuna bo'lgan har qanday ko'rinishda ko'rinish shtatdan qanday ma'lumotlar olinganiga va nima o'zgarganiga qarab ko'rsatiladi.

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

Ilova ishga tushganda doʻkon EnvironmentObject sifatida kiritiladi va keyin @EnvironmentObject yordamida istalgan koʻrinishda foydalanish mumkin boʻladi. Ishlash uchun jarima yo'q, chunki olingan xususiyatlar dastur holatidan tezda olinadi yoki hisoblab chiqiladi.

Film posteri o'zgarsa, yuqoridagi kod tasvirni o'zgartiradi.

Va bu aslida faqat bitta chiziq bilan amalga oshiriladi, uning yordami bilan qarashlar davlat bilan bog'lanadi. Agar siz iOS yoki hatto ReSwift bilan ishlagan bo'lsangiz ulanish React bilan siz SwiftUI sehrini tushunasiz.

Endi siz harakatni faollashtirishga urinib ko'rishingiz va yangi holatni nashr qilishingiz mumkin. Mana murakkabroq misol.

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!]))
            }
        }
    }
}

Yuqoridagi kodda men har bir IP uchun SwiftUI-dan .onDelete amalidan foydalanmoqdaman. Bu roʻyxatdagi qatorni oʻchirish uchun oddiy iOS surishini koʻrsatish imkonini beradi. Shunday qilib, foydalanuvchi o'chirish tugmasini bosganida, u tegishli harakatni ishga tushiradi va filmni ro'yxatdan o'chiradi.

Roʻyxat xususiyati BindableObject holatidan olinganligi va EnvironmentObject sifatida kiritilganligi sababli, SwiftUI roʻyxatni yangilaydi, chunki ForEach filmlar hisoblangan xususiyat bilan bogʻlangan.

Mana MoviesState reduktorining bir qismi:

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
}

Reduktor yuqorida aytib o'tilganidek, harakatni yuborganingizda va yangi holatni qaytarganingizda bajariladi.

Men hali batafsil ma'lumot bermayman - SwiftUI aslida nimani ko'rsatishni biladi. Buni chuqurroq tushunish uchun bunga arziydi ma'lumotlar oqimi bo'yicha WWDC sessiyasini ko'rish SwiftUI-da. Shuningdek, u nima uchun va qachon foydalanishni batafsil tushuntiradi Davlat, @Binding, ObjectBinding va EnvironmentObject.

Skillbox tavsiya qiladi:

Manba: www.habr.com

a Izoh qo'shish