Jos olet iOS-kehittäjä Kuka on tehnyt kohtuullisen määrän käyttöliittymää ja on siitä intohimoinen, sinun on rakastettava UIKitin voimaa animaatioiden suhteen. UIView-animaatio on yhtä helppoa kuin kakku. Sinun ei tarvitse ajatella paljon siitä, miten saada se haalistumaan, kiertymään, liikkumaan tai kutistumaan / laajentumaan ajan myötä. Siihen kuitenkin liittyy vähän, jos haluat ketjuttaa animaatioita yhteen ja asettaa riippuvuuksia niiden välille. Koodisi voi olla lopulta melko yksityiskohtainen ja vaikeasti seurattava, ja siinä on monia sisäkkäisiä sulkemisia ja sisennystasoja.
Tässä artikkelissa tutkin, miten RxSwiftin kaltaisen reaktiivisen kehyksen voimaa voidaan käyttää, jotta koodi näyttää paljon puhtaammalta sekä helpommin luettavalta ja seurattavalta. Idea tuli minulle, kun olin tekemässä projektia asiakkaalle. Kyseinen asiakas oli erittäin käyttöliittymätajuinen (joka sopi täydellisesti intohimoani)! He halusivat sovelluksensa käyttöliittymän käyttäytyvän hyvin erityisellä tavalla, lukuisilla erittäin tyylikkäillä siirtymillä ja animaatioilla. Yksi heidän ideoistaan oli saada käyttöönotto sovellukseen, joka kertoi tarinan siitä, mistä sovelluksessa oli kyse. He halusivat, että tarina kerrotaan animaatioiden sarjan kautta pikemminkin kuin toistamalla ennalta renderöityä videota, jotta se voitaisiin helposti säätää ja virittää. RxSwift osoittautui täydelliseksi valinnaksi tällaiselle ongelmalle, koska toivon, että ymmärrät sen, kun olet viimeistellyt artikkelin.
Reaktiivisesta ohjelmoinnista on tulossa katkottua osa, ja se on otettu käyttöön useimmissa moderneissa ohjelmointikielissä. Siellä on paljon kirjoja ja blogeja, joissa selitetään yksityiskohtaisesti, miksi reaktiivinen ohjelmointi on niin tehokas käsite ja kuinka se auttaa kannustamaan hyvää ohjelmistosuunnittelua noudattamalla tiettyjä suunnitteluperiaatteita ja -malleja. Se antaa sinulle myös työkalupaketin, joka voi auttaa vähentämään merkittävästi koodin sotkua.
Haluaisin koskettaa yhtä näkökohtaa, joka todella pidän - helppous, jolla voit ketjuttaa asynkronisia toimintoja ja ilmaista ne selittävällä, helposti luettavalla tavalla.
Swiftin suhteen on kaksi kilpailevaa kehystä, jotka auttavat sinua muuttamaan sen reaktiiviseksi ohjelmointikieleksi: ReactiveSwift ja RxSwift. En käytä RxSwiftia esimerkeissäni siksi, että se olisi parempi, vaan siksi, että tunnen sen paremmin. Oletan, että sinäkin, lukija, tunnet sen hyvin, jotta pääsen suoraan sen lihaan.
Oletetaan, että haluat kiertää näkymää 180 ° ja sitten häivyttää sen. Voit käyttää completion
sulje ja tee jotain tällaista:
UIView.animate(withDuration: 0.5, animations: { self.animatableView.transform = CGAffineTransform(rotationAngle: .pi/2) }, completion: { _ in UIView.animate(withDuration: 0.5) { self.animatableView.alpha = 0 } })
Se on vähän iso, mutta silti kunnossa. Mutta entä jos haluat lisätä vielä yhden animaation väliin, sano, siirrä näkymää oikealle sen kiertämisen jälkeen ja ennen kuin se häviää? Soveltamalla samaa lähestymistapaa päädyt jotain tällaiseen:
UIView.animate(withDuration: 0.5, animations: { self.animatableView.transform = CGAffineTransform(rotationAngle: .pi/2) }, completion: { _ in UIView.animate(withDuration: 0.5, animations: { self.animatableView.frame = self.animatableView.frame.offsetBy(dx: 50, dy: 0) }, completion: { _ in UIView.animate(withDuration: 0.5, animations: { self.animatableView.alpha = 0 }) }) })
Mitä enemmän vaiheita siihen lisätään, sitä porrastetumpi ja hankalampi se on. Ja sitten, jos päätät muuttaa tiettyjen vaiheiden järjestystä, joudut suorittamaan jonkin ei-triviaalin leikkaus- ja liittämisjakson, joka on altis virheille.
No, Apple on ilmeisesti ajatellut sitä - he tarjoavat paremman tavan tehdä tämä käyttämällä avainkehykseen perustuvia animaatioiden sovellusliittymää. Tällä lähestymistavalla yllä oleva koodi voidaan kirjoittaa uudestaan seuraavasti:
UIView.animateKeyframes(withDuration: 1.5, delay: 0, options: [], animations: { UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.33, animations: { self.animatableView.transform = CGAffineTransform(rotationAngle: .pi/2) }) UIView.addKeyframe(withRelativeStartTime: 0.33, relativeDuration: 0.33, animations: { self.animatableView.frame = self.animatableView.frame.offsetBy(dx: 50, dy: 0) }) UIView.addKeyframe(withRelativeStartTime: 0.66, relativeDuration: 0.34, animations: { self.animatableView.alpha = 0 }) })
Se on iso parannus, ja tärkeimmät edut ovat:
Tämän lähestymistavan haittana on, että sinun on ajatteltava suhteellisen keston suhteen ja vaiheen absoluuttisen ajoituksen tai järjestyksen muuttaminen on vaikeaa (tai ainakaan ei kovin suoraviivaista). Ajattele vain, mitä laskelmia sinun pitäisi käydä läpi ja millaisia muutoksia sinun tulisi tehdä jokaisen animaation kokonaiskestoon ja suhteelliseen kestoon / aloitusaikaan, jos päätät saada näkymän haalistumaan 1 sekunnissa puolen toinen pitäen kaikki muu ennallaan. Sama pätee, jos haluat muuttaa vaiheiden järjestystä - sinun on laskettava uudelleen niiden suhteelliset aloitusajat.
Ottaen huomioon haitat, en löydä mitään edellä mainituista lähestymistavoista riittävän hyviä. Etsimäni ihanteellisen ratkaisun tulisi täyttää seuraavat ehdot:
Huomasin, että RxSwiftin avulla voin helposti saavuttaa molemmat tavoitteet. RxSwift ei ole ainoa kehys, jota voit käyttää tällaisen tekemiseen - mikä tahansa lupauspohjainen kehys, jonka avulla voit kääriä asynkronointitoiminnot menetelmiin, jotka voidaan ketjuttaa yhteen ketjussa käyttämättä täydennyslohkoja. Mutta RxSwiftillä on paljon enemmän tarjottavaa operaattorivalikoimallaan, jota kosketamme hieman myöhemmin.
Tässä on pääpiirre siitä, miten aion tehdä sen:
Observable
.flatMap
operaattori.Näin toimintoni voivat näyttää:
func rotate(_ view: UIView, duration: TimeInterval) -> Observable { return Observable.create { (observer) -> Disposable in UIView.animate(withDuration: duration, animations: { view.transform = CGAffineTransform(rotationAngle: .pi/2) }, completion: { (_) in observer.onNext(()) observer.onCompleted() }) return Disposables.create() } } func shift(_ view: UIView, duration: TimeInterval) -> Observable { return Observable.create { (observer) -> Disposable in UIView.animate(withDuration: duration, animations: { view.frame = view.frame.offsetBy(dx: 50, dy: 0) }, completion: { (_) in observer.onNext(()) observer.onCompleted() }) return Disposables.create() } } func fade(_ view: UIView, duration: TimeInterval) -> Observable { return Observable.create { (observer) -> Disposable in UIView.animate(withDuration: duration, animations: { view.alpha = 0 }, completion: { (_) in observer.onNext(()) observer.onCompleted() }) return Disposables.create() } }
Ja näin laitan kaiken yhteen:
rotate(animatableView, duration: 0.5) .flatMap { [unowned self] in self.shift(self.animatableView, duration: 0.5) } .flatMap { [unowned self] in self.fade(self.animatableView, duration: 0.5) } .subscribe() .disposed(by: disposeBag)
Se on varmasti paljon enemmän koodia kuin edellisissä toteutuksissa, ja se voi näyttää hieman ylenmääräiseltä tällaiselle yksinkertaiselle animaatioille, mutta kauneus on, että sitä voidaan laajentaa käsittelemään joitain melko monimutkaisia animaatiojaksoja ja se on erittäin helppo lukea syntaksin julistava luonne.
Kun saat siitä käsityksen, voit luoda animaatioita niin monimutkaisiksi kuin elokuva ja sinulla on käytettävissänne laaja valikoima käteviä RxSwift-operaattoreita, joita voit käyttää sellaisten asioiden toteuttamiseen, joita olisi erittäin vaikea tehdä millä tahansa edellä mainituista lähestymistavoista.
Näin voimme käyttää .concat
operaattori tehdä koodistani vielä suppeampi - osa, jossa animaatiot ketjutetaan yhteen:
Observable.concat([ rotate(animatableView, duration: 0.5), shift(animatableView, duration: 0.5), fade(animatableView, duration: 0.5) ]) .subscribe() .disposed(by: disposeBag)
Voit lisätä viiveitä tällaisten animaatioiden väliin:
func delay(_ duration: TimeInterval) -> Observable { return Observable.of(()).delay(duration, scheduler: MainScheduler.instance) } Observable.concat([ rotate(animatableView, duration: 0.5), delay(0.5), shift(animatableView, duration: 0.5), delay(1), fade(animatableView, duration: 0.5) ]) .subscribe() .disposed(by: disposeBag)
Oletetaan nyt, että haluamme näkymän kiertyvän tietyn määrän kertoja ennen kuin se alkaa liikkua. Ja haluamme säätää helposti, kuinka monta kertaa sen pitäisi pyöriä.
Ensin teen menetelmän, joka toistaa rotaatioanimaation jatkuvasti ja lähettää elementin jokaisen kierroksen jälkeen. Haluan, että nämä kierrot pysähtyvät heti, kun havaittava on hävitetty. Voisin tehdä jotain tällaista:
func rotateEndlessly(_ view: UIView, duration: TimeInterval) -> Observable { var disposed = false return Observable.create { (observer) -> Disposable in func animate() { UIView.animate(withDuration: duration, animations: { view.transform = view.transform.rotated(by: .pi/2) }, completion: { (_) in observer.onNext(()) if !disposed { animate() } }) } animate() return Disposables.create { disposed = true } } }
Ja sitten kaunis animaatioketjuni voisi näyttää tältä:
Observable.concat([ rotateEndlessly(animatableView, duration: 0.5).take(5), shift(animatableView, duration: 0.5), fade(animatableView, duration: 0.5) ]) .subscribe() .disposed(by: disposeBag)
Näet kuinka helppoa on hallita kuinka monta kertaa näkymä pyörii - muuta vain take
operaattori.
Haluaisin nyt viedä toteutukseni askeleen pidemmälle käärimällä ne kaikki, jos luomani animaatiotoiminnot UIView: n 'Reactive' -laajennukseen (pääsee .rx
-liitteen kautta). Tämä tekisi siitä enemmän RxSwift-käytäntöjen mukaisesti, joissa reaktiivisiin toimintoihin pääsee yleensä .rx
pääte, jotta voidaan tehdä selväksi, että he palaavat havaittavissa oleviin.
extension Reactive where Base == UIView { func shift(duration: TimeInterval) -> Observable { return Observable.create { (observer) -> Disposable in UIView.animate(withDuration: duration, animations: { self.base.frame = self.base.frame.offsetBy(dx: 50, dy: 0) }, completion: { (_) in observer.onNext(()) observer.onCompleted() }) return Disposables.create() } } func fade(duration: TimeInterval) -> Observable { return Observable.create { (observer) -> Disposable in UIView.animate(withDuration: duration, animations: { self.base.alpha = 0 }, completion: { (_) in observer.onNext(()) observer.onCompleted() }) return Disposables.create() } } func rotateEndlessly(duration: TimeInterval) -> Observable { var disposed = false return Observable.create { (observer) -> Disposable in func animate() { UIView.animate(withDuration: duration, animations: { self.base.transform = self.base.transform.rotated(by: .pi/2) }, completion: { (_) in observer.onNext(()) if !disposed { animate() } }) } animate() return Disposables.create { disposed = true } } } }
Sen avulla voin koota ne yhteen näin:
Observable.concat([ animatableView.rx.rotateEndlessly(duration: 0.5).take(5), animatableView.rx.shift(duration: 0.5), animatableView.rx.fade(duration: 0.5) ]) .subscribe() .disposed(by: disposeBag)
Kuten tämä artikkeli havainnollistaa, vapauttamalla RxSwiftin voima ja kun primitiivisi ovat paikallaan, voit pitää hauskaa animaatioiden kanssa. Koodisi on puhdas ja helppo lukea, eikä se näytä enää koodilta - kuvailet kuinka animaatiosi kootaan ja ne vain heräävät eloon! Jos haluat tehdä jotain monimutkaisempaa kuin tässä on kuvattu, voit varmasti tehdä sen lisäämällä enemmän omia kapselointisi muuntyyppisiä animaatioita. Voit myös aina hyödyntää jatkuvasti kasvavaa työkalujen ja kehysten arsenaalia, jonka jotkut intohimoiset ihmiset ovat kehittäneet avoimen lähdekoodin yhteisössä.
Jos olet RxSwift-muuntaja, jatka vierailua RxSwiftCommunity-arkisto säännöllisesti: löydät aina jotain uutta!
Jos etsit lisää käyttöliittymäneuvoja, kokeile lukea Kuinka toteuttaa Pixel-täydellinen iOS-käyttöliittymäsuunnittelu kirjoittanut ApeeScapeer Roman Stetsenko.
Lähdetiedot (Perustietojen ymmärtäminen)
'Swift on yleiskäyttöinen, moniparadigmainen, käännetty ohjelmointikieli, jonka Apple Inc. on kehittänyt iOS-, macOS-, watchOS-, tvOS-, Linux- ja z / OS-käyttöjärjestelmille. Swift on suunniteltu toimimaan Applen Cocoa- ja Cocoa Touch -kehysten ja Applen tuotteille kirjoitettujen olemassa olevien Objective-C-koodien kanssa. ''
'UIView-luokka määrittää suorakulmaisen alueen ruudulla, jolla se näyttää sisältöä. Se hoitaa minkä tahansa alueen sisällön renderoinnin ja myös kaikki vuorovaikutukset kyseisen sisällön kanssa - kosketukset ja eleet. '
Reaktiivinen ohjelmointi on asynkroninen ohjelmointiparadigma, joka koskee asynkronisia datavirtoja ja muutoksen etenemistä.
RxSwift on reaktiivinen ohjelmointikirjasto iOS: lle. Sen avulla on helppo ohjelmoida dynaamisia sovelluksia, jotka vastaavat tietojen muutoksiin ja käyttäjän tapahtumiin.
Havainnoitavat kohteet tarjoavat tukea viestien lähettämiseen julkaisijoiden ja tilaajien välillä sovelluksessasi. Havaittavat muuntavat datavirrat tapahtumasarjaksi, joka toimitetaan yhdelle tai useammalle tilaajalle asynkronisesti.