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.
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.
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.
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.
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 PaintingController
het 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
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
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 StrokeGestureRecognizer
Positie 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.
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
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.