Laten we een RubyMotion-app schrijven deel 2

Wat je gaat creëren

RubyMotion is een fantastisch raamwerk voor het bouwen van performante iOS-applicaties die de Ruby-taal gebruiken. In het eerste deel van deze tutorial hebt u geleerd hoe u een RubyMotion-toepassing opzet en implementeert. U hebt met Interface Builder gewerkt om de gebruikersinterface van de toepassing te maken, een view-controller geïmplementeerd en geleerd hoe u tests voor uw toepassing schrijft.

In deze zelfstudie leert u meer over het Model-View-Controller of MVC-ontwerppatroon en hoe u het kunt gebruiken om uw toepassing te structureren. Je gaat ook een schilderweergave implementeren en een gebaarherkenning toevoegen waarmee de gebruiker op het scherm kan tekenen. Als je klaar bent, heb je een complete, volledig werkende applicatie.

1. Model-View-Controller

Apple moedigt iOS-ontwikkelaars aan om het ontwerppatroon van de Model-View-Controller op hun toepassingen toe te passen. Met dit patroon worden klassen onderverdeeld in een van de drie categorieën, modellen, weergaven en controllers.

  • Modellen bevatten de bedrijfslogica van uw toepassing, de code die de regels bepaalt voor het beheer en de interactie met gegevens. In uw model leeft de kernlogica voor uw toepassing.
  • Weergaven geven informatie weer voor de gebruiker en zorgen ervoor dat ze kunnen communiceren met de applicatie.
  • Controllers zijn verantwoordelijk voor het aan elkaar knopen van de modellen en views. De iOS SDK gebruikt view controllers, gespecialiseerde controllers met een beetje meer kennis van de views dan andere MVC-frameworks.

Hoe is MVC van toepassing op uw toepassing? U bent al begonnen met het implementeren van het PaintingController klasse, die uw modellen en views met elkaar verbindt. Voor de modellaag voegt u twee klassen toe:

  • Beroerte Deze klasse vertegenwoordigt een enkele lijn in het schilderij.
  • schilderij Deze klasse vertegenwoordigt het volledige schilderij en bevat een of meer lijnen.

Voor de weergavelaag maakt u een PaintingView klasse die verantwoordelijk is voor het weergeven van een schilderij bezwaar tegen de gebruiker. Je voegt ook een toe StrokeGestureRecongizer die aanraakinvoer van de gebruiker vastlegt.

2. Slagen

Laten we beginnen met de Beroerte model. Een streek zal bestaan ​​uit een kleur en meerdere punten die de streek voorstellen. Maak om te beginnen een bestand voor de Beroerte klasse, app / modellen / stroke.rb, en nog een voor zijn spec, spec / modellen / stroke.rb.

Implementeer vervolgens het skelet van de streekklasse en een constructor.

class Stroke attr_reader: points,: color end

De Beroerte klasse heeft twee attributen, points, een verzameling punten, en kleur, de kleur van de Beroerte voorwerp. Implementeer vervolgens een constructor.

class Stroke attr_reader: punten,: color def initialize (start_point, kleur) @points = [startpunt] @color = einde kleur

Dat ziet er tot nu toe geweldig uit. De constructor accepteert twee argumenten, startpunt en kleur. Het gaat op points naar een reeks punten die bevatten startpunt en kleur naar de geleverde kleur.

Wanneer een gebruiker met zijn vinger over het scherm veegt, hebt u een manier nodig om punten toe te voegen aan de Beroerte voorwerp. Voeg de toe add_point methode om Beroerte.

def add_point (punt) punten << point end

Dat was gemakkelijk. Voeg gemakshalve nog een methode toe aan de Beroerte klasse die het startpunt retourneert.

def beginpunt punten.eerste einde

Natuurlijk is geen enkel model compleet zonder een aantal specificaties om mee te gaan.

beschrijf Stroke do before do @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) end beschrijven "#initialize" do before do @stroke = Stroke.new (@start_point, @color) end it "stelt de color "do @ stroke.color.should == @color end end beschrijven" #start_point "doen" retourneert het beginpunt van de streek "do @ stroke.start_point.should == @start_point end end beschrijven" #add_point "doe het" voegt de punten toe aan de streek "do @ stroke.points.should == [@start_point, @middle_point, @end_point] einde beschrijft" #start_point "doe het" geeft het startpunt terug "do @ stroke.start_point.should == @ endart_point end end end

Dit zou vertrouwd moeten worden. U hebt vier beschrijvingsblokken toegevoegd die de test initialiseren, startpunt, add_point, en startpunt methoden. Er is ook een voor blok dat een paar instantievariabelen voor de specificaties instelt. Let op de beschrijven blokkeren voor #initialize heeft een voor blok dat de @beroerte voorwerp. Dat is prima. Met specificaties hoef je je niet zo druk te maken om de prestaties als met een gewone applicatie.

3. Tekening

Het is het moment van de waarheid, het is tijd om je toepassing iets te laten tekenen. Begin met het maken van een bestand voor de PaintingView les op app / views / painting_view.rb. Omdat we een gespecialiseerde tekening maken, is de PaintingView klasse is lastig om te testen. Kortheidshalve ga ik voorlopig de specificaties overslaan.

Implementeer vervolgens de PaintingView klasse.

klas 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

Pff, dat is veel code. Laten we het stuk voor stuk opsplitsen. De PaintingView class breidt het uit UIView klasse. Dit staat toe PaintingView worden toegevoegd als een subweergave van PaintingControllerhet uitzicht. De PaintingView klasse heeft één kenmerk, beroerte, wat een instantie is van de Beroerte modelklasse.

Met betrekking tot het MVC-patroon is het bij het werken met de iOS-SDK aanvaardbaar om een ​​beeld te kennen van een model, maar het is niet oké voor een model om een ​​beeld te kennen.

In de PaintingView klas, we hebben overschreven UIView's drawRect: methode. Met deze methode kunt u aangepaste tekencode implementeren. De eerste regel van deze methode, super, roept de methode op in de superklasse, UIView in dit voorbeeld met de opgegeven argumenten.

In drawRect:, we controleren ook dat de beroerte kenmerk is dat niet nul. Dit voorkomt fouten als beroerte is nog niet ingesteld. Vervolgens halen we de huidige tekencontext op door op te roepen UIGraphicsGetCurrentContext, configureer de streek die we gaan tekenen, verplaats de tekencontext naar de startpunt van de streek en voegt lijnen toe voor elk punt in de beroerte voorwerp. Ten slotte roepen we aan CGContextStrokePath om het pad te aaien en het in de weergave te tekenen.

Voeg een stopcontact toe aan PaintingController voor het schilderijaanzicht.

outlet: painting_view

Start Interface Builder door te hardlopen bundel exec rake ib: open en voeg een toe UIView bezwaar tegen de PaintingController's uitzicht vanaf de Ojbect-bibliotheek aan de rechterkant. Stel de klasse van de weergave in op PaintingView in de Identiteitsinspecteur. Zorg ervoor dat de schilderijweergave zich onder de knoppen bevindt die u eerder hebt toegevoegd. U kunt de volgorde van de subweergaven aanpassen door de posities van de weergaven in de weergavehiërarchie aan de linkerkant te wijzigen.

Bedien en sleep van de view controller naar de PaintingView en selecteer de painting_view uit het menu dat verschijnt.

Selecteer de schilderijweergave en stel de achtergrondkleur ervan in 250 rood, 250 groen, en 250 blauw.

Vergeet niet om een ​​spec toe te voegen spec / controllers / painting_controller_spec.rb voor de painting_view stopcontact.

beschrijf "#painting_view" do it "is verbonden in het storyboard" do controller.painting_view.should.not.be.nil end end

Om ervoor te zorgen dat uw tekencode correct werkt, voegt u het volgende codefragment toe aan de PaintingController klasse en voer je applicatie uit. U kunt dit codefragment verwijderen wanneer u heeft geverifieerd dat alles werkt zoals verwacht.

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 end

4. Schilderen

Nu je een streek kunt tekenen, is het tijd om naar het hele schilderij te stijgen. Laten we beginnen met de schilderij model. Maak een bestand voor de klas op app / modellen / painting.rb en implementeer de schilderij klasse.

klasse Painting attr_accessor: slagen def initialize @strokes = [] end def start_stroke (point, color) stroke << Stroke.new(point, color) end def continue_stroke(point) current_stroke.add_point(point) end def current_stroke strokes.last end end

De schilderij model is vergelijkbaar met de Beroerte klasse. De constructor initialiseert beroertes naar een lege array. Wanneer een persoon het scherm aanraakt, start de toepassing een nieuwe streek door te bellen start_stroke. Als de gebruiker vervolgens met zijn vinger sleept, voegt hij punten toe continue_stroke. Vergeet de specificaties voor de schilderij klasse.

beschrijf Schilderen do before do @ 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 end beschrijft "#initialize" doe voordat doe @painting = Painting.new end it "zet de streep op een lege array "do @ painting.strokes.should == [] end end beschrijven" #start_stroke "doe voordat doe @ painting.start_stroke (@ point1, UIColor.redColor) @ painting.start_stroke (@ point2, UIColor.blueColor) einde het "start nieuwe slagen" do @ 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 end end beschrijven "#continue_stroke" doen voordat doen @ painting.start_stroke (@ point1 , UIColor.redColor) @ painting.continue_stroke (@ point2) @ painting.start_stroke (@ point3, UIColor.blueColor) @ painting.con tinue_stroke (@ point4) beëindigen "voegt punten toe aan de huidige streken" do @ painting.strokes [0] .points.should == [@ point1, @ point2] @ painting.strokes [1] .points.should == [ @ end3, @ point4] end end end

Wijzig vervolgens de PaintingView klas om te tekenen schilderij object in plaats van een Beroerte voorwerp.

klas 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

Je hebt de beroerte attribuut aan schilderij. De drawRect: methode itereert nu over alle streken in het schilderij en tekent er elk een uit draw_stroke, die de tekencode bevat die je eerder hebt geschreven.

U moet ook de weergavecontroller bijwerken om een ​​te bevatten schilderij model. Aan de top van de PaintingController klasse, toevoegen attr_reader: schilderen. Zoals de naam al aangeeft, de viewDidLoad methode van de UIViewController class-de superklasse van de PaintingController class-wordt aangeroepen wanneer de view controller klaar is met het laden van zijn weergave. De viewDidLoad methode is daarom een ​​goede plek om een ​​te maken schilderij Bijvoorbeeld en stel de schilderij attribuut van de PaintingView voorwerp.

def viewDidLoad @painting = Painting.new painting_view.painting = painting end

Zoals altijd, vergeet niet om tests voor toe te voegen viewDidLoad naar spec / controllers / painting_controller_spec.rb.

beschrijf "#viewDidLoad" do it "stelt het schilderij in" do controller.painting.should.be.instance_of Painting end it "stelt het schilderijkenmerk van de schilderijweergave in" do controller.painting_view.painting.should == controller.painting end end

5. Gebaarherkenners

Je sollicitatie zal behoorlijk saai zijn, tenzij je mensen toestaat met hun vingers op het scherm te tekenen. Laten we dat stukje functionaliteit nu toevoegen. Maak een bestand voor de StrokeGestureRecognizer klasse samen met de specificatie door de volgende opdrachten uit te voeren vanaf de opdrachtregel.

touch app / views / stroke_gesture_recognizer.rb touch spec / views / stroke_gesture_recognizer_spec.rb

Maak vervolgens het skelet voor de klas.

class StrokeGestureRecognizer < UIGestureRecognizer attr_reader :position end

De StrokeGestureRecognizer class breidt het uit UIGestureRecognizer klasse, die aanraakinvoer verwerkt. Het heeft een positie attribuut dat de PaintingController klasse gebruikt om de positie van de vinger van de gebruiker te bepalen.

Er zijn vier methoden die u moet implementeren in de StrokeGestureRecognizer klasse, touchesBegan: withEvent:, touchesMoved: withEvent:, touchesEnded: withEvent:, en touchesCancelled: withEvent:. De touchesBegan: withEvent: methode wordt aangeroepen wanneer de gebruiker het scherm met zijn vinger begint aan te raken. De touchesMoved: withEvent: methode wordt herhaaldelijk aangeroepen wanneer de gebruiker zijn vinger beweegt en de touchesEnded: withEvent: methode wordt aangeroepen wanneer de gebruiker zijn vinger van het scherm haalt. eindelijk, de touchesCancelled: withEvent: methode wordt aangeroepen als de beweging door de gebruiker wordt geannuleerd.

Uw gebaarherkenner moet twee dingen doen voor elke gebeurtenis, de update bijwerken positie attribuut en verander de staat eigendom.

class 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

Beide touchesEnded: withEvent: en touchesCancelled: withEvent: methoden stellen de status in op UIGestureRecognizerStateEnded. Dit komt omdat het niet uitmaakt of de gebruiker wordt onderbroken, de tekening moet onaangetast blijven.

Om het te testen StrokeGestureRecognizer klasse, moet je een instantie kunnen maken van UITouch. Helaas is er geen openbaar beschikbare API om dit te bereiken. Om het te laten werken, maken we gebruik van de spotbibliotheek van Facon.

Toevoegen gem 'motion-facon' naar je Gemfile en rennen bundel installeren. Dan toevoegen vereisen "motion-facon" beneden vereisen "sugarcube-color" in Rakefile van het project.

Implementeer vervolgens de StrokeGestureRecognizer spec.

beschrijven StrokeGestureRecognizer verlengen Facon :: SpecHelpers voor doen @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] einde beschrijving" #touchesBegan: withEvent: "doe daarvoor @ stroke_gesture_recognizer.touchesBegan (@ touches1, withEvent: nil) end it "stelt de positie in op de positie van het gebaar" do @ stroke_gesture_recognizer.position.should == CGPoint.new (100, 200) stop het "zet de status van de gebaarherkenner" do @ stroke_gesture_recognizer.state .should == UIGestureRecognizerStateBegan end end beschrijven "#touchesMoved: withEvent:" doe daarvoor @ stroke_gesture_recognizer.touchesBegan (@ touches1, withEvent: nil) @ stroke_gesture_recognizer.touchesMoved (@ touches2, withEvent: nil) end it "bepaalt de positie naar de de positie van het gebaar "do @ stroke_gesture_recognizer.pos ition.should == CGPoint.new (300, 400) eindig ermee "stelt de status van de gebaarherkenner in" do @ stroke_gesture_recognizer.state.should == UIGestureRecognizerStateChanged end end beschrijven "#touchesEnded: withEvent:" doe voordat u @stroke_gesture_recognizer gebruikt. raaktBegan aan (@ touches1, withEvent: nil) @ stroke_gesture_recognizer.touchesEnded (@ touches2, withEvent: nil) end it "stelt de positie in op de positie van het gebaar" do @ stroke_gesture_recognizer.position.should == CGPoint.new (300, 400) einde het "bepaalt de status van de gebarenherkenner" do @ stroke_gesture_recognizer.state.should == UIGestureRecognizerStateEended end-end beschrijft "#touchesCancelled: withEvent:" doe daarvoor @ stroke_gesture_recognizer.touchesBegan (@ raakt1, withEvent: nihil) @ stroke_gesture_recognizer.touchesCancelled ( @ touches2, withEvent: nihil) eindig "stelt de positie in op de positie van het gebaar" do @ stroke_gesture_recognizer.position.should == CGPoint.new (300, 400) eindig het "stelt de status van de gebaarherkenner in" doe @stroke_gesture_r ecognizer.state.should == UIGestureRecognizerStateEnded end end end

verlengen Facon :: SpecHelpers maakt verschillende methoden beschikbaar in uw specificaties, inclusief bespotten. bespotten is een eenvoudige manier om testobjecten te maken die precies zo werken als u wilt. In de voor blok aan het begin van de specs, je spot met instanties UITouch met de locationInView: methode die een vooraf gedefinieerd punt retourneert.

Voeg vervolgens een toe stroke_gesture_changed methode om de PaintingController klasse. Deze methode ontvangt een exemplaar van de StrokeGestureRecognizer klasse wanneer het gebaar is bijgewerkt.

def stroke_gesture_changed (stroke_gesture_recognizer) als stroke_gesture_recognizer.state == UIGestureRecognizerStateBegan painting.start_stroke (stroke_gesture_recognizer.position, selected_color) else painting.continue_stroke (stroke_gesture_recognizer.position) end painting_view.setNeedsDisplay end

Wanneer de status van de bewegingsherkenner is UIGestureRecognizerStateBegan, deze methode start een nieuwe streek in de schilderij object met behulp van de StrokeGestureRecognizerPositie en selected_color. Anders gaat het door met de huidige streek.

Voeg de specificaties voor deze methode toe.

beschrijf "#stroke_gesture_changed" doe voordat je sleept (controller.painting_view,: points => [CGPoint.new (100, 100), CGPoint.new (150, 150), CGPoint.new (200, 200)]) stop het " voegt de punten toe aan de streek "do controller.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) eindig met "stelt de kleur van de lijn in op de geselecteerde kleur" do controller.painting.strokes.first .color.should == controller.selected_color einde end

RubyMotion biedt verschillende hulpmethoden om de interactie van gebruikers te simuleren, inclusief slepen. Gebruik makend van slepen, je kunt de interactie van een gebruiker met het scherm simuleren. De points optie biedt u een reeks punten voor het slepen.

Als je nu de specificaties zou uitvoeren, zouden ze falen. Dat komt omdat je de gebaarherkenner aan het storyboard moet toevoegen. Start Interface Builder door te starten bundel exec rake ib: open. Van de Objectbibliotheek, sleep een Voorwerp in je scene, en verander zijn klasse in StrokeGestureRecognizer in de Identiteitsinspecteur aan de rechterkant.

Besturen en slepen vanuit de StrokeGestureRecognizer bezwaar tegen de PaintingController en kies de selecteer kleur methode uit het menu dat verschijnt. Dit zorgt voor de selecteer kleur methode wordt aangeroepen wanneer de bewegingsherkenner wordt geactiveerd. Bedien en sleep vervolgens vanuit de PaintingView bezwaar tegen de StrokeGestureRecognizer object en selecteer gestureRecognizer vanuit het menu dat verschijnt.

Voeg een specificatie voor de gebaarherkenner toe aan de PaintingController specificaties in de #painting_view beschrijven blok.

beschrijf "#painting_view" do it "is verbonden in het storyboard" do controller.painting_view.should.not.be.nil end it "has a stroke gesture recognizer" do controller.painting_view.gestureRecognizers.length.should == 1 controller. painting_view.gestureRecognizers [0] .should.be.instance_of StrokeGestureRecognizer end end

Dat is het. Met deze wijzigingen moet uw toepassing nu toestaan ​​dat iemand op het scherm tekent. Run je applicatie en heb plezier.

6. Laatste details

Er zijn nog een paar laatste details over om toe te voegen voordat uw toepassing is voltooid. Omdat je toepassing meeslepend is, is de statusbalk een beetje storend. U kunt het verwijderen door de UIStatusBarHidden en UIViewControllerBasedStatusBarAppearance waarden in de Info.plist van de toepassing. Dit is eenvoudig te doen in de RubyMotion opstelling blokkeren in de Rakefile van het project.

Motion :: Project :: App.setup do | app | app.name = 'Paint' app.info_plist ['UIStatusBarHidden'] = true app.info_plist ['UIViewControllerBasedStatusBarAppearance'] = false end

De pictogrammen van de toepassing en de startafbeeldingen zijn opgenomen in de bronbestanden van deze zelfstudie. Download de afbeeldingen en kopieer ze naar de middelen map van het project. Stel vervolgens het pictogram van de toepassing in de Rakefile-configuratie in. Mogelijk moet je de build opschonen door te hardlopen bundel exec rake clean: allemaal om de nieuwe startafbeelding te zien.

Motion :: Project :: App.setup do | app | app.name = 'Paint' app.info_plist ['UIStatusBarHidden'] = true app.info_plist ['UIViewControllerBasedStatusBarAppearance'] = false app.icons = ["icon.png"] einde

Conclusie

Dat is het. Je hebt nu een complete app die klaar is voor een miljoen downloads in de App Store. U kunt de bron voor deze toepassing bekijken en downloaden vanuit GitHub.

Hoewel je app is voltooid, kun je er nog veel meer aan toevoegen. U kunt krommingen tussen de lijnen toevoegen, meer kleuren, verschillende lijndiktes, opslaan, ongedaan maken en opnieuw uitvoeren, en alles wat u maar kunt bedenken. Wat gaat u doen om uw app beter te maken? Laat het me weten in de reacties hieronder.