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ă.
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.
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.
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ă.
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 PaintingController
. PaintingView
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 PaintingController
din 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
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
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ă.
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
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.