Să scriem o aplicație RubyMotion Partea 2

Ce veți crea

RubyMotion este un cadru fantastic pentru construirea de aplicații performante iOS folosind limba Ruby. În prima parte a acestui tutorial, ați învățat cum să configurați și să implementați o aplicație RubyMotion. Ați lucrat cu Interface Builder pentru a crea interfața de utilizare a aplicației, a implementat un controler de vizualizare și a învățat cum să scrieți teste pentru aplicația dvs..

În acest tutorial, veți afla despre Model-View-Controller sau modelul de design MVC și cum îl puteți folosi pentru a structura aplicația. De asemenea, veți implementa o vizualizare de pictură și veți adăuga un instrument de recunoaștere a gesturilor care permite utilizatorului să deseneze pe ecran. Când ați terminat, veți avea o aplicație completă și complet funcțională.

1. Model-View-Controller

Apple încurajează dezvoltatorii iOS să aplice modelul de design-View-Controller pentru aplicațiile lor. Acest model împarte clasele într-una din cele trei categorii, modele, vederi și controlori.

  • Modelele conțin logica de afaceri a aplicației dvs., codul care determină regulile pentru gestionarea și interacțiunea cu datele. Modelul tău este locul unde locuiește logica de bază pentru aplicația ta.
  • Afișările afișează informații utilizatorului și le permit să interacționeze cu aplicația.
  • Controlorii sunt responsabili pentru legarea împreună a modelelor și a vizualizărilor. SDK-ul iOS utilizează controale de vizualizare, controlori specializați, cu o mai mică cunoaștere a opiniilor decât alte cadre MVC.

Cum se aplică MVC aplicației dvs.? Ați început deja implementarea PaintingController clasă, care vă va conecta împreună modelele și vizionările. Pentru stratul de model, veți adăuga două clase:

  • Accident vascular cerebral Această clasă reprezintă un singur accident vascular cerebral în pictura.
  • pictură Această clasă reprezintă întreaga pictură și conține una sau mai multe lovituri.

Pentru stratul de vizualizare, veți crea un PaintingView clasa care este responsabilă pentru afișarea a pictură obiect pentru utilizator. De asemenea, veți adăuga a StrokeGestureRecongizer care captează intrarea touch de la utilizator.

2. Strokes

Să începem cu Accident vascular cerebral model. Un accident vascular cerebral va consta dintr-o culoare și câteva puncte reprezentând un accident vascular cerebral. Pentru a începe, creați un fișier pentru Accident vascular cerebral clasă, app / modele / stroke.rb, și altul pentru spec, spec / modele / stroke.rb.

Apoi, implementați scheletul clasei de accident vascular cerebral și un constructor.

clasa Accident vascular cerebral attr_reader: points,: color end

Accident vascular cerebral clasa are două atribute, puncte, o colecție de puncte și culoare, culoarea Accident vascular cerebral obiect. Apoi, implementați un constructor.

cursă cursă attr_reader: puncte,: color def initialize (start_point, color) @points = [start_point] @color = end end color

Asta arată foarte bine până acum. Constructorul acceptă două argumente, punctul de start și culoare. Se stabilește puncte la o serie de puncte care conțin punctul de start și culoare la culoarea furnizată.

Atunci când un utilizator scutură degetul pe ecran, aveți nevoie de o modalitate de a adăuga puncte la Accident vascular cerebral obiect. Adaugă add_point metoda de a Accident vascular cerebral.

def point ad add_point (punct) << point end

A fost ușor. Pentru confort, adăugați o altă metodă la Accident vascular cerebral clasă care returnează punctul de pornire.

def start_point points.first end

Desigur, nici un model nu este complet fără un set de specificații pentru a merge împreună cu el.

