RubyMotion er et fantastisk rammeverk for å bygge performante iOS-applikasjoner ved hjelp av Ruby-språket. I første del denne opplæringen lærte du hvordan du konfigurerer og implementerer et RubyMotion-program. Du jobbet med Interface Builder for å lage applikasjonens brukergrensesnitt, implementert en visningskontroller og lærte å skrive tester for søknaden din.
I denne opplæringen lærer du om modell-visning-kontrolleren eller MVC-mønsteret, og hvordan du kan bruke den til å strukturere applikasjonen din. Du vil også implementere et malerivisning og legge til en gjenkjenning som lar brukeren tegne på skjermen. Når du er ferdig, har du et komplett, fullt fungerende program.
Apple oppfordrer iOS-utviklere til å bruke modell-View-Controller designmønsteret til sine applikasjoner. Dette mønsteret bryter klasser inn i en av tre kategorier, modeller, visninger og kontroller.
Hvordan gjelder MVC for søknaden din? Du har allerede begynt å implementere PaintingController
klassen, som vil koble modeller og synspunkter sammen. For modelllaget legger du til to klasser:
Stroke
Denne klassen representerer et enkelt slag i maleriet.Maleri
Denne klassen representerer hele maleriet og inneholder ett eller flere slag.For visningslaget lager du en PaintingView
klasse som er ansvarlig for å vise en Maleri
protestere mot brukeren. Du vil også legge til en StrokeGestureRecongizer
som fanger berøringsinngang fra brukeren.
La oss begynne med Stroke
modell. Et slag vil bestå av en farge og flere punkter som representerer streken. For å starte, opprett en fil for Stroke
klasse, app / modeller / stroke.rb, og en annen for sin spesifikasjon, spec / modeller / stroke.rb.
Deretter implementere slagsklasseskeletet og en konstruktør.
klasse strekk attr_reader: poeng,: farge ende
De Stroke
klassen har to attributter, punkter
, en samling poeng, og farge
, fargen på Stroke
gjenstand. Deretter implementere en konstruktør.
klasse Stroke attr_reader: poeng,: farge def initialisere (start_point, farge) @points = [start_point] @color = farge ende ende
Det ser så bra ut så langt. Konstruktøren godtar to argumenter, startpunkt
og farge
. Det setter punkter
til en rekke punkter som inneholder startpunkt
og farge
til den angitte fargen.
Når en bruker sveiper fingeren over skjermen, trenger du en måte å legge til poeng på Stroke
gjenstand. Legg til add_point
metode til Stroke
.
def add_point (poeng) poeng << point end
Det var lett. For å legge til rette, legg til en ekstra metode til Stroke
klasse som returnerer startpunktet.
def start_point points.first end
Selvfølgelig er ingen modell komplett uten et sett med spesifikasjoner for å følge med.
Beskriv Stroke gjør før du gjør @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) @ stroke.add_point (@end_point) slutten beskriver "#initialize" gjør før @stroke = Stroke.new (@start_point, @color) avslutte det " farge "do @ stroke.color.should == @color slutten beskriver" #start_point "gjør det" returnerer slagets startpunkt "gjør @ stroke.start_point.should == @start_point slutten end beskriver" #add_point "gjør det" legger poengene til strekningen "do @ stroke.points.should == [@start_point, @middle_point, @end_point] sluttenden beskriver" #start_point "gjør det" returnerer startpunktet "do @ stroke.start_point.should == @start_point slutten slutten
Dette bør begynne å bli kjent. Du har lagt til fire beskrive blokker som tester initial
, startpunkt
, add_point
, og startpunkt
metoder. Det er også en før
blokkere som setter noen instansvariabler for spesifikasjonene. Legg merke til beskrive
blokkere for #initialize
har en før
blokkere som tilbakestiller @stroke
gjenstand. Det er greit. Med spesifikasjoner trenger du ikke å være så opptatt av ytelse som du gjør med en vanlig applikasjon.
Det er øyeblikk av sannhet, det er på tide å få søknaden din tegnet noe. Start med å opprette en fil for PaintingView
klasse på app / visninger / painting_view.rb. Fordi vi gjør noen spesialiserte tegninger, PaintingView
klassen er vanskelig å teste. For korthetens skyld skal jeg hoppe over spesifikasjonene for nå.
Deretter implementerer du PaintingView
klasse.
klasse 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, det er mye kode. La oss slå det ned stykke for bit. De PaintingView
klassen strekker seg ut UIView
klasse. Dette tillater PaintingView
å bli lagt til som et undervisning av PaintingController
s syn. De PaintingView
klassen har en attributt, hjerneslag
, som er en forekomst av Stroke
modell klasse.
Når det gjelder MVC-mønsteret, er det akseptabelt for en visning å vite om en modell når du arbeider med iOS SDK, men det er ikke greit for en modell å vite om en visning.
I PaintingView
klasse, vi har overstyrt UIView
's drawRect:
metode. Denne metoden lar deg implementere egendefinert tegningskode. Den første linjen i denne metoden, super
, kaller metoden på superklassen, UIView
i dette eksemplet, med de angitte argumentene.
I drawRect:
, Vi sjekker også at hjerneslag
Attributtet er ikke nil
. Dette forhindrer feil hvis hjerneslag
har ikke blitt satt ennå. Vi henter nå den nåværende tegningskonteksten ved å påkalle UIGraphicsGetCurrentContext
, konfigurer slagtaket som vi skal tegne, flytt tegningskonteksten til startpunkt
av streken, og legger til linjer for hvert punkt i hjerneslag
gjenstand. Til slutt påberoper vi oss CGContextStrokePath
å stryke banen, tegne den i visningen.
Legg til et uttak til PaintingController
for maleriet.
uttak: painting_view
Brann opp grensesnittbyggeren ved å kjøre bunt exec rake ib: åpen
og legg til en UIView
protestere mot PaintingController
utsikt fra Ojbect Library til høyre. Sett visningen klassen til PaintingView
i Identitetsinspektør. Pass på at malingsvisningen er plassert under knappene du la til tidligere. Du kan justere rekkefølgen til undervisningen ved å endre posisjonene til visningen i visningshierarkiet til venstre.
Kontroller og dra fra visningsregulatoren til PaintingView
og velg painting_view
uttak fra menyen som vises.
Velg målvisning og sett bakgrunnsfargen til 250
rød, 250
grønn og 250
blå.
Ikke glem å legge til en spesifikasjon til spec / kontrollere / painting_controller_spec.rb for painting_view
stikkontakt.
beskrive "#painting_view" gjør det "er koblet til i storyboardet" do controller.painting_view.should.not.be.nil end-end
For å sikre at tegningskoden fungerer riktig, legg til følgende kodestykke til PaintingController
klasse og kjør din søknad. Du kan slette denne kodestykket når du har bekreftet alt som fungerer som forventet.
def viewDidLoad 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)) painting_view.stroke = stroke painting_view.setNeedsDisplay ende
Nå som du kan tegne et slag, er det på tide å komme opp til hele maleriet. La oss begynne med Maleri
modell. Lag en fil for klassen på app / modeller / painting.rb og implementere Maleri
klasse.
klassemaleri attr_accessor: strekk def initialiser @strokes = [] ende def start_stroke (punkt, farge) slag << Stroke.new(point, color) end def continue_stroke(point) current_stroke.add_point(point) end def current_stroke strokes.last end end
De Maleri
modellen ligner på Stroke
klasse. Konstruktøren initialiserer slag
til en tom rekkefølge. Når en person berører skjermen, starter programmet et nytt slag ved å ringe start_stroke
. Da, som brukeren drar fingeren, vil den legge til punkter med continue_stroke
. Ikke glem spesifikasjonene for Maleri
klasse.
beskriv Painting gjør før du gjør @ point1 = CGPoint.new (10, 60) @ point2 = CGPoint.new (20, 50) @ point3 = CGPoint.new (30, 40) @ point4 = CGPoint.new (40, 30) @ point5 = CGPoint.new (50, 20) @ point6 = CGPoint.new (60, 10) @painting = Painting.new slutten beskriver "#initialize" gjør før @painting = Painting.new avslutte det "setter streken til en empty array "do @ painting.strokes.should == [] slutten beskriver" #start_stroke "gjøre før du avslutter @ painting.start_stroke (@ point1, UIColor.redColor) @ painting.start_stroke (@ point2, UIColor.blueColor) "starter nye slag" gjør @ 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 slutten end beskriver "#continue_stroke" gjør før du gjør @ painting.start_stroke (@ point1 , UIColor.redColor) @ painting.continue_stroke (@ point2) @ painting.start_stroke (@ point3, UIColor.blueColor) @ painting.con tinue_stroke (@ point4) avslutte det "legger til poeng til gjeldende slag" gjør @ painting.strokes [0] .points.should == [@ point1, @ point2] @ painting.strokes [1] .points.should == [ @ point3, @ point4] slutten slutten
Deretter endrer du PaintingView
klasse for å tegne en Maleri
objekt i stedet for a Stroke
gjenstand.
klasse 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
Du har endret hjerneslag
tilskrive maleri
. De drawRect:
Metoden nå iterates over alle slag i maleriet og trekker hver enkelt bruker draw_stroke
, som inneholder tegningskoden du skrev tidligere.
Du må også oppdatere visningskontrolleren for å inneholde en Maleri
modell. På toppen av PaintingController
klasse, legg til attr_reader: maleri
. Som navnet tilsier, er det viewDidLoad
metode av UIViewController
klasse-superklassen av PaintingController
klasse-kalles når visningsregulatoren har fullført visningen. De viewDidLoad
Metoden er derfor et godt sted å skape en Maleri
eksempel og sett maleri
attributten til PaintingView
gjenstand.
def viewDidLoad @painting = Painting.new painting_view.painting = malingen slutten
Som alltid, ikke glem å legge til tester for viewDidLoad
til spec / kontrollere / painting_controller_spec.rb.
beskrive "#viewDidLoad" gjør det "setter maleriet" gjør controller.painting.should.be.instance_of Painting end it "setter malerattributtet til malerisvisningen" gjør controller.painting_view.painting.should == controller.painting end-end
Din søknad vil bli ganske kjedelig med mindre du tillater folk å tegne på skjermen med fingrene. La oss legge til den aktuelle funksjonen nå. Lag en fil for StrokeGestureRecognizer
klassen sammen med sin spesifikasjon ved å kjøre følgende kommandoer fra kommandolinjen.
berør app / visninger / stroke_gesture_recognizer.rb touch spec / visninger / stroke_gesture_recognizer_spec.rb
Deretter lager skeletet for klassen.
klasse StrokeGestureRecognizer < UIGestureRecognizer attr_reader :position end
De StrokeGestureRecognizer
klassen strekker seg ut UIGestureRecognizer
klassen, som håndterer berøringsinngang. Den har en stilling
tilskrive at PaintingController
klassen vil bruke til å bestemme posisjonen til brukerens finger.
Det er fire metoder du må implementere i StrokeGestureRecognizer
klasse, touchesBegan: withEvent:
, touchesMoved: withEvent:
, touchesEnded: withEvent:
, og touchesCancelled: withEvent:
. De touchesBegan: withEvent:
Metoden kalles når brukeren begynner å berøre skjermen med fingeren. De touchesMoved: withEvent:
Metoden kalles gjentatte ganger når brukeren beveger fingeren og touchesEnded: withEvent:
Metoden påberopes når brukeren løfter fingeren fra skjermen. Endelig, den touchesCancelled: withEvent:
Metoden påberopes dersom bevegelsen avbrytes av brukeren.
Din gjenkjenning må gjøre to ting for hver hendelse, oppdatere stilling
Tilordne og endre stat
eiendom.
klasse 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
Begge touchesEnded: withEvent:
og touchesCancelled: withEvent:
metoder setter staten til UIGestureRecognizerStateEnded
. Dette skyldes at det ikke er viktig om brukeren avbrytes, tegningen bør forbli uberørt.
For å teste StrokeGestureRecognizer
klasse, må du kunne lage en forekomst av UITouch
. Dessverre er det ingen offentlig tilgjengelig API for å oppnå dette. For å få det til å fungere, bruker vi biblioteket Facon mocking.
Legg til perle 'bevegelse-facon'
til Gemfile og løp bunt installasjon
. Legg så til krever "bevegelse-form"
under krever "sukkerrør-farge"
i prosjektets Rakefile.
Deretter implementerer du StrokeGestureRecognizer
spec.
Beskriv StrokeGestureRecognizer utvide Facon :: SpecHelpers before do @stroke_gesture_recognizer = StrokeGestureRecognizer.new @ touch1 = mock (UITouch,: "locationInView:" => CGPoint.new (100, 200)) @ touch2 = mock (UITouch,: "locationInView: "=> CGPoint.new (300, 400)) @ touches1 = NSSet.setWithArray [@ touch1] @ touches2 = NSSet.setWithArray [@ touch2] slutten beskriver" #touchesBegan: withEvent: "gjør før du gjør @ stroke_gesture_recognizer.touchesBegan (@ touches1, withEvent: null) avslutte det "setter posisjonen til bevegelsens posisjon" gjør @ stroke_gesture_recognizer.position.should == CGPoint.new (100, 200) avslutte det "angir tilstanden til gestrekognisatoren" gjør @ stroke_gesture_recognizer.state .should == UIGestureRecognizerStateBegan endendat beskriver "#touchesMoved: withEvent:" gjør før @ stroke_gesture_recognizer.touchesBegan (@ touches1, withEvent: null) @ stroke_gesture_recognizer.touchesMoved (@ touches2, withEvent: null) avslutte det "setter posisjonen til gestus posisjon "gjør @ stroke_gesture_recognizer.pos ition.should == CGPoint.new (300, 400) avslutte det "setter tilstanden til gestrekognisatoren" gjør @ stroke_gesture_recognizer.state.should == UIGestureRecognizerStateChanged slutten slutten beskriver "#touchesEnded: withEvent:" gjør før gjør @stroke_gesture_recognizer. berørerBegan (@ touches1, withEvent: null) @ stroke_gesture_recognizer.touchesEnded (@ touches2, withEvent: null) avslutter den "setter posisjonen til bevegelsens posisjon" gjør @ stroke_gesture_recognizer.position.should == CGPoint.new (300, 400) det "angir tilstanden til gestrekognisatoren" gjør @ stroke_gesture_recognizer.state.should == UIGestureRecognizerStateEnded slutten beskriver "#touchesCancelled: withEvent:" gjør før du gjør @ stroke_gesture_recognizer.touchesBegan (@ touches1, withEvent: null) @ stroke_gesture_recognizer.touchesCancelled @ touches2, withEvent: nil) avslutte det "setter posisjonen til bevegelsens posisjon" gjør @ stroke_gesture_recognizer.position.should == CGPoint.new (300, 400) avslutter det "angir tilstanden til gestrekognisatoren" gjør @stroke_gesture_r ecognizer.state.should == UIGestureRecognizerStateEnded slutten slutten
utvide Facon :: SpecHelpers
gjør flere metoder tilgjengelige i spesifikasjonene dine, inkludert håne
. håne
er en enkel måte å lage testobjekter på som fungerer akkurat slik du vil. I før
blokkere i begynnelsen av spesifikasjonene, du er spottende forekomster av UITouch
med locationInView:
metode som returnerer et forhåndsdefinert punkt.
Deretter legger du til en stroke_gesture_changed
metode til PaintingController
klasse. Denne metoden vil motta en forekomst av StrokeGestureRecognizer
klasse når gesten er oppdatert.
def stroke_gesture_changed (stroke_gesture_recognizer) hvis stroke_gesture_recognizer.state == UIGestureRecognizerStateBegan painting.start_stroke (stroke_gesture_recognizer.position, selected_color) ellers painting.continue_stroke (stroke_gesture_recognizer.position) ende painting_view.setNeedsDisplay end
Når gjenkjenningens tilstand er UIGestureRecognizerStateBegan
, denne metoden starter et nytt slag i Maleri
objekt ved hjelp av StrokeGestureRecognizer
posisjon og selected_color
. Ellers fortsetter den nåværende slag.
Legg til spesifikasjonene for denne metoden.
beskriv "#stroke_gesture_changed" gjør før dra (controller.painting_view,: points => [CGPoint.new (100, 100), CGPoint.new (150, 150), CGPoint.new (200, 200)]) avslutte det " legger punktene til strekningen "gjør controller.painting.strokes.first.points [0] .should == CGPoint.new (100, 100) controller.painting.strokes.first.points [1] .should == CGPoint. nye (150, 150) controller.painting.strokes.first.points [2] .should == CGPoint.new (200, 200) avslutter det "setter slagets farge til den valgte farge" gjør controller.painting.strokes.first .color.should == controller.selected_color slutten
RubyMotion gir flere hjelpemetoder for å simulere brukerinteraksjon, inkludert dra
. Ved hjelp av dra
, Du kan simulere en brukers interaksjon med skjermen. De punkter
alternativet lar deg gi en rekke poeng for dra.
Hvis du skulle kjøre spesifikasjonene nå, ville de mislykkes. Det er fordi du må legge til gjenkjenningspersonen i storyboardet. Start grensesnittbyggeren ved å kjøre bunt exec rake ib: åpen
. Fra Objektbibliotek, dra en Gjenstand inn i scenen din, og endre klassen til StrokeGestureRecognizer
i Identitetsinspektør til høyre.
Kontroller og dra fra StrokeGestureRecognizer
protestere mot PaintingController
og velg select_color
metode fra menyen som vises. Dette vil sikre select_color
Metoden blir kalt når gjenkjenningsmannen utløses. Deretter kontrollerer du og drar fra PaintingView
protestere mot StrokeGestureRecognizer
objekt og velg gestureRecognizer
fra menyen som vises.
Legg til en spesifikasjon for gestgjenkjenningen til PaintingController
spesifikasjoner i #painting_view
beskrive
blokkere.
beskriv "#painting_view" gjør det "er koblet til i storyboardet" do controller.painting_view.should.not.be.nil end det "har en strekkbevisgjenkjenning" gjør controller.painting_view.gestureRecognizers.length.should == 1 kontrolleren. painting_view.gestureRecognizers [0] .should.be.instance_of StrokeGestureRecognizer slutten
Det er det. Med disse endringene skal søknaden din nå tillate en person å tegne på skjermen. Kjør din søknad og ha det gøy.
Det er noen få siste detaljer å legge til før søknaden din er ferdig. Fordi søknaden din er nedsenkende, er statuslinjen litt forstyrrende. Du kan fjerne den ved å sette inn UIStatusBarHidden
og UIViewControllerBasedStatusBarAppearance
verdier i programmets Info.plist. Dette er lett å gjøre i RubyMotion oppsett
blokkere inne i prosjektets Rakefile.
Bevegelse :: Prosjekt :: App.setup gjør | app | app.name = 'Paint' app.info_plist ['UIStatusBarHidden'] = sann app.info_plist ['UIViewControllerBasedStatusBarAppearance'] = falsk slutt
Applikasjonens ikoner og startbilder er inkludert i kildefilene i denne opplæringen. Last ned bildene og kopier dem til ressurser katalog over prosjektet. Sett deretter programikonet i Rakefile-konfigurasjonen. Det kan hende du må rense bygningen ved å løpe Bundle Exec Rask Ren: Alt
for å se det nye lanseringsbildet.
Bevegelse :: Prosjekt :: App.setup gjør | app | app.name = 'Paint' app.info_plist ['UIStatusBarHidden'] = sann app.info_plist ['UIViewControllerBasedStatusBarAppearance'] = feil app.icons = ["icon.png"] slutten
Det er det. Du har nå en komplett app som er klar for en million nedlastinger i App Store. Du kan se og laste ned kilden til dette programmet fra GitHub.
Selv om appen er ferdig, er det så mye mer du kan legge til. Du kan legge til kurver mellom linjene, flere farger, forskjellige linjebredder, lagre, angre og gjenta, og alt annet du kan forestille deg. Hva vil du gjøre for å gjøre appen din bedre? Gi meg beskjed i kommentarene nedenfor.