SwiftUIలో అప్లికేషన్ అభివృద్ధి. పార్ట్ 1: డేటాఫ్లో మరియు రీడక్స్
WWDC 2019లో స్టేట్ ఆఫ్ ది యూనియన్ సెషన్కు హాజరైన తర్వాత, నేను SwiftUIలో లోతుగా డైవ్ చేయాలని నిర్ణయించుకున్నాను. నేను దానితో పని చేయడానికి చాలా సమయాన్ని వెచ్చించాను మరియు ఇప్పుడు విస్తృత శ్రేణి వినియోగదారులకు ఉపయోగపడే నిజమైన అప్లికేషన్ను అభివృద్ధి చేయడం ప్రారంభించాను.
నేను దీనిని MovieSwiftUI అని పిలిచాను - ఇది కొత్త మరియు పాత చిత్రాల కోసం శోధించడానికి, అలాగే వాటిని ఉపయోగించి సేకరణలో సేకరించడానికి ఒక యాప్. TMDB API. నేను ఎప్పుడూ సినిమాలను ఇష్టపడుతున్నాను మరియు చాలా కాలం క్రితం ఈ రంగంలో పనిచేసే సంస్థను కూడా సృష్టించాను. కంపెనీని కూల్ అని పిలవలేము, కానీ అప్లికేషన్!
మేము గుర్తు చేస్తున్నాము:Habr పాఠకులందరికీ - Habr ప్రోమో కోడ్ని ఉపయోగించి ఏదైనా Skillbox కోర్సులో నమోదు చేసుకున్నప్పుడు 10 రూబుల్ తగ్గింపు.
APIతో పరస్పర చర్య చేస్తుంది - దాదాపు ఏదైనా ఆధునిక అప్లికేషన్ దీన్ని చేస్తుంది.
అభ్యర్థనలపై అసమకాలిక డేటాను లోడ్ చేస్తుంది మరియు ఉపయోగించి స్విఫ్ట్ మోడల్లో JSONని అన్వయిస్తుంది కోడబుల్.
అభ్యర్థనపై లోడ్ చేయబడిన చిత్రాలను చూపుతుంది మరియు వాటిని కాష్ చేస్తుంది.
iOS, iPadOS మరియు macOS కోసం ఈ యాప్ ఈ OSల వినియోగదారుల కోసం ఉత్తమ UXని అందిస్తుంది.
వినియోగదారు డేటాను రూపొందించవచ్చు మరియు వారి స్వంత చలనచిత్ర జాబితాలను సృష్టించవచ్చు. అప్లికేషన్ వినియోగదారు డేటాను సేవ్ చేస్తుంది మరియు పునరుద్ధరిస్తుంది.
వీక్షణలు, భాగాలు మరియు నమూనాలు Redux నమూనాను ఉపయోగించి స్పష్టంగా వేరు చేయబడ్డాయి. ఇక్కడ డేటా ఫ్లో ఏకదిశగా ఉంటుంది. ఇది పూర్తిగా కాష్ చేయబడవచ్చు, పునరుద్ధరించబడుతుంది మరియు తిరిగి వ్రాయబడుతుంది.
అప్లికేషన్ SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal మొదలైన ప్రాథమిక భాగాలను ఉపయోగిస్తుంది. ఇది అనుకూల వీక్షణలు, సంజ్ఞలు, UI/UXని కూడా అందిస్తుంది.
నిజానికి, యానిమేషన్ మృదువైనది, GIF కొద్దిగా జెర్కీగా మారింది
యాప్లో పని చేయడం నాకు చాలా అనుభవాన్ని ఇచ్చింది మరియు మొత్తంగా ఇది సానుకూల అనుభవం. నేను పూర్తిగా ఫంక్షనల్ అప్లికేషన్ను వ్రాయగలిగాను, సెప్టెంబరులో నేను iOS 13 విడుదలతో ఏకకాలంలో దాన్ని మెరుగుపరుస్తాను మరియు AppStoreలో ప్రచురిస్తాను.
Redux, BindableObject మరియు EnvironmentObject
నేను ఇప్పుడు సుమారు రెండు సంవత్సరాలుగా Reduxతో పని చేస్తున్నాను, కాబట్టి నేను దానిలో బాగా ప్రావీణ్యం కలిగి ఉన్నాను. ముఖ్యంగా, నేను దానిని ఫ్రంటెండ్లో ఉపయోగిస్తాను స్పందించలేదు వెబ్సైట్, అలాగే స్థానిక iOS (స్విఫ్ట్) మరియు ఆండ్రాయిడ్ (కోట్లిన్) అప్లికేషన్లను అభివృద్ధి చేయడం కోసం.
SwiftUI అప్లికేషన్ను రూపొందించడానికి Reduxని డేటా ఫ్లో ఆర్కిటెక్చర్గా ఎంచుకున్నందుకు నేను ఎప్పుడూ చింతించలేదు. UIKit యాప్లో Reduxని ఉపయోగిస్తున్నప్పుడు అత్యంత సవాలుగా ఉండే భాగాలు స్టోర్తో పని చేయడం మరియు డేటాను పొందడం మరియు తిరిగి పొందడం మరియు దానిని మీ వీక్షణలు/భాగాలకు మ్యాపింగ్ చేయడం. దీన్ని చేయడానికి, నేను ఒక రకమైన కనెక్టర్ల లైబ్రరీని సృష్టించాల్సి వచ్చింది (ReSwift మరియు ReKotlin ఉపయోగించి). బాగా పనిచేస్తుంది, కానీ చాలా కోడ్. దురదృష్టవశాత్తు, ఇది (ఇంకా) ఓపెన్ సోర్స్ కాదు.
శుభవార్త! SwiftUIతో ఆందోళన చెందాల్సిన విషయాలు - మీరు Reduxని ఉపయోగించాలని ప్లాన్ చేస్తే - స్టోర్లు, స్టేట్లు మరియు రీడ్యూసర్లు. @EnvironmentObjectకి ధన్యవాదాలు SwiftUI ద్వారా స్టోర్తో పరస్పర చర్య పూర్తిగా నిర్వహించబడుతుంది. కాబట్టి, స్టోర్ బైండబుల్ ఆబ్జెక్ట్తో ప్రారంభమవుతుంది.
నేను సాధారణ స్విఫ్ట్ ప్యాకేజీని సృష్టించాను, 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)
}
}
మీరు చర్యను ప్రారంభించిన ప్రతిసారీ, మీరు గేర్బాక్స్ను సక్రియం చేస్తారు. ఇది అప్లికేషన్ యొక్క ప్రస్తుత స్థితికి అనుగుణంగా చర్యలను మూల్యాంకనం చేస్తుంది. ఇది చర్య రకం మరియు డేటాకు అనుగుణంగా కొత్త సవరించిన స్థితిని అందిస్తుంది.
సరే, స్టోర్ బైండబుల్ ఆబ్జెక్ట్ అయినందున, PassthroughSubject అందించిన willChange ప్రాపర్టీని ఉపయోగించి దాని విలువ మారినప్పుడు అది SwiftUIకి తెలియజేస్తుంది. ఎందుకంటే BindableObject తప్పనిసరిగా PublisherTypeని అందించాలి, కానీ ప్రోటోకాల్ అమలు దానిని నిర్వహించడానికి బాధ్యత వహిస్తుంది. మొత్తంమీద, ఇది Apple నుండి చాలా శక్తివంతమైన సాధనం. దీని ప్రకారం, తదుపరి రెండరింగ్ సైకిల్లో, రాష్ట్ర మార్పుకు అనుగుణంగా వీక్షణల బాడీని అందించడంలో 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ని ఉపయోగించి ఏ వీక్షణలోనైనా యాక్సెస్ చేయవచ్చు. ఉత్పన్నమైన లక్షణాలు త్వరగా తిరిగి పొందబడతాయి లేదా అప్లికేషన్ స్థితి నుండి లెక్కించబడతాయి కాబట్టి పనితీరు పెనాల్టీ లేదు.
సినిమా పోస్టర్ మారితే పై కోడ్ చిత్రాన్ని మారుస్తుంది.
మరియు ఇది వాస్తవానికి కేవలం ఒక లైన్తో చేయబడుతుంది, వీక్షణలు రాష్ట్రానికి అనుసంధానించబడిన సహాయంతో. మీరు iOSలో లేదా కూడా ReSwiftతో పని చేసి ఉంటే కనెక్ట్ రియాక్ట్తో, మీరు 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!]))
}
}
}
}
పై కోడ్లో, నేను ప్రతి IP కోసం SwiftUI నుండి .onDelete చర్యను ఉపయోగిస్తున్నాను. ఇది తొలగించడానికి సాధారణ iOS స్వైప్ను ప్రదర్శించడానికి జాబితాలోని అడ్డు వరుసను అనుమతిస్తుంది. కాబట్టి వినియోగదారు తొలగించు బటన్ను తాకినప్పుడు, అది సంబంధిత చర్యను ప్రేరేపిస్తుంది మరియు జాబితా నుండి మూవీని తీసివేస్తుంది.
సరే, జాబితా ప్రాపర్టీ BindableObject స్థితి నుండి తీసుకోబడింది మరియు ఎన్విరాన్మెంట్ ఆబ్జెక్ట్గా ఇంజెక్ట్ చేయబడినందున, SwiftUI జాబితాను అప్డేట్ చేస్తుంది ఎందుకంటే ForEach చలనచిత్రాల గణించిన ఆస్తితో అనుబంధించబడింది.
మూవీస్స్టేట్ రీడ్యూసర్లో భాగం ఇక్కడ ఉంది:
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లో. ఇది ఎందుకు మరియు ఎప్పుడు ఉపయోగించాలో కూడా వివరంగా వివరిస్తుంది రాష్ట్రం, @ బైండింగ్, ఆబ్జెక్ట్ బైండింగ్ మరియు ఎన్విరాన్మెంట్ ఆబ్జెక్ట్.