descrieți Stroke face înainte de a face @start_point = CGPoint.new (0.0, 50.0) @middle_point = CGPoint.new (50.0, 100.0) @end_point = CGPoint.new (100.0, 0.0) @color = UIColor.blueColor @stroke = Stroke.new (@start_point, @color) @ stroke.add_point (@middle_point) @ sfârșitul stroke.add_point (@end_point) descriu "#initialize" înainte de a face @stroke = Stroke.new (@start_point, @color) color "nu @ stroke.color.should == @ capătul final al capului descrie" #start_point "face" returnează punctul de plecare al cursei "nu @ stroke.start_point.should == sfârșitul capătului @start_point descrie" #add_point "face" adaugă punctele la cursa "do @ stroke.points.should == [@start_point, @middle_point, @end_point] sfârșitul final descrie" #start_point "face" întoarce punctul de start "nu @ stroke.start_point.should == @start_point sfârșitul capătului final

Acest lucru ar trebui să înceapă să se simtă familiar. Ați adăugat patru blocuri de descriere care testează inițializa, punctul de start, add_point, și punctul de start metode. Există și a inainte de bloc care stabilește câteva variabile de instanță pentru specificații. Observați descrie bloc pentru #initialize are o inainte de bloc care resetează @accident vascular cerebral obiect. E in regula. Cu specificații, nu trebuie să fii atât de preocupat de performanță ca și de o aplicație obișnuită.

3. Desenarea

Este momentul adevărului, este momentul să faceți ca cererea să atragă ceva. Începeți prin crearea unui fișier pentru PaintingView clasa la app / views / painting_view.rb. Pentru că facem niște desene specializate PaintingView clasa este dificil de testat. Din motive de coerență, voi trece peste specificațiile de acum.

Apoi, implementați PaintingView clasă.

clasa PaintingView < UIView attr_accessor :stroke def drawRect(rectangle) super # ensure the stroke is provided return if stroke.nil? # set up the drawing context context = UIGraphicsGetCurrentContext() CGContextSetStrokeColorWithColor(context, stroke.color.CGColor) CGContextSetLineWidth(context, 20.0) CGContextSetLineCap(context, KCGLineCapRound) CGContextSetLineJoin(context, KCGLineJoinRound) # move the line to the start point CGContextMoveToPoint(context, stroke.start_point.x, stroke.start_point.y) # add each line in the path stroke.points.drop(1).each do |point| CGContextAddLineToPoint(context, point.x, point.y) end # stroke the path CGContextStrokePath(context); end end

Phew, e un cod foarte bun. Să-l rupem în bucăți. PaintingView clasa extinde UIView clasă. Asta permite PaintingView care urmează să fie adăugat sub formă de subiectiv PaintingControllerPaintingView clasa are un atribut, accident vascular cerebral, care este un exemplu al Accident vascular cerebral clasa de modele.

În ceea ce privește modelul MVC, atunci când lucrăm cu SDK-ul iOS, este acceptabil pentru o privire de a ști despre un model, dar nu este bine ca un model să știe despre o vizualizare.

În PaintingView clasa, am rămas în contradicție UIView„s drawRect: metodă. Această metodă vă permite să implementați codul de desen personalizat. Prima linie a acestei metode, super, sună metoda în clasa super, UIView în acest exemplu, cu argumentele furnizate.

În drawRect:, de asemenea, verificăm dacă accident vascular cerebral atributul nu este zero. Acest lucru previne erorile dacă accident vascular cerebral nu a fost încă stabilită. Apoi, preluăm contextul curent de desen invocând UIGraphicsGetCurrentContext, configurați cursa pe care urmează să o desenați, mutați contextul de desen la punctul de start a cursei și adaugă linii pentru fiecare punct din accident vascular cerebral obiect. În cele din urmă, invocăm CGContextStrokePath pentru a cursa calea, tragând-o în vedere.

Adăugați o priză la PaintingController pentru vizualizarea picturii.

ieșire: painting_view

Activați interfața Builder prin a alerga pachetul exec rake ib: deschis și adăugați a UIView obiecte față de PaintingControllerdin punctul de vedere al Biblioteca Ojbect pe dreapta. Setați clasa de vizualizare la PaintingView în Inspectorul de identitate. Asigurați-vă că vizualizarea de pictură este poziționată sub butoanele pe care le-ați adăugat mai devreme. Puteți ajusta ordonarea subreviselor schimbând pozițiile vizualizărilor din ierarhia de vizualizare din stânga.

