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.
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.
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.
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.
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 PaintingController
s å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 PaintingController
sikt 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
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
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 StrokeGestureRecognizer
s 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.
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
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.