ืคื™ืชื•ื— ืืคืœื™ืงืฆื™ื•ืช ื‘- SwiftUI. ื—ืœืง 1: ื–ืจื™ืžืช ื ืชื•ื ื™ื ื•-Redux

ืคื™ืชื•ื— ืืคืœื™ืงืฆื™ื•ืช ื‘- SwiftUI. ื—ืœืง 1: ื–ืจื™ืžืช ื ืชื•ื ื™ื ื•-Redux

ืœืื—ืจ ืฉื”ืฉืชืชืคืชื™ ื‘ืžื•ืฉื‘ ืžืฆื‘ ื”ืื™ื—ื•ื“ ื‘-WWDC 2019, ื”ื—ืœื˜ืชื™ ืœืฆืœื•ืœ ืขืžื•ืง ืœืชื•ืš SwiftUI. ื‘ื™ืœื™ืชื™ ื”ืจื‘ื” ื–ืžืŸ ื‘ืขื‘ื•ื“ื” ืขื ื–ื” ื•ืขื›ืฉื™ื• ื”ืชื—ืœืชื™ ืœืคืชื— ืืคืœื™ืงืฆื™ื” ืืžื™ืชื™ืช ืฉื™ื›ื•ืœื” ืœื”ื™ื•ืช ืฉื™ืžื•ืฉื™ืช ืœืžื’ื•ื•ืŸ ืจื—ื‘ ืฉืœ ืžืฉืชืžืฉื™ื.

ืงืจืืชื™ ืœื–ื” MovieSwiftUI - ื–ื• ืืคืœื™ืงืฆื™ื” ืœื—ื™ืคื•ืฉ ืกืจื˜ื™ื ื—ื“ืฉื™ื ื•ื™ืฉื ื™ื, ื›ืžื• ื’ื ืื™ืกื•ืฃ ืฉืœื”ื ื‘ืื•ืกืฃ ื‘ืืžืฆืขื•ืช TMDB API. ืชืžื™ื“ ืื”ื‘ืชื™ ืกืจื˜ื™ื ื•ืืคื™ืœื• ื™ืฆืจืชื™ ื—ื‘ืจื” ืฉืขื•ื‘ื“ืช ื‘ืชื—ื•ื ื”ื–ื”, ืื ื›ื™ ืžื–ืžืŸ. ื”ื—ื‘ืจื” ื‘ืงื•ืฉื™ ื™ื›ื•ืœื” ืœื”ื™ืงืจื ืžื’ื ื™ื‘ื”, ืื‘ืœ ื”ืืคืœื™ืงืฆื™ื” ื”ื™ื™ืชื”!

ืื ื• ืžื–ื›ื™ืจื™ื: ืœื›ืœ ืงื•ืจืื™ Habr - ื”ื ื—ื” ืฉืœ 10 ืจื•ื‘ืœ ื‘ืขืช ื”ืจืฉืžื” ืœื›ืœ ืงื•ืจืก Skillbox ื‘ืืžืฆืขื•ืช ืงื•ื“ ื”ื”ื˜ื‘ื” ืฉืœ Habr.

Skillbox ืžืžืœื™ืฆื”: ืงื•ืจืก ื—ื™ื ื•ื›ื™ ืžืงื•ื•ืŸ "ืžืงืฆื•ืขื™ ืžืคืชื— ื’'ืื•ื•ื”".