Controlați și trageți de la controlerul de vizualizare la PaintingView și selectați painting_view ieșiți din meniul care apare.

Selectați vizualizarea de pictură și setați culoarea de fundal la 250 roșu, 250 verde și 250 albastru.

Nu uitați să adăugați o specificație spec / controlere / painting_controller_spec.rb pentru painting_view priză.

descrieți "#painting_view" face "este conectat în scenariul de scenariu" do end end controller.painting_view.should.not.be.nil

Pentru a vă asigura că codul de desen funcționează corect, adăugați următorul fragment de cod la PaintingController clasați și executați aplicația. Puteți șterge acest fragment de cod după ce ați confirmat că totul funcționează conform așteptărilor.

def vizualDidLoad stroke = Stroke.new (CGPoint.new (80, 100), '# ac5160'.uicolor) stroke.add_point (CGPoint.new (240, 100)) stroke.add_point (CGPoint.new (240, 428)) stroke.add_point (CGPoint.new (80, 428)) stroke.add_point (CGPoint.new (80, 100)) pictura_view.stroke = accident vascular cerebral painting_view.setNeedsDisplay end

4. Pictura

Acum, când puteți trage un accident vascular cerebral, este timpul să vă ridicați până la întreaga pictură. Să începem cu pictură model. Creați un fișier pentru clasă la app / modele / painting.rb și să pună în aplicare pictură clasă.

clasa Pictura attr_accessor: cursoare def initialize @strokes = [] sfarsit def start_stroke (punct, culoare) curse << Stroke.new(point, color) end def continue_stroke(point) current_stroke.add_point(point) end def current_stroke strokes.last end end

pictură modelul este similar cu modelul Accident vascular cerebral clasă. Constructorul inițializează accidente vasculare cerebrale la o matrice goală. Atunci când o persoană atinge ecranul, aplicația va începe un nou accident prin apelare start_stroke. Apoi, în timp ce utilizatorul își trage cu degetul, va adăuga puncte cu continue_stroke. Nu uitați specificațiile pentru pictură clasă.

descrie Pictura înainte de a face @ point1 = CGPoint.new (10, 60) @ point2 = CGPoint.new (20, 50) @ point3 = CGPoint.new (30, 40) @ point4 = point5 = CGPoint.new (50, 20) @ point6 = CGPoint.new (60, 10) @painting = Painting.new capăt descrie "#initialize" înainte de a face @painting = Painting.new end " array goală "do @ painting.strokes.should == [] sfârșitul final descrie" #start_stroke "face înainte de a face @ pictura.start_stroke (@ point1, UIColor.redColor) @ painting.start_stroke (@ point2, UIColor.blueColor) "incepe noi lovituri" nu @ painting.strokes.length.should == 2 @ painting.strokes [0] .points.should == [@ point1] @ painting.strokes [0] .color.should == UIColor.redColor @ painting.strokes [1] .points.should == [@ point2] @ painting.strokes [1] .color.should == UIColor.blueColor capătul final descrie "#continue_stroke" înainte de a face @ painting.start_stroke (@ point1 , UIColor.redColor) @ painting.continue_stroke (@ point2) @ painting.start_stroke (@ point3, UIColor.blueColor) @ painting.con tinue_stroke (@ point4) sfarseste-l "adauga puncte la loviturile curente" face @ painting.strokes [0] .points.should == [point1, point2] @ painting.strokes [1] .points.should == [ @ point3, @ point4] capăt sfârșitul final

Apoi, modificați PaintingView clasa pentru a desena a pictură obiect în loc de a Accident vascular cerebral obiect.

clasa PaintingView < UIView attr_accessor :painting def drawRect(rectangle) super # ensure the painting is provided return if painting.nil? painting.strokes.each do |stroke| draw_stroke(stroke) end end def draw_stroke(stroke) # set up the drawing context context = UIGraphicsGetCurrentContext() CGContextSetStrokeColorWithColor(context, stroke.color.CGColor) CGContextSetLineWidth(context, 20.0) CGContextSetLineCap(context, KCGLineCapRound) CGContextSetLineJoin(context, KCGLineJoinRound) # move the line to the start point CGContextMoveToPoint(context, stroke.start_point.x, stroke.start_point.y) # add each line in the path stroke.points.drop(1).each do |point| CGContextAddLineToPoint(context, point.x, point.y) end # stroke the path CGContextStrokePath(context); end end

