La oss skrive en RubyMotion App Del 2

Hva du skal skape

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.

1. Model-View-Controller

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.

  • Modeller inneholder søknadens forretningslogikk, koden som bestemmer reglene for styring og interaksjon med data. Modellen din er hvor kjernen logikken for deg søknad lever.
  • Visninger viser informasjon til brukeren og tillater at de samhandler med programmet.
  • Kontrollører er ansvarlige for å binde modellene og visningene sammen. IOS SDK bruker visningsstyrere, spesialiserte kontrollører med litt mer kunnskap om visningene enn andre MVC-rammer.

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.

2. Strokes

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.

3. Tegning

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 PaintingControllers 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 PaintingControllerutsikt 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

4. Maleri

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

5. Gesture Recognizers

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 StrokeGestureRecognizerposisjon 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.

6. Endelige touches

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

Konklusjon

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.