Låt oss skriva en RubyMotion App Del 2

Vad du ska skapa

RubyMotion är en fantastisk ram för att bygga prestanda iOS-applikationer med hjälp av Ruby-språket. I den första delen lärde du dig hur du installerar och implementerar en RubyMotion-applikation. Du arbetade med gränssnittsbyggare för att skapa applikationens användargränssnitt, implementerade en kontroller och lärde dig hur man skriver tester för din ansökan.

I den här handledningen lär du dig mer om modell-View-Controller eller MVC-designmönstret och hur du kan använda det för att strukturera din applikation. Du kommer också att implementera en målningsvy och lägga till en gestkännare som tillåter användaren att dra på skärmen. När du är klar har du en komplett, fullt fungerande applikation.

1. Model-View-Controller

Apple uppmuntrar iOS-utvecklare att tillämpa modell-View-Controller-designmönstret för sina applikationer. Detta mönster bryter klasserna i en av tre kategorier, modeller, visningar och kontroller.

  • Modeller innehåller din applikations affärslogik, koden som bestämmer reglerna för hantering och interaktion med data. Din modell är där kärnlogiken för din ansökan lever.
  • Visningar visar information till användaren och låter dem interagera med applikationen.
  • Controllers ansvarar för att koppla modeller och synpunkter tillsammans. IOS SDK använder visningsstyrare, specialiserade styrenheter med lite mer kunskap om synpunkterna än andra MVC-ramar.

Hur gäller MVC för din ansökan? Du har redan börjat implementera PaintingController klass, som kommer att ansluta dina modeller och synpunkter tillsammans. För modellskiktet lägger du till två klasser:

  • Stroke Denna klass representerar en enda stroke i målningen.
  • Målning Denna klass representerar hela målningen och innehåller en eller flera slag.

För visningsskiktet skapar du en PaintingView klass som ansvarar för att visa en Målning motsätta sig användaren. Du lägger också till en StrokeGestureRecongizer som fångar beröringsinmatning från användaren.

2. Strokes

Låt oss börja med Stroke modell. En stroke kommer att bestå av en färg och flera punkter som representerar stroke. För att starta, skapa en fil för Stroke klass, app / modeller / stroke.rb, och en annan för sin spec, spec / modeller / stroke.rb.

Därefter implementera stroke klassskelettet och en konstruktör.

klassstreck attr_reader: poäng,: färgänd

De Stroke klassen har två attribut, poäng, en samling poäng, och Färg, färgen på Stroke objekt. Sedan implementera en konstruktör.

klassstreck attr_reader: poäng,: färg def initiera (start_punkt, färg) @points = [start_point] @color = färgändänd

Det ser så bra ut hittills. Konstruktören accepterar två argument, startpunkt och Färg. Det sätter poäng till en rad punkter som innehåller startpunkt och Färg till den angivna färgen.

När en användare sveper fingret över skärmen behöver du ett sätt att lägga till poäng på Stroke objekt. Lägg till add_point metod till Stroke.

Def add_point (poäng) poäng << point end

Det var enkelt. För enkelhets skyld, lägg till ytterligare en metod till Stroke klass som returnerar startpunkten.

def start_point points.first slut

Naturligtvis är ingen modell komplett utan en uppsättning specs för att följa med den.

beskriv Stroke gör innan du gö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) avsluta beskriva "#initialize" gör innan @stroke = Stroke.new (@start_point, @color) avsluta det " color @ do @ stroke.color.should == @color slutänden beskriver "#start_point" gör det "returnerar stroke startpunkt" gör @ stroke.start_point.should == @start_point slutänden beskriver "#add_point" gör det " lägger till punkterna till stroke "do @ stroke.points.should == [@start_point, @middle_point, @end_point] slutänden beskriver" #start_point "gör det" returnerar startpunkten "do @ stroke.start_point.should == @start_point slutet slutet

Detta bör börja känna sig bekant. Du har lagt till fyra beskriva block som testar initialisera, startpunkt, add_point, och startpunkt metoder. Det finns också en innan block som ställer in några instansvariabler för specifikationerna. Lägg märke till beskriva blockera för #initialize har en innan block som återställer @stroke objekt. Det är okej. Med specifikationer behöver du inte vara så bekymrad över prestanda som du gör med en vanlig applikation.

3. Ritning

Det är dags sanningen, det är dags att göra din ansökan rita något. Börja med att skapa en fil för PaintingView klass på app / vyer / painting_view.rb. Eftersom vi gör en specialiserad ritning, PaintingView klassen är knepig att testa. För korthetens skull kommer jag att hoppa över specifikationerna för nu.

Sedan implementerar du PaintingView klass.