Ai schimbat-o accident vascular cerebral atribuit lui pictură. drawRect: metoda se repetă peste toate loviturile din tablou și atrage fiecare folosind draw_stroke, care conține codul de desen pe care l-ați scris anterior.

De asemenea, trebuie să actualizați controlerul de vizualizare pentru a conține a pictură model. În partea de sus a PaintingController clasă, adăugați attr_reader: pictura. După cum sugerează și numele, viewDidLoad metodă a UIViewController clasa - superclajul PaintingController class-este apelat atunci când controlerul de vizualizare a terminat încărcarea vizualizare. viewDidLoad metoda este, prin urmare, un loc bun pentru a crea o pictură exemplu și setați pictură atributul PaintingView obiect.

def viewDidLoad @painting = Painting.new painting_view.painting = sfârșitul picturii

Ca întotdeauna, nu uitați să adăugați teste pentru viewDidLoad la spec / controlere / painting_controller_spec.rb.

descrieți "#viewDidLoad" faceți "setarea vopselei" faceți controller.painting.should.be.instance_of Pictura finalizează "setează atributul de pictură al vizualizării de vopsire" do controller.painting_view.painting.should == controller.painting end end

5. Recunoașterea gesturilor

Cererea dvs. va fi destul de plictisitoare dacă nu permiteți oamenilor să deseneze pe ecran cu degetele. Să adăugăm acea bucată de funcționalitate acum. Creați un fișier pentru StrokeGestureRecognizer clasa împreună cu spec., executând următoarele comenzi din linia de comandă.

touch app / views / stroke_gesture_recognizer.rb atingeți spec / views / stroke_gesture_recognizer_spec.rb

Apoi, creați scheletul pentru clasă.

clasa StrokeGestureRecognizer < UIGestureRecognizer attr_reader :position end

StrokeGestureRecognizer clasa extinde UIGestureRecognizer clasa, care se ocupă de intrarea pe atingere. Are o poziţie atribuiți că PaintingController clasa va folosi pentru a determina poziția degetului utilizatorului.

Există patru metode pe care trebuie să le implementați în StrokeGestureRecognizer clasă, touchesBegan: withEvent:, touchesMoved: withEvent:, touchesEnded: withEvent:, și touchesCancelled: withEvent:.  touchesBegan: withEvent: este apelată când utilizatorul începe să atingă ecranul cu degetul. touchesMoved: withEvent: metoda se numește în mod repetat când utilizatorul își mișcă degetul și touchesEnded: withEvent: metoda este invocată atunci când utilizatorul ridică degetul de pe ecran. În cele din urmă, touchesCancelled: withEvent: metoda este invocată dacă gestul este anulat de către utilizator.

Recunoașterea gestului dvs. are nevoie de două lucruri pentru fiecare eveniment, actualizați-l poziţie atribuiți și modificați stat proprietate.

clasa StrokeGestureRecognizer < UIGestureRecognizer attr_accessor :position def touchesBegan(touches, withEvent: event) super @position = touches.anyObject.locationInView(self.view) self.state = UIGestureRecognizerStateBegan end def touchesMoved(touches, withEvent: event) super @position = touches.anyObject.locationInView(self.view) self.state = UIGestureRecognizerStateChanged end def touchesEnded(touches, withEvent: event) super @position = touches.anyObject.locationInView(self.view) self.state = UIGestureRecognizerStateEnded end def touchesCancelled(touches, withEvent: event) super @position = touches.anyObject.locationInView(self.view) self.state = UIGestureRecognizerStateEnded end end

Amandoua touchesEnded: withEvent: și touchesCancelled: withEvent: metodele stabilesc starea la UIGestureRecognizerStateEnded. Acest lucru se datorează faptului că nu contează dacă utilizatorul este întrerupt, desenul ar trebui să rămână neatins.