ืื– ืžื” MovieSwiftUI ื™ื›ื•ืœ ืœืขืฉื•ืช?

  • ืื™ื ื˜ืจืืงืฆื™ื” ืขื ื”-API - ื›ืžืขื˜ ื›ืœ ืืคืœื™ืงืฆื™ื” ืžื•ื“ืจื ื™ืช ืขื•ืฉื” ื–ืืช.
  • ื˜ื•ืขืŸ ื ืชื•ื ื™ื ืืกื™ื ื›ืจื•ื ื™ื™ื ืขืœ ื‘ืงืฉื•ืช ื•ืžื ืชื— JSON ืœืžื•ื“ืœ Swift ื‘ืืžืฆืขื•ืช ื ื™ืชื ืช ืœืงื™ื“ื•ื“.
  • ืžืฆื™ื’ ืชืžื•ื ื•ืช ืฉื ื˜ืขื ื• ืœืคื™ ื‘ืงืฉื” ื•ืฉื•ืžืจ ืื•ืชืŸ ื‘ืžื˜ืžื•ืŸ.
  • ืืคืœื™ืงืฆื™ื” ื–ื• ืขื‘ื•ืจ iOS, iPadOS ื•- macOS ืžืกืคืงืช ืืช ื”-UX ื”ื˜ื•ื‘ ื‘ื™ื•ืชืจ ืขื‘ื•ืจ ืžืฉืชืžืฉื™ ืžืขืจื›ื•ืช ื”ืคืขืœื” ืืœื”.
  • ื”ืžืฉืชืžืฉ ื™ื›ื•ืœ ืœื”ืคื™ืง ื ืชื•ื ื™ื ื•ืœื™ืฆื•ืจ ืจืฉื™ืžื•ืช ืกืจื˜ื™ื ืžืฉืœื•. ื”ืืคืœื™ืงืฆื™ื” ืฉื•ืžืจืช ื•ืžืฉื—ื–ืจืช ื ืชื•ื ื™ ืžืฉืชืžืฉ.
  • ืชืฆื•ื’ื•ืช, ืจื›ื™ื‘ื™ื ื•ื“ื’ืžื™ื ืžื•ืคืจื“ื™ื ื‘ื‘ื™ืจื•ืจ ื‘ืืžืฆืขื•ืช ื“ืคื•ืก Redux. ื–ืจื™ืžืช ื”ื ืชื•ื ื™ื ื›ืืŸ ื”ื™ื ื—ื“ ื›ื™ื•ื•ื ื™ืช. ื ื™ืชืŸ ืœืื—ืกืŸ ืื•ืชื• ื‘ืžืœื•ืื• ื‘ืžื˜ืžื•ืŸ, ืœืฉื—ื–ืจ ืื•ืชื• ื•ืœื“ืจื•ืก ืื•ืชื•.
  • ื”ืืคืœื™ืงืฆื™ื” ืžืฉืชืžืฉืช ื‘ืจื›ื™ื‘ื™ื ื”ื‘ืกื™ืกื™ื™ื ืฉืœ SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal ื•ื›ื•'. ื–ื” ื’ื ืžืกืคืง ืชืฆื•ื’ื•ืช ืžื•ืชืืžื•ืช ืื™ืฉื™ืช, ืžื—ื•ื•ืช, ืžืžืฉืง ืžืฉืชืžืฉ/UX.

ืคื™ืชื•ื— ืืคืœื™ืงืฆื™ื•ืช ื‘- SwiftUI. ื—ืœืง 1: ื–ืจื™ืžืช ื ืชื•ื ื™ื ื•-Redux
ืœืžืขืฉื”, ื”ืื ื™ืžืฆื™ื” ื—ืœืงื”, ื”-GIF ื™ืฆื ืงืฆืช ืžื˜ื•ืจืฃ

ื”ืขื‘ื•ื“ื” ืขืœ ื”ืืคืœื™ืงืฆื™ื” ื ืชื ื” ืœื™ ื ื™ืกื™ื•ืŸ ืจื‘ ื•ื‘ืกืš ื”ื›ืœ ื–ื• ื”ื™ื™ืชื” ื—ื•ื•ื™ื” ื—ื™ื•ื‘ื™ืช. ื”ืฆืœื—ืชื™ ืœื›ืชื•ื‘ ืืคืœื™ืงืฆื™ื” ืคื•ื ืงืฆื™ื•ื ืœื™ืช ืžืœืื”, ื‘ืกืคื˜ืžื‘ืจ ืืฉืคืจ ืื•ืชื” ื•ืืคืจืกื ืื•ืชื” ื‘-AppStore, ื‘ืžืงื‘ื™ืœ ืœืฉื—ืจื•ืจ iOS 13.

Redux, BindableObject ื•- EnvironmentObject

ืคื™ืชื•ื— ืืคืœื™ืงืฆื™ื•ืช ื‘- SwiftUI. ื—ืœืง 1: ื–ืจื™ืžืช ื ืชื•ื ื™ื ื•-Redux

ืื ื™ ืขื•ื‘ื“ ืขื Redux ื›ื‘ืจ ื›ืฉื ืชื™ื™ื, ืื– ืื ื™ ื‘ืงื™ื ื‘ื–ื” ื™ื—ืกื™ืช. ื‘ืคืจื˜, ืื ื™ ืžืฉืชืžืฉ ื‘ื• ื‘-frontend ืขื‘ื•ืจ ืœื”ื’ื™ื‘ ืืชืจ ืื™ื ื˜ืจื ื˜, ื›ืžื• ื’ื ืœืคื™ืชื•ื— ื™ื™ืฉื•ืžื™ iOS (Swift) ื•-Android (Kotlin) ืžืงื•ืจื™ื™ื.

ืžืขื•ืœื ืœื ื”ืฆื˜ืขืจืชื™ ืฉื‘ื—ืจืชื™ ื‘- Redux ื›ืืจื›ื™ื˜ืงื˜ื•ืจืช ื–ืจื™ืžืช ื”ื ืชื•ื ื™ื ืœื‘ื ื™ื™ืช ื™ื™ืฉื•ื SwiftUI. ื”ื—ืœืงื™ื ื”ืžืืชื’ืจื™ื ื‘ื™ื•ืชืจ ื‘ืขืช ืฉื™ืžื•ืฉ ื‘- Redux ื‘ืืคืœื™ืงืฆื™ื™ืช UIKit ื”ื ืขื‘ื•ื“ื” ืขื ื”ื—ื ื•ืช ื•ืงื‘ืœืช ื•ืื—ื–ื•ืจ ื ืชื•ื ื™ื ื•ืžื™ืคื•ื™ ืื•ืชื ืœืชืฆื•ื’ื•ืช/ืจื›ื™ื‘ื™ื ืฉืœืš. ืœืฉื ื›ืš, ื ืืœืฆืชื™ ืœื™ืฆื•ืจ ืžืขื™ืŸ ืกืคืจื™ื™ื” ืฉืœ ืžื—ื‘ืจื™ื (ื‘ืืžืฆืขื•ืช ReSwift ื•-ReKotlin). ืขื•ื‘ื“ ื˜ื•ื‘, ืื‘ืœ ื“ื™ ื”ืจื‘ื” ืงื•ื“. ืœืžืจื‘ื” ื”ืฆืขืจ, ื–ื” ืœื (ืขื“ื™ื™ืŸ) ืงื•ื“ ืคืชื•ื—.

ื—ื“ืฉื•ืช ื˜ื•ื‘ื•ืช! ื”ื“ื‘ืจื™ื ื”ื™ื—ื™ื“ื™ื ืฉืฆืจื™ืš ืœื“ืื•ื’ ืœื’ื‘ื™ื”ื ืขื SwiftUI - ืื ืืชื” ืžืชื›ื ืŸ ืœื”ืฉืชืžืฉ ื‘- Redux - ื”ื ื—ื ื•ื™ื•ืช, ืžื“ื™ื ื•ืช ื•ืžืคื—ื™ืชื™ื. ื”ืื™ื ื˜ืจืืงืฆื™ื” ืขื ื”ื—ื ื•ืช ืžื˜ื•ืคืœืช ืœื—ืœื•ื˜ื™ืŸ ืขืœ ื™ื“ื™ SwiftUI ื”ื•ื“ื•ืช ืœ-@EnvironmentObject. ืื–, ื”ื—ื ื•ืช ืžืชื—ื™ืœื” ืขื BindableObject.

ื™ืฆืจืชื™ ื—ื‘ื™ืœืช Swift ืคืฉื•ื˜ื”, SwiftUIFlux, ื”ืžืกืคืง ืฉื™ืžื•ืฉ ื‘ืกื™ืกื™ ื‘- Redux. ื‘ืžืงืจื” ืฉืœื™ ื–ื” ื—ืœืง ืž- MovieSwiftUI. ืื ื™ ื’ื ื›ืชื‘ ื”ื“ืจื›ื” ืฉืœื‘ ืื—ืจ ืฉืœื‘, ืฉื™ืขื–ื•ืจ ืœืš ืœื”ืฉืชืžืฉ ื‘ืจื›ื™ื‘ ื–ื”.

ืื™ืš ื–ื” ืขื•ื‘ื“?

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

ื‘ื›ืœ ืคืขื ืฉืืชื” ืžืคืขื™ืœ ืคืขื•ืœื”, ืืชื” ืžืคืขื™ืœ ืืช ืชื™ื‘ืช ื”ื”ื™ืœื•ื›ื™ื. ื”ื•ื ื™ืขืจื™ืš ืคืขื•ืœื•ืช ื‘ื”ืชืื ืœืžืฆื‘ ื”ื ื•ื›ื—ื™ ืฉืœ ื”ื™ื™ืฉื•ื. ืœืื—ืจ ืžื›ืŸ ื”ื•ื ื™ื—ื–ื™ืจ ืžืฆื‘ ืฉื•ื ื” ื‘ื”ืชืื ืœืกื•ื’ ื”ืคืขื•ืœื” ื•ื”ื ืชื•ื ื™ื.

ื•ื‘ื›ืŸ, ืžื›ื™ื•ื•ืŸ ืฉื”ื—ื ื•ืช ื”ื™ื BindableObject, ื”ื™ื ืชื•ื“ื™ืข ืœ- SwiftUI ื›ืืฉืจ ื”ืขืจืš ืฉืœื” ืžืฉืชื ื” ื‘ืืžืฆืขื•ืช ื”ืžืืคื™ื™ืŸ willChange ืฉืกื•ืคืง ืขืœ ื™ื“ื™ PassthroughSubject. ื”ืกื™ื‘ื” ืœื›ืš ื”ื™ื ืฉื”-BindableObject ื—ื™ื™ื‘ ืœืกืคืง PublisherType, ืืš ื™ื™ืฉื•ื ื”ืคืจื•ื˜ื•ืงื•ืœ ืื—ืจืื™ ืœื ื™ื”ื•ืœื•. ื‘ืกืš ื”ื›ืœ, ื–ื”ื• ื›ืœื™ ื—ื–ืง ืžืื•ื“ ืฉืœ ืืคืœ. ื‘ื”ืชืื ืœื›ืš, ื‘ืžื—ื–ื•ืจ ื”ืจื™ื ื“ื•ืจ ื”ื‘ื, SwiftUI ื™ืกื™ื™ืข ื‘ืขื™ื‘ื•ื“ ื’ื•ืฃ ื”ืชืฆื•ื’ื•ืช ื‘ื”ืชืื ืœืฉื™ื ื•ื™ ื”ืžืฆื‘.

ืœืžืขืฉื”, ื–ื” ื›ืœ ื”ืœื‘ ื•ื”ืงืกื ืฉืœ SwiftUI. ื›ืขืช, ื‘ื›ืœ ืชืฆื•ื’ื” ืฉื ืจืฉืžืช ืœืžื“ื™ื ื”, ื”ืชืฆื•ื’ื” ืชื•ืฆื’ ืœืคื™ ื”ื ืชื•ื ื™ื ื”ืžืชืงื‘ืœื™ื ืžื”ืžื“ื™ื ื” ื•ืžื” ื”ืฉืชื ื”.

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

ื”ื—ื ื•ืช ืžื•ื–ืจืงืช ื›-EnvironmentObject ื›ืืฉืจ ื”ืืคืœื™ืงืฆื™ื” ืžืชื—ื™ืœื” ื•ืœืื—ืจ ืžื›ืŸ ื”ื™ื ื ื’ื™ืฉื” ื‘ื›ืœ ืชืฆื•ื’ื” ื‘ืืžืฆืขื•ืช @EnvironmentObject. ืื™ืŸ ืงื ืก ื‘ื™ืฆื•ืขื™ื ืžื›ื™ื•ื•ืŸ ืฉืžืืคื™ื™ื ื™ื ื ื’ื–ืจื™ื ืžืื•ื—ื–ืจื™ื ื‘ืžื”ื™ืจื•ืช ืื• ืžื—ื•ืฉื‘ื™ื ืžืžืฆื‘ ื”ื™ื™ืฉื•ื.

ื”ืงื•ื“ ืœืžืขืœื” ืžืฉื ื” ืืช ื”ืชืžื•ื ื” ืื ืคื•ืกื˜ืจ ื”ืกืจื˜ ืžืฉืชื ื”.

ื•ื”ื“ื‘ืจ ื ืขืฉื” ืœืžืขืฉื” ื‘ืงื• ืื—ื“ ื‘ืœื‘ื“, ืฉื‘ืขื–ืจืชื• ืžื—ื•ื‘ืจื•ืช ื“ืขื•ืช ืœืžื“ื™ื ื”. ืื ืขื‘ื“ืช ืขื ReSwift ื‘-iOS ืื• ืืคื™ืœื• ืœึฐื—ึทื‘ึผึตืจ ืขื React, ืชื‘ื™ื ื• ืืช ื”ืงืกื ืฉืœ SwiftUI.

ื›ืขืช ืชื•ื›ืœ ืœื ืกื•ืช ืœื”ืคืขื™ืœ ืืช ื”ืคืขื•ืœื” ื•ืœืคืจืกื ืืช ื”ืžืฆื‘ ื”ื—ื“ืฉ. ื”ื ื” ื“ื•ื’ืžื” ืžื•ืจื›ื‘ืช ื™ื•ืชืจ.

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

ื‘ืงื•ื“ ืœืžืขืœื”, ืื ื™ ืžืฉืชืžืฉ ื‘ืคืขื•ืœืช .onDelete ืž- SwiftUI ืขื‘ื•ืจ ื›ืœ IP. ื–ื” ืžืืคืฉืจ ืœืฉื•ืจื” ื‘ืจืฉื™ืžื” ืœื”ืฆื™ื’ ืืช ื”ื”ื—ืœืงื” ื”ืจื’ื™ืœื” ืฉืœ iOS ืœืžื—ื™ืงื”. ืื– ื›ืืฉืจ ื”ืžืฉืชืžืฉ ื ื•ื’ืข ื‘ื›ืคืชื•ืจ ื”ืžื—ื™ืงื”, ื”ื•ื ืžืคืขื™ืœ ืืช ื”ืคืขื•ืœื” ื”ืžืชืื™ืžื” ื•ืžืกื™ืจ ืืช ื”ืกืจื˜ ืžื”ืจืฉื™ืžื”.

ื•ื‘ื›ืŸ, ืžื›ื™ื•ื•ืŸ ืฉืžืืคื™ื™ืŸ ื”ืจืฉื™ืžื” ื ื’ื–ืจ ืžืžืฆื‘ BindableObject ื•ืžื•ื–ืจืง ื›- EnvironmentObject, SwiftUI ืžืขื“ื›ืŸ ืืช ื”ืจืฉื™ืžื” ืžื›ื™ื•ื•ืŸ ืฉ-ForEach ืžืฉื•ื™ืš ืœืžืืคื™ื™ืŸ ื”ืžื—ื•ืฉื‘ ืฉืœ ื”ืกืจื˜ื™ื.

ื”ื ื” ื—ืœืง ืžืžืคื—ื™ืช 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
}

ื”ืžืคื—ื™ืช ืžื‘ื•ืฆืข ื›ืืฉืจ ืืชื” ืฉื•ืœื— ืคืขื•ืœื” ื•ืžื—ื–ื™ืจ ืžืฆื‘ ื—ื“ืฉ, ื›ืคื™ ืฉืฆื•ื™ืŸ ืœืขื™ืœ.

ืื ื™ ืขื•ื“ ืœื ืืคืจื˜ - ืื™ืš SwiftUI ื‘ืขืฆื ื™ื•ื“ืข ืžื” ืœื”ืฆื™ื’. ื›ื“ื™ ืœื”ื‘ื™ืŸ ืืช ื–ื” ื™ื•ืชืจ ืœืขื•ืžืง, ื–ื” ืฉื•ื•ื” ื”ืฆื’ ื”ืคืขืœื” ืฉืœ WWDC ืขืœ ื–ืจื™ืžืช ื ืชื•ื ื™ื ื‘- SwiftUI. ื–ื” ื’ื ืžืกื‘ื™ืจ ื‘ืคื™ืจื•ื˜ ืœืžื” ื•ืžืชื™ ืœื”ืฉืชืžืฉ ืžื“ื™ื ื”, @Binding, ObjectBinding ื•- EnvironmentObject.

Skillbox ืžืžืœื™ืฆื”:

ืžืงื•ืจ: www.habr.com

ื”ื•ืกืคืช ืชื’ื•ื‘ื”