klass 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 är mycket koden. Låt oss bryta ner det stycket för bit. De PaintingView klassen sträcker sig UIView klass. Det här tillåter PaintingView att läggas till som en undervy av PaintingControllers åsikt. De PaintingView klassen har ett attribut, stroke, vilket är en förekomst av Stroke modellklass.

När det gäller MVC-mönstret är det acceptabelt för en bild att veta om en modell när den arbetar med iOS SDK, men det är inte okej för en modell att veta om en vy.

I PaintingView klass, vi har överträtt UIView's drawRect: metod. Med den här metoden kan du implementera anpassad ritningskod. Den första raden av denna metod, super, kallar metoden på superklassen, UIView i det här exemplet, med de angivna argumenten.

I drawRect:, Vi kontrollerar också att stroke Attributet är inte noll. Detta förhindrar fel om stroke har inte ställts in ännu. Vi hämtar nu det aktuella tecknadskontextet genom att åberopa UIGraphicsGetCurrentContext, konfigurera stroke som vi ska rita, flytta ritningssammanhang till startpunkt av stroke, och lägger till linjer för varje punkt i stroke objekt. Slutligen åberopar vi CGContextStrokePath att stroke banan och rita den i vyn.

Lägg till ett uttag till PaintingController för målningsvyn.

utlopp: painting_view

Brand upp gränssnittsbyggare genom att springa bunt exec rake ib: öppen och lägg till en UIView protestera mot PaintingControllersikt från Ojbect Library till höger. Ställ in bildens klass till PaintingView i Identitetsinspektör. Se till att målningen är placerad under knapparna som du lagt till tidigare. Du kan justera beställningen av undervisningarna genom att ändra positionerna för visningen i visningshierarkin till vänster.

Styr och dra från visningsregulatorn till PaintingView och välj painting_view uttag från menyn som visas.

Välj målningsvyn och ställ in dess bakgrundsfärg till 250 röd, 250 grön och 250 blå.

Glöm inte att lägga till en specifikation till spec / controllers / painting_controller_spec.rb för painting_view utlopp.

beskriv "#painting_view" gör det "är anslutet i storyboardet" do controller.painting_view.should.not.be.nil slutet slutet

För att säkerställa att din ritningskod fungerar korrekt lägger du till följande kodbit till PaintingController klass och kör din ansökan. Du kan ta bort denna kodbit när du har verifierat allt som fungerar som förväntat.

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 slutet

4. Måleri

Nu när du kan rita en stroke är det dags att nivån upp till hela målningen. Låt oss börja med Målning modell. Skapa en fil för klassen på app / modeller / painting.rb och implementera Målning klass.

klass Målning attr_accessor: streck def initiera @strokes = [] slutet def start_stroke (punkt, färg) slag << Stroke.new(point, color) end def continue_stroke(point) current_stroke.add_point(point) end def current_stroke strokes.last end end

De Målning modellen liknar Stroke klass. Konstruktorn initierar stroke till en tom array. När en person röra på skärmen startar programmet ett nytt slag genom att ringa start_stroke. Då, som användaren drar sitt finger, kommer det att lägga till punkter med continue_stroke. Glöm inte specifikationerna för Målning klass.

beskriva Målning gör innan du gö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 = Målning.ny ände beskriva "#initialize" gör innan @painting = Painting.new avsluta "sätter stroke till en empty array "gör @ painting.strokes.should == [] änden beskriver" #start_stroke "gör innan @ painting.start_stroke (@ point1, UIColor.redColor) @ painting.start_stroke (@ point2, UIColor.blueColor) avslutar det "startar nya slag" gö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 slutänden beskriver "#continue_stroke" gör innan @ painting.start_stroke (@ point1 , UIColor.redColor) @ painting.continue_stroke (@ point2) @ painting.start_stroke (@ point3, UIColor.blueColor) @ painting.con tinue_stroke (@ point4) avsluta "lägger till punkter till aktuella slag" gör @ painting.strokes [0] .points.should == [@ point1, @ point2] @ painting.strokes [1] .points.should == [ @ punkt3, @ punkt4] ändänden

Ändra sedan PaintingView klass för att rita en Målning objekt istället för a Stroke objekt.

klass 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 ändrat stroke tillskriva målning. De drawRect: Metoden nu iterates över alla slag i målningen och drar var och en med draw_stroke, som innehåller teckenkoden du skrev tidigare.

Du måste också uppdatera vykontrollen för att innehålla a Målning modell. På toppen av PaintingController klass, lägg till attr_reader: målning. Som namnet antyder, viewDidLoad metod för UIViewController klass-superklassen av PaintingController klass-kallas när visningsstyrenheten har laddat upp sin syn. De viewDidLoad Metod är därför ett bra ställe att skapa en Målning instans och ställa in målning attribut av PaintingView objekt.

def viewDidLoad @painting = Painting.new painting_view.painting = målningens slut