Pentru a testa StrokeGestureRecognizer class, trebuie să puteți crea o instanță de UITouch. Din păcate, nu există niciun API disponibil public pentru a realiza acest lucru. Pentru a face acest lucru, vom folosi biblioteca de batjocură facon.

Adăuga bijuterie "motion-facon" la Gemfile și să fugi instalare pachet. Apoi adauga necesită "mișcare-facon" de mai jos necesită "sugarcube-color" în fișierul Rake al proiectului.

Apoi, implementați StrokeGestureRecognizer spec.

descrie StrokeGestureRecognizer fac extins Facon :: SpecHelpers înainte de a face @stroke_gesture_recognizer = StrokeGestureRecognizer.new @ touch1 = mock (UITouch,: "locationInView:" => CGPoint.new (100, 200)) @ touch2 = "=> CGPoint.new (300, 400)) @ atinge1 = NSSet.setWithArray [@ touch1] @ touch22 = NSSet.setWithArray [@ touch2] atinge1, cuEvent: nil) sfarseste-l "stabileste pozitia in pozitia gestului" nu @ stroke_gesture_recognizer.position.should == CGPoint.new (100, 200) incheie "stabileste starea recunoasterii gestului" do @ stroke_gesture_recognizer.state .should == UIGestureRecognizerStateBegan sfârșitul final descrie "#touchesMoved: withEvent:" face înainte de a face @ stroke_gesture_recognizer.touchesBegan (@ atinge1, cuEvent: nil) @ stroke_gesture_recognizer.touchesMoved (@ atinge2, cuEvent: nil) poziția gestului "do @ stroke_gesture_recognizer.pos ition.should == CGPoint.new (300, 400) se termină "setează starea recunoașterii gestului" do @ stroke_gesture_recognizer.state.should == UIGestureRecognizerStateChanged sfârșitul final descrie "#touchesEnded: withEvent:" face înainte de a face @stroke_gesture_recognizer. atingeBegan (@ atinge1, cuEvent: nil) @ stroke_gesture_recognizer.touchesEnded (@ atinge2, cuEvent: nil) sfarseste-l "stabileste pozitia in pozitia gestului" do @ stroke_gesture_recognizer.position.should == CGPoint.new (300, 400) aceasta "stabilește starea recunoașterii gestului" nu face stroke_gesture_recognizer.state.should == UIGestureRecognizerStateEnded sfârșitul final descrie "#touchesCancelled: withEvent:" face înainte de a face @ stroke_gesture_recognizer.touchesBegan (@ touches1, withEvent: nil) @ stroke_gesture_recognizer.touchesCancelled @ atinge2, cuEvent: nil) terminați-l "stabilește poziția în poziția gestului" do @ stroke_gesture_recognizer.position.should == CGPoint.new (300, 400) final "stabilește starea recunoașterii gestului" do @stroke_gesture_r ecognizer.state.should == UIGestureRecognizerStateEnded end end end

extindă Facon :: SpecHelpers face mai multe metode disponibile în specificațiile dvs., inclusiv a-și bate joc. a-și bate joc este o modalitate simplă de a crea obiecte de testare care funcționează exact așa cum doriți. În inainte de blocați la începutul specificațiilor, sunteți de batjocură instanțe de UITouch cu locationInView: care returnează un punct predefinit.

Apoi, adăugați o stroke_gesture_changed metoda pentru a PaintingController clasă. Această metodă va primi o instanță a StrokeGestureRecognizer clasa ori de câte ori gestul este actualizat.

def stroke_gesture_recognizer (stroke_gesture_recognizer) dacă stroke_gesture_recognizer.state == UIGestureRecognizerStateBegan painting.start_stroke (stroke_gesture_recognizer.position, selected_color) altfel painting.continue_stroke (stroke_gesture_recognizer.position) end painting_view.setNeedsDisplay end

Când starea recunoașterii gestului este UIGestureRecognizerStateBegan, această metodă inițiază un nou accident în pictură obiect folosind StrokeGestureRecognizerși selected_color. În caz contrar, continuă cursa curentă.

Adăugați specificațiile pentru această metodă.

descrieți "#stroke_gesture_changed" înainte de a face drag (controler.painting_view,: points => CGPoint.new (100, 100), CGPoint.new (150, 150), CGPoint.new (200, 200) adaugă punctele la cursa "do controller.painting.strokes.first.points [0] .should == CGPoint.new (100, 100) controller.painting.strokes.first.points [1] .should == CGPoint. nou (150, 150) controller.painting.strokes.first.points [2] .should == CGPoint.new (200, 200) terminați "setarea culorii cursei la culoarea selectată" do controller.painting.strokes.first .color.should == controler.selected_color sfârșitul final

RubyMotion oferă mai multe metode de ajutor pentru a simula interacțiunea cu utilizatorul, inclusiv trage. Utilizarea trage, puteți simula interacțiunea unui utilizator cu ecranul. puncte vă permite să furnizați o serie de puncte pentru tragere.

Dacă ați rula specificațiile acum, ar eșua. Asta pentru că trebuie să adăugați recunoașterea gestului în tabloul de bord. Lansați Interface Builder prin rulare pachetul exec rake ib: deschis. De la Biblioteca de obiecte, trageți un Obiect în scena dvs. și schimbați clasa sa StrokeGestureRecognizer în Inspectorul de identitate pe dreapta.

Controlați și trageți din StrokeGestureRecognizer obiecte față de PaintingController și alegeți select_color din meniul care apare. Acest lucru va asigura select_color metoda se numește ori de câte ori se declanșează recunoașterea gestului. Apoi, controlați și trageți din PaintingView obiecte față de StrokeGestureRecognizer obiect și selectați gestureRecognizer din meniul care apare.

Adăugați o specificație pentru recunoașterea gestului la PaintingController specificații în #painting_view descrie bloc.

descrieți "#painting_view" faceți clic pe "face" este conectat în storyboard "do controller.painting_view.should.not.be.nil sfârșitul" are un recunoaștere a gestului accidental "do controller.painting_view.gestureRecognizers.length.should == 1 controler. painting_view.gestureRecognizers [0] .informa_instrumentelor StrokeGestureRecognizer end end

Asta e. Cu aceste modificări, aplicația ar trebui să permită acum unei persoane să deseneze pe ecran. Rulați aplicația și distrați-vă.

6. Sentimente finale

Există câteva atingeri finale pe care trebuie să le adăugați înainte de finalizarea solicitării. Deoarece aplicația dvs. este imersivă, bara de stare este puțin distragătoare. Puteți să o eliminați prin setarea UIStatusBarHidden și UIViewControllerBasedStatusBarAppearance valori în Info.plist aplicației. Acest lucru este ușor de făcut în RubyMotion înființat blocați în cadrul Rakefile al proiectului.

Propunere :: Proiect :: App.setup do | app app.name = 'Paint' app.info_plist ['UIStatusBarHidden'] = true app.info_plist ['UIViewControllerBasedStatusBarAppearance'] = sfarsit false

Pictogramele aplicației și imaginile de lansare sunt incluse în fișierele sursă ale acestui tutorial. Descărcați imaginile și copiați-le la resurse directorul proiectului. Apoi, setați pictograma aplicației în configurația Rakefile. Este posibil să trebuiască să curățați construirea rulând pachet exec rake curat: toate pentru a vedea noua imagine de lansare.

Propunere :: Proiect :: App.setup do | app app.name = 'Paint' app.info_plist ['UIStatusBarHidden'] = true app.info_plist ['UIViewControllerBasedStatusBarAppearance'] = sfarsit false app.icons = ["icon.png"] end

Concluzie

Asta e. Aveți acum o aplicație completă care este pregătită pentru un milion de descărcări în App Store. Puteți vizualiza și descărca sursa pentru această aplicație de la GitHub.

Chiar dacă aplicația dvs. este terminată, puteți adăuga mult mai mult la aceasta. Puteți adăuga curbe între linii, mai multe culori, lățimi diferite de linii, salvarea, anularea și refacerea și orice altceva ce vă puteți imagina. Ce veți face pentru a face aplicația mai bună? Anunță-mă în comentariile de mai jos.

Cod