αž€αžΆαžšαž’αž—αž·αžœαžŒαŸ’αžαž“αŸαž€αž˜αŸ’αž˜αžœαž·αž’αžΈαž“αŸ…αž›αžΎ SwiftUI αŸ” αž•αŸ’αž“αŸ‚αž€αž‘αžΈ 1: αž›αŸ†αž αžΌαžšαž‘αž·αž“αŸ’αž“αž“αŸαž™ αž“αž·αž„ Redux

αž€αžΆαžšαž’αž—αž·αžœαžŒαŸ’αžαž“αŸαž€αž˜αŸ’αž˜αžœαž·αž’αžΈαž“αŸ…αž›αžΎ SwiftUI αŸ” αž•αŸ’αž“αŸ‚αž€αž‘αžΈ 1: αž›αŸ†αž αžΌαžšαž‘αž·αž“αŸ’αž“αž“αŸαž™ αž“αž·αž„ Redux

αž”αž“αŸ’αž‘αžΆαž”αŸ‹αž–αžΈαž”αžΆαž“αž…αžΌαž›αžšαž½αž˜αžŸαž˜αŸαž™αž”αŸ’αžšαž‡αž»αŸ†αžšαžŠαŸ’αž‹αž“αŸƒαžŸαž αž—αžΆαž–αž“αŸ… WWDC αž†αŸ’αž“αžΆαŸ† 2019 αžαŸ’αž‰αž»αŸ†αž”αžΆαž“αžŸαž˜αŸ’αžšαŸαž…αž…αž·αžαŸ’αžαž…αžΌαž›αž‡αŸ’αžšαŸ…αž‘αŸ…αž€αŸ’αž“αž»αž„ SwiftUI αŸ” αžαŸ’αž‰αž»αŸ†αž”αžΆαž“αž…αŸ†αžŽαžΆαž™αž–αŸαž›αž…αŸ’αžšαžΎαž“αž€αŸ’αž“αž»αž„αž€αžΆαžšαž’αŸ’αžœαžΎαž€αžΆαžšαž‡αžΆαž˜αž½αž™αžœαžΆ αž αžΎαž™αž₯αž‘αžΌαžœαž“αŸαŸ‡αž”αžΆαž“αž…αžΆαž”αŸ‹αž•αŸ’αžαžΎαž˜αž”αž„αŸ’αž€αžΎαžαž€αž˜αŸ’αž˜αžœαž·αž’αžΈαž–αž·αžαž”αŸ’αžšαžΆαž€αžŠαžŠαŸ‚αž›αž’αžΆαž…αž˜αžΆαž“αž”αŸ’αžšαž™αŸ„αž‡αž“αŸαžŠαž›αŸ‹αž’αŸ’αž“αž€αž”αŸ’αžšαžΎαž”αŸ’αžšαžΆαžŸαŸ‹αž‡αžΆαž…αŸ’αžšαžΎαž“αŸ”

αžαŸ’αž‰αž»αŸ†αž”αžΆαž“αž αŸ…αžœαžΆαžαžΆ MovieSwiftUI - αž“αŸαŸ‡αž‚αžΊαž‡αžΆαž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžŸαž˜αŸ’αžšαžΆαž”αŸ‹αžŸαŸ’αžœαŸ‚αž„αžšαž€αžαŸ’αžŸαŸ‚αž—αžΆαž–αž™αž“αŸ’αžαžαŸ’αž˜αžΈ αž“αž·αž„αž…αžΆαžŸαŸ‹ αž€αŸαžŠαžΌαž…αž‡αžΆαž€αžΆαžšαž”αŸ’αžšαž˜αžΌαž›αžœαžΆαž“αŸ…αž€αŸ’αž“αž»αž„αž”αžŽαŸ’αžαž»αŸ†αžŠαŸ„αž™αž”αŸ’αžšαžΎ TMDB API. αžαŸ’αž‰αž»αŸ†αžαŸ‚αž„αžαŸ‚αž…αžΌαž›αž…αž·αžαŸ’αžαž—αžΆαž–αž™αž“αŸ’αž αž αžΎαž™αžαŸ‚αž˜αž‘αžΆαŸ†αž„αž”αž„αŸ’αž€αžΎαžαž€αŸ’αžšαž»αž˜αž αŸŠαž»αž“αž˜αž½αž™αžŠαŸ‚αž›αž€αŸ†αž–αž»αž„αž’αŸ’αžœαžΎαž€αžΆαžšαž€αŸ’αž“αž»αž„αžœαž·αžŸαŸαž™αž“αŸαŸ‡ αž‘αŸ„αŸ‡αž”αžΈαž‡αžΆαž™αžΌαžšαž™αžΆαžšαžŽαžΆαžŸαŸ‹αž˜αž€αž αžΎαž™αž€αŸαžŠαŸ„αž™αŸ” αž€αŸ’αžšαž»αž˜αž αŸŠαž»αž“αžŸαŸ’αž‘αžΎαžšαžαŸ‚αž˜αž·αž“αž’αžΆαž…αž αŸ…αžαžΆαž‘αžΌαž™ αž”αŸ‰αž»αž“αŸ’αžαŸ‚αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαž‚αžΊ!

αž™αžΎαž„αžšαŸ†αž›αžΉαž€αŸ– αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž’αŸ’αž“αž€αž’αžΆαž“αž‘αžΆαŸ†αž„αž’αžŸαŸ‹αž“αŸƒ "Habr" - αž€αžΆαžšαž”αž‰αŸ’αž…αž»αŸ‡αžαž˜αŸ’αž›αŸƒ 10 rubles αž“αŸ…αž–αŸαž›αž…αž»αŸ‡αžˆαŸ’αž˜αŸ„αŸ‡αž€αŸ’αž“αž»αž„αžœαž‚αŸ’αž‚αžŸαž·αž€αŸ’αžŸαžΆ Skillbox αžŽαžΆαž˜αž½αž™αžŠαŸ„αž™αž”αŸ’αžšαžΎαž›αŸαžαž€αžΌαžŠαž•αŸ’αžŸαž–αŸ’αžœαž•αŸ’αžŸαžΆαž™ "Habr" αŸ”

Skillbox αžŽαŸ‚αž“αžΆαŸ†αŸ– αžœαž‚αŸ’αž‚αžŸαž·αž€αŸ’αžŸαžΆαžαžΆαž˜αž’αŸŠαžΈαž“αž’αžΊαžŽαž·αž "Profession Java Developer".

αžŠαžΌαž…αŸ’αž“αŸαŸ‡αžαžΎ MovieSwiftUI αž’αžΆαž…αž’αŸ’αžœαžΎαž’αŸ’αžœαžΈαž”αžΆαž“?

  • αž’αž“αŸ’αžαžšαž€αž˜αŸ’αž˜αž‡αžΆαž˜αž½αž™ API - αžŸαŸ’αž‘αžΎαžšαžαŸ‚αž‚αŸ’αžšαž”αŸ‹αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαž‘αŸ†αž“αžΎαž”αž’αŸ’αžœαžΎαž”αŸ‚αž”αž“αŸαŸ‡αŸ”
  • αž•αŸ’αž‘αž»αž€αž‘αž·αž“αŸ’αž“αž“αŸαž™αž’αžŸαž˜αž€αžΆαž›αž“αŸ…αž›αžΎαžŸαŸ†αžŽαžΎ αž“αž·αž„αž‰αŸ‚αž€ JSON αž‘αŸ…αž€αŸ’αž“αž»αž„αž‚αŸ†αžšαžΌ Swift αžŠαŸ„αž™αž”αŸ’αžšαžΎ Codable.
  • αž”αž„αŸ’αž αžΆαž‰αžšαžΌαž”αž—αžΆαž–αžŠαŸ‚αž›αž”αžΆαž“αž•αŸ’αž‘αž»αž€αžαžΆαž˜αžŸαŸ†αžŽαžΎ αž“αž·αž„αžšαž€αŸ’αžŸαžΆαž‘αž»αž€αž–αž½αž€αžœαžΆαŸ”
  • αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαž“αŸαŸ‡αžŸαž˜αŸ’αžšαžΆαž”αŸ‹ iOS, iPadOS αž“αž·αž„ macOS αž•αŸ’αžαž›αŸ‹αž“αžΌαžœ UX αžŠαŸαž›αŸ’αž’αž”αŸ†αž•αž»αžαžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž’αŸ’αž“αž€αž”αŸ’αžšαžΎαž”αŸ’αžšαžΆαžŸαŸ‹αž”αŸ’αžšαž–αŸαž“αŸ’αž’αž”αŸ’αžšαžαž·αž”αžαŸ’αžαž·αž€αžΆαžšαž‘αžΆαŸ†αž„αž“αŸαŸ‡αŸ”
  • αž’αŸ’αž“αž€αž”αŸ’αžšαžΎαž”αŸ’αžšαžΆαžŸαŸ‹αž’αžΆαž…αž”αž„αŸ’αž€αžΎαžαž‘αž·αž“αŸ’αž“αž“αŸαž™ αž“αž·αž„αž”αž„αŸ’αž€αžΎαžαž”αž‰αŸ’αž‡αžΈαž—αžΆαž–αž™αž“αŸ’αžαž•αŸ’αž‘αžΆαž›αŸ‹αžαŸ’αž›αž½αž“αžšαž”αžŸαŸ‹αž–αž½αž€αž‚αŸαŸ” αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαžšαž€αŸ’αžŸαžΆαž‘αž»αž€ αž“αž·αž„αžŸαŸ’αžŠαžΆαžšαž‘αž·αž“αŸ’αž“αž“αŸαž™αž’αŸ’αž“αž€αž”αŸ’αžšαžΎαž”αŸ’αžšαžΆαžŸαŸ‹αŸ”
  • αž‘αž·αžŠαŸ’αž‹αž—αžΆαž– αžŸαž˜αžΆαžŸαž—αžΆαž‚ αž“αž·αž„αž˜αŸ‰αžΌαžŠαŸ‚αž›αžαŸ’αžšαžΌαžœαž”αžΆαž“αž”αŸ†αž”αŸ‚αž€αž™αŸ‰αžΆαž„αž…αŸ’αž”αžΆαžŸαŸ‹αžŠαŸ„αž™αž”αŸ’αžšαžΎαž›αŸ†αž“αžΆαŸ† Redux αŸ” αž›αŸ†αž αžΌαžšαž‘αž·αž“αŸ’αž“αž“αŸαž™αž“αŸ…αž‘αžΈαž“αŸαŸ‡αž‚αžΊαž‚αŸ’αž˜αžΆαž“αž‘αž·αžŸαžŠαŸ…αŸ” αžœαžΆαž’αžΆαž…αžαŸ’αžšαžΌαžœαž”αžΆαž“αžŠαžΆαž€αŸ‹αž€αŸ’αž“αž»αž„αžƒαŸ’αž›αžΆαŸ†αž„αžŸαž˜αŸ’αž„αžΆαžαŸ‹αž–αŸαž‰αž›αŸαž‰ αžŸαŸ’αžŠαžΆαžš αž“αž·αž„αžŸαžšαžŸαŸαžšαž‡αžΆαž“αŸ‹αž–αžΈαž›αžΎαŸ”
  • αž€αž˜αŸ’αž˜αžœαž·αž’αžΈαž”αŸ’αžšαžΎαž”αŸ’αžšαžΆαžŸαŸ‹αžŸαž˜αžΆαžŸαž’αžΆαžαž»αž‡αžΆαž˜αžΌαž›αžŠαŸ’αž‹αžΆαž“αž“αŸƒ SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal αž‡αžΆαžŠαžΎαž˜αŸ” αžœαžΆαž€αŸαž•αŸ’αžαž›αŸ‹αž“αžΌαžœαž‘αž·αžŠαŸ’αž‹αž—αžΆαž–αž•αŸ’αž‘αžΆαž›αŸ‹αžαŸ’αž›αž½αž“ αž€αžΆαž™αžœαž·αž€αžΆαžš UI/UX αž•αž„αžŠαŸ‚αžšαŸ”

αž€αžΆαžšαž’αž—αž·αžœαžŒαŸ’αžαž“αŸαž€αž˜αŸ’αž˜αžœαž·αž’αžΈαž“αŸ…αž›αžΎ SwiftUI αŸ” αž•αŸ’αž“αŸ‚αž€αž‘αžΈ 1: αž›αŸ†αž αžΌαžšαž‘αž·αž“αŸ’αž“αž“αŸαž™ αž“αž·αž„ Redux
αžαžΆαž˜β€‹αž–αž·αž αž…αž›αž“αžΆβ€‹αž‚αžΊβ€‹αžšαž›αžΌαž“ GIF αž”αžΆαž“β€‹αž”αŸ’αžšαŸ‚β€‹αž‘αŸ…β€‹αž‡αžΆβ€‹αž€αž“αŸ’αžαŸ’αžšαžΆαž€αŸ‹β€‹αž”αž“αŸ’αžαž·αž…

αž€αžΆαžšαž’αŸ’αžœαžΎαž€αžΆαžšαž›αžΎαž€αž˜αŸ’αž˜αžœαž·αž’αžΈαž”αžΆαž“αž•αŸ’αžαž›αŸ‹αž±αŸ’αž™αžαŸ’αž‰αž»αŸ†αž“αžΌαžœαž”αž‘αž–αž·αžŸαŸ„αž’αž“αŸαž‡αžΆαž…αŸ’αžšαžΎαž“ αž αžΎαž™αž‡αžΆαž‘αžΌαž‘αŸ…αžœαžΆαž‚αžΊαž‡αžΆαž”αž‘αž–αž·αžŸαŸ„αž’αž“αŸαžœαž·αž‡αŸ’αž‡αž˜αžΆαž“αŸ” αžαŸ’αž‰αž»αŸ†β€‹αž’αžΆαž…β€‹αžŸαžšαžŸαŸαžšβ€‹αž€αž˜αŸ’αž˜αžœαž·αž’αžΈβ€‹αžŠαŸ‚αž›β€‹αž˜αžΆαž“β€‹αž˜αž»αžαž„αžΆαžšβ€‹αž–αŸαž‰β€‹αž›αŸαž‰β€‹ αž αžΎαž™β€‹αž“αŸ…β€‹αžαŸ‚β€‹αž€αž‰αŸ’αž‰αžΆβ€‹αžαŸ’αž‰αž»αŸ†β€‹αž“αžΉαž„β€‹αž€αŸ‚αž›αž˜αŸ’αž’β€‹αžœαžΆβ€‹αž αžΎαž™β€‹αž”αŸ„αŸ‡αž•αŸ’αžŸαžΆαž™β€‹αžœαžΆβ€‹αž€αŸ’αž“αž»αž„β€‹ AppStore αž–αŸ’αžšαž˜β€‹αž‚αŸ’αž“αžΆβ€‹αž“αžΉαž„β€‹αž€αžΆαžšβ€‹αž…αŸαž‰β€‹ iOS 13αŸ”

Redux, BindableObject αž“αž·αž„ EnvironmentObject

αž€αžΆαžšαž’αž—αž·αžœαžŒαŸ’αžαž“αŸαž€αž˜αŸ’αž˜αžœαž·αž’αžΈαž“αŸ…αž›αžΎ SwiftUI αŸ” αž•αŸ’αž“αŸ‚αž€αž‘αžΈ 1: αž›αŸ†αž αžΌαžšαž‘αž·αž“αŸ’αž“αž“αŸαž™ αž“αž·αž„ Redux

αžαŸ’αž‰αž»αŸ†αž”αžΆαž“αž’αŸ’αžœαžΎαž€αžΆαžšαž‡αžΆαž˜αž½αž™ Redux αž”αŸ’αžšαž αŸ‚αž› XNUMX αž†αŸ’αž“αžΆαŸ†αž αžΎαž™ αžŠαžΌαž…αŸ’αž“αŸαŸ‡αžαŸ’αž‰αž»αŸ†αž™αž›αŸ‹αž…αŸ’αž”αžΆαžŸαŸ‹αž’αŸ†αž–αžΈαžœαžΆαŸ” αž‡αžΆαž–αž·αžŸαŸαžŸαžαŸ’αž‰αž»αŸ†αž”αŸ’αžšαžΎαžœαžΆαž“αŸ…αž€αŸ’αž“αž»αž„ 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 αž”αŸ‰αž»αž“αŸ’αžαŸ‚αž€αžΆαžšαž’αž“αž»αžœαžαŸ’αžαž–αž·αž’αžΈαž€αžΆαžšαž‚αžΊαž‘αž‘αž½αž›αžαž»αžŸαžαŸ’αžšαžΌαžœαž€αŸ’αž“αž»αž„αž€αžΆαžšαž‚αŸ’αžšαž”αŸ‹αž‚αŸ’αžšαž„αžœαžΆαŸ” αžŸαžšαž»αž”αž˜αž€ αž“αŸαŸ‡αž‚αžΊαž‡αžΆαž§αž”αž€αžšαžŽαŸαžŠαŸαž˜αžΆαž“αž₯αž‘αŸ’αž’αž·αž–αž›αž”αŸ†αž•αž»αžαž–αžΈ 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())
    }
}

Store αžαŸ’αžšαžΌαžœαž”αžΆαž“αž…αžΆαž€αŸ‹αž”αž‰αŸ’αž…αžΌαž›αž‡αžΆ 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 reducerαŸ–

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

αž”αž“αŸ’αžαŸ‚αž˜αž˜αžαž·αž™αŸ„αž”αž›αŸ‹