Som alltid glöm inte att lägga till tester för viewDidLoad till spec / controllers / painting_controller_spec.rb.

beskriv "#viewDidLoad" gör det "sätter målningen" göra controller.painting.should.be.instance_of Painting avsluta det "sätter målningsattributet för målningsvyn" gör controller.painting_view.painting.should == controller.painting endänden

5. Gestkännare

Din ansökan blir ganska tråkig om du inte tillåter människor att rita på skärmen med sina fingrar. Låt oss lägga till den här funktionen nu. Skapa en fil för StrokeGestureRecognizer klass tillsammans med sin spec genom att köra följande kommandon från kommandoraden.

tryck på app / visningar / stroke_gesture_recognizer.rb touch spec / views / stroke_gesture_recognizer_spec.rb

Skapa sedan skelettet för klassen.

klass StrokeGestureRecognizer < UIGestureRecognizer attr_reader :position end

De StrokeGestureRecognizer klassen sträcker sig UIGestureRecognizer klass, som hanterar beröringsinmatning. Den har en placera ange att PaintingController klassen kommer att användas för att bestämma positionen för användarens finger.

Det finns fyra metoder du behöver implementera i StrokeGestureRecognizer klass, touchesBegan: withEvent:, touchesMoved: withEvent:, touchesEnded: withEvent:, och touchesCancelled: withEvent:. De touchesBegan: withEvent: Metoden heter när användaren börjar röra skärmen med fingret. De touchesMoved: withEvent: Metoden heter upprepade gånger när användaren flyttar sitt finger och touchesEnded: withEvent: Metoden åberopas när användaren lyfter fingret från skärmen. Slutligen, den touchesCancelled: withEvent: Metoden åberopas om användaren avbröter gesten.

Din gestkännare måste göra två saker för varje händelse, uppdatera placera attribut och ändra stat fast egendom.

klass 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

Både touchesEnded: withEvent: och touchesCancelled: withEvent: metoder ställde staten till UIGestureRecognizerStateEnded. Detta beror på att det inte spelar någon roll om användaren avbryts, så borde ritningen vara orörd.

För att testa StrokeGestureRecognizer klass måste du kunna skapa en instans av UITouch. Tyvärr finns det inget offentligt tillgängligt API för att uppnå detta. För att få det att fungera använder vi biblioteket Facon mocking.

Lägg till pärla "rörelse-facon" till din Gemfile och springa buntinstallation. Sen Lägg till kräver "motion-facon" Nedan kräver "sockercube-color" i projektets Rakefile.

Sedan implementerar du StrokeGestureRecognizer spec.

beskriv StrokeGestureRecognizer utvidga 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] slutet beskriver" #touchesBegan: withEvent: "gör före gör @ stroke_gesture_recognizer.touchesBegan touches1, withEvent: nil) avsluta det "ställer in positionen till gestens position" gör @ stroke_gesture_recognizer.position.should == CGPoint.new (100, 200) avslutar det "anger tillståndet för gestkännaren" gör @ stroke_gesture_recognizer.state .should == UIGestureRecognizerStateBegan endänden beskriver "#touchesMoved: withEvent:" gör före gör @ stroke_gesture_recognizer.touchesBegan (@ touches1, withEvent: nil) @ stroke_gesture_recognizer.touchesMoved (@ touches2, withEvent: nil) avsluta det "ställer in positionen till gestens position "gör @ stroke_gesture_recognizer.pos ition.should == CGPoint.new (300, 400) avslutar det "anger tillståndet för gestgjenkännaren" gör @ stroke_gesture_recognizer.state.should == UIGestureRecognizerStateChanged endänden beskriver "#touchesEnded: withEvent:" gör innan @stroke_gesture_recognizer. touchesBegan (@ touches1, withEvent: nil) @ stroke_gesture_recognizer.touchesEnded (@ touches2, withEvent: nil) avsluta "ställer positionen till gestens position" gör @ stroke_gesture_recognizer.position.should == CGPoint.new (300, 400) det "anger tillståndet för gestkännigenkännaren" gör @ stroke_gesture_recognizer.state.should == UIGestureRecognizerStateEnded slutändan beskriver "#touchesCancelled: withEvent:" gör före gör @ stroke_gesture_recognizer.touchesBegan (@ touches1, withEvent: nil) @ stroke_gesture_recognizer.touchesCancelled @ touches2, withEvent: nil) avsluta det "ställer positionen till gestpositionen" gör @ stroke_gesture_recognizer.position.should == CGPoint.new (300, 400) avslutar det "anger tillståndet för gestkännaren" gör @stroke_gesture_r ecognizer.state.should == UIGestureRecognizerStateEnded slutet änden

förlänga Facon :: SpecHelpers gör flera metoder tillgängliga i dina specifikationer, inklusive falsk. falsk är ett enkelt sätt att skapa testobjekt som fungerar precis som du vill att de ska. I innan blockera i början av specifikationerna, du mocking instanser av UITouch med locationInView: metod som returnerar en fördefinierad punkt.

Lägg sedan till en stroke_gesture_changed metod till PaintingController klass. Denna metod kommer att få en förekomst av StrokeGestureRecognizer klass närhelst gesten är uppdaterad.

def stroke_gesture_changed (stroke_gesture_recognizer) om stroke_gesture_recognizer.state == UIGestureRecognizerStateBegan painting.start_stroke (stroke_gesture_recognizer.position, selected_color) annars painting.continue_stroke (stroke_gesture_recognizer.position) slutmålning_view.setNeedsDisplay slutet

När gestkännarens status är UIGestureRecognizerStateBegan, den här metoden startar en ny stroke i Målning objekt med hjälp av StrokeGestureRecognizers position och selected_color. Annars fortsätter den aktuella stroke.

Lägg till specifikationerna för den här metoden.

beskriv "#stroke_gesture_changed" gör innan dra (controller.painting_view,: points => [CGPoint.new (100, 100), CGPoint.new (150, 150), CGPoint.new (200, 200)]) avsluta det " lägger till punkterna till stroke "do control.painting.strokes.first.points [0] .should == CGPoint.new (100, 100) controller.painting.strokes.first.points [1] .should == CGPoint. new (150, 150) controller.painting.strokes.first.points [2] .should == CGPoint.new (200, 200) avslutar det "sätter streckets färg till den valda färgen" göra kontroller.painting.strokes.first .color.should == controller.selected_color slutänden

RubyMotion ger flera hjälpar metoder för att simulera användarens interaktion, inklusive drag. Använder sig av drag, Du kan simulera en användares interaktion med skärmen. De poäng alternativet tillåter dig att ge en rad punkter för draget.

Om du skulle köra specifikationerna nu skulle de misslyckas. Det beror på att du måste lägga till gestkännaren till storyboardet. Starta gränssnittsbyggaren genom att köra bunt exec rake ib: öppen. Från Objektbibliotek, dra en Objekt in i din scen och ändra sin klass till StrokeGestureRecognizer i Identitetsinspektör till höger.

Styr och dra från StrokeGestureRecognizer protestera mot PaintingController och välj select_color metod från menyn som visas. Detta kommer att säkerställa select_color Metoden kallas närhelst gestkännaren utlöses. Styr sedan och dra från PaintingView protestera mot StrokeGestureRecognizer objekt och välj gestureRecognizer från menyn som visas.

Lägg till en spec för gestkännaren till PaintingController specs i #painting_view beskriva blockera.

beskriv "#painting_view" gör det "är anslutet i storyboard" gör controller.painting_view.should.not.be.nil avsluta det "har en stroke gestkännare" gör controller.painting_view.gestureRecognizers.length.should == 1 kontrollenhet. painting_view.gestureRecognizers [0] .should.be.instance_of StrokeGestureRecognizer slutet slutet

Det är allt. Med dessa ändringar bör din ansökan nu tillåta en person att dra på skärmen. Kör din ansökan och ha det kul.

6. Slutliga tuffar

Det finns några sista handen att lägga till innan din ansökan är klar. Eftersom din ansökan är nedsänkt är statusfältet lite distraherande. Du kan ta bort den genom att ställa in UIStatusBarHidden och UIViewControllerBasedStatusBarAppearance värden i programmets Info.plist. Detta är lätt att göra i RubyMotion inrätta blockera inuti projektets Rakefile.

Motion :: Projekt :: App.setup gör | app | app.name = 'Paint' app.info_plist ['UIStatusBarHidden'] = sant app.info_plist ['UIViewControllerBasedStatusBarAppearance'] = falskt slut

Applikations ikoner och startbilder ingår i källfilerna i denna handledning. Ladda ner bilderna och kopiera dem till Medel katalog över projektet. Ställ sedan in programikonen i Rakefile-konfigurationen. Du kan behöva rengöra byggnaden genom att springa bunt exec rake clean: allt för att se den nya lanseringsbilden.

Motion :: Projekt :: App.setup gör | app | app.name = 'Paint' app.info_plist ['UIStatusBarHidden'] = sann app.info_plist ['UIViewControllerBasedStatusBarAppearance'] = falskt app.icons = ["icon.png"] slut

Slutsats

Det är allt. Du har nu en komplett app som är redo för en miljon nedladdningar i App Store. Du kan visa och ladda ner källan till den här applikationen från GitHub.

Även om din app är klar finns det så mycket mer att du kan lägga till. Du kan lägga till kurvor mellan raderna, fler färger, olika linjebredder, spara, ångra och göra om, och allt annat du kan tänka dig. Vad kommer du att göra för att förbättra din app? Låt mig veta i kommentarerna nedan.