RubyMotion è un fantastico framework per la creazione di applicazioni iOS performanti utilizzando il linguaggio Ruby. Nella prima parte di questo tutorial, hai imparato come impostare e implementare un'applicazione RubyMotion. Hai lavorato con Interface Builder per creare l'interfaccia utente dell'applicazione, implementato un controller di visualizzazione e hai imparato come scrivere test per la tua applicazione.
In questo tutorial, imparerai a conoscere il modello di progettazione Model-View-Controller o MVC e come utilizzarlo per strutturare l'applicazione. Implementerai anche una visualizzazione di pittura e aggiungi un riconoscitore di gesti che consente all'utente di disegnare sullo schermo. Quando hai finito, avrai un'applicazione completa e pienamente funzionante.
Apple incoraggia gli sviluppatori iOS ad applicare il modello di progettazione Model-View-Controller alle loro applicazioni. Questo schema suddivide le classi in una delle tre categorie, modelli, viste e controller.
Come si applica MVC alla tua domanda? Hai già iniziato a implementare il PaintingController
classe, che collegherà i tuoi modelli e viste insieme. Per il livello del modello, dovrai aggiungere due classi:
Ictus
Questa classe rappresenta un singolo tratto nel dipinto.Pittura
Questa classe rappresenta l'intero dipinto e contiene uno o più tratti.Per il livello vista, creerai a PaintingView
classe che è responsabile della visualizzazione di a Pittura
oggetto per l'utente. Aggiungerai anche un StrokeGestureRecongizer
che cattura l'input tattile dall'utente.
Iniziamo con Ictus
modello. Un tratto consisterà in un colore e diversi punti che rappresentano il tratto. Per iniziare, crea un file per il Ictus
classe, app / modelli / stroke.rb, e un altro per le sue specifiche, spec / modelli / stroke.rb.
Quindi, implementa lo scheletro della classe del tratto e un costruttore.
class Stroke attr_reader: points,: color end
Il Ictus
la classe ha due attributi, punti
, una raccolta di punti, e colore
, il colore del Ictus
oggetto. Quindi, implementa un costruttore.
class Stroke attr_reader: points,: color def initialize (start_point, colour) @points = [start_point] @color = color end end
Sembra fantastico finora. Il costruttore accetta due argomenti, punto di partenza
e colore
. Imposta punti
a una serie di punti contenenti punto di partenza
e colore
al colore fornito.
Quando un utente fa scorrere il dito sullo schermo, è necessario un modo per aggiungere punti al Ictus
oggetto. Aggiungi il add_point
metodo a Ictus
.
def punti add_point (punto) << point end
È stato facile. Per comodità, aggiungi un altro metodo al Ictus
classe che restituisce il punto di partenza.
def start_point points.first end
Naturalmente, nessun modello è completo senza un set di specifiche per andare avanti con esso.
descrivere 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 descrive "#initialize" do before do @stroke = Stroke.new (@start_point, @color) termina "imposta il color "do @ stroke.color.should == @color end end descrive" #start_point "fallo" restituisce il punto di inizio del tratto "do @ stroke.start_point.should == @start_point end end descrive" #add_point "fallo" aggiunge i punti al tratto "do @ stroke.points.should == [@start_point, @middle_point, @end_point] end end descrive" #start_point "fallo" restituisce il punto di partenza "do @ stroke.start_point.should == @start_point end end end
Questo dovrebbe iniziare a sentirsi familiare. Hai aggiunto quattro blocchi descrittivi che testano il inizializzare
, punto di partenza
, add_point
, e punto di partenza
metodi. C'è anche un prima
blocco che imposta alcune variabili di istanza per le specifiche. Notare il descrivere
blocco per #inizializzare
ha un prima
blocco che ripristina il @ictus
oggetto. Va bene. Con le specifiche, non devi preoccuparti delle prestazioni come con un'applicazione normale.
È il momento della verità, è il momento di far disegnare qualcosa. Inizia creando un file per il PaintingView
classe a app / views / painting_view.rb. Perché stiamo facendo un disegno specializzato, il PaintingView
la classe è difficile da testare. Per brevità, ho intenzione di saltare le specifiche per ora.
Quindi, implementare il PaintingView
classe.
classe 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, questo è un codice molto. Scopriamolo pezzo per pezzo. Il PaintingView
la classe estende il UIView
classe. Questo permette PaintingView
da aggiungere come sottoview di PaintingController
vista. Il PaintingView
la classe ha un attributo, ictus
, che è un'istanza del Ictus
classe del modello.
Per quanto riguarda il pattern MVC, quando si lavora con l'SDK iOS, è accettabile per una vista conoscere un modello, ma non è corretto per un modello sapere di una vista.
Nel PaintingView
classe, abbiamo scavalcato UIView
'S drawRect:
metodo. Questo metodo consente di implementare codice di disegno personalizzato. La prima riga di questo metodo, super
, chiama il metodo sulla super classe, UIView
in questo esempio, con gli argomenti forniti.
Nel drawRect:
, controlliamo anche che il ictus
l'attributo non lo è zero
. Questo impedisce errori se ictus
non è stato ancora impostato. Quindi richiamiamo il contesto di disegno corrente invocando UIGraphicsGetCurrentContext
, configura il tratto che stiamo per disegnare, sposta il contesto del disegno su punto di partenza
del tratto e aggiunge linee per ogni punto nel ictus
oggetto. Infine, invochiamo CGContextStrokePath
per tracciare il percorso, disegnandolo nella vista.
Aggiungi una presa a PaintingController
per la vista del dipinto.
outlet: painting_view
Accendi Interface Builder eseguendo bundle exec rake ib: aperto
e aggiungere un UIView
oggetto al PaintingController
Vista dal Libreria Ojbect sulla destra. Imposta la classe della vista su PaintingView
nel Identity Inspector. Assicurati che la vista del disegno sia posizionata sotto i pulsanti che hai aggiunto in precedenza. È possibile regolare l'ordine delle sottoview modificando le posizioni della vista nella gerarchia della vista a sinistra.
Controlla e trascina dal controller vista su PaintingView
e selezionare il painting_view
uscita dal menu che appare.
Seleziona la vista pittura e imposta il suo colore di sfondo a 250
rosso, 250
verde, e 250
blu.
Non dimenticare di aggiungere una specifica a spec / controller / painting_controller_spec.rb per il painting_view
presa.
descrivere "#painting_view" farlo "è connesso nello storyboard" do controller.painting_view.should.not.be.nil end end
Per assicurarti che il tuo codice di disegno funzioni correttamente, aggiungi il seguente snippet di codice al file PaintingController
classe ed esegui la tua applicazione. Puoi eliminare questo snippet di codice quando hai verificato che tutto funzioni come previsto.
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
Ora che puoi disegnare un tratto, è il momento di salire di livello all'intero dipinto. Iniziamo con Pittura
modello. Crea un file per la classe a app / modelli / painting.rb e implementare il Pittura
classe.
class Pittura attr_accessor: strokes def initialize @strokes = [] end def stroke_stroke (point, colour) strokes << Stroke.new(point, color) end def continue_stroke(point) current_stroke.add_point(point) end def current_stroke strokes.last end end
Il Pittura
il modello è simile al Ictus
classe. Il costruttore si inizializza colpi
a un array vuoto. Quando una persona tocca lo schermo, l'applicazione inizierà un nuovo tratto chiamando start_stroke
. Quindi, mentre l'utente trascina il dito, aggiungerà punti con continue_stroke
. Non dimenticare le specifiche per il Pittura
classe.
descrivere Pittura fai prima 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 descrive "#initialize" fai prima di @painting = Painting.new finisce "imposta il tratto su un array vuoto "do @ painting.strokes.should == [] end end descrive" #start_stroke "fai prima @ painting.start_stroke (@ point1, UIColor.redColor) @ painting.start_stroke (@ point2, UIColor.blueColor) terminalo "avvia nuovi tratti" 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 descrive "#continue_stroke" prima di fare @ painting.start_stroke (@ point1 , UIColor.redColor) @ painting.continue_stroke (@ point2) @ painting.start_stroke (@ point3, UIColor.blueColor) @ painting.con tinue_stroke (@ point4) termina "aggiunge punti ai tratti correnti" do @ painting.strokes [0] .points.should == [@ point1, @ point2] @ painting.strokes [1] .points.should == [ @ point3, @ point4] end end end
Quindi, modificare il PaintingView
classe per disegnare a Pittura
oggetto invece di a Ictus
oggetto.
classe 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
Hai cambiato il ictus
attribuire a pittura
. Il drawRect:
il metodo ora esegue iterazioni su tutti i tratti del dipinto e disegna ognuno di essi usando draw_stroke
, che contiene il codice del disegno che hai scritto in precedenza.
È inoltre necessario aggiornare il controller della vista per contenere a Pittura
modello. Nella parte superiore del PaintingController
classe, aggiungi attr_reader: painting
. Come suggerisce il nome, il viewDidLoad
metodo del UIViewController
classe: la superclasse del PaintingController
la classe viene chiamata quando il controller della vista ha finito di caricare la sua vista. Il viewDidLoad
il metodo è quindi un buon posto per creare un Pittura
istanza e impostare il pittura
attributo del PaintingView
oggetto.
def viewDidLoad @painting = Painting.new painting_view.painting = fine della pittura
Come sempre, non dimenticare di aggiungere test per viewDidLoad
a spec / controller / painting_controller_spec.rb.
descrivi "#viewDidLoad" fallo "imposta il disegno" do controller.painting.should.be.instance_of Pittura fine "imposta l'attributo pittura della vista pittura" do controller.painting_view.painting.should == controller.painting end end
La tua applicazione sarà piuttosto noiosa a meno che tu non permetta alle persone di disegnare sullo schermo con le dita. Aggiungiamo ora quel pezzo di funzionalità. Creare un file per il StrokeGestureRecognizer
class insieme alle sue specifiche eseguendo i seguenti comandi dalla riga di comando.
touch app / views / stroke_gesture_recognizer.rb touch spec / views / stroke_gesture_recognizer_spec.rb
Quindi, crea lo scheletro per la classe.
class StrokeGestureRecognizer < UIGestureRecognizer attr_reader :position end
Il StrokeGestureRecognizer
la classe estende il UIGestureRecognizer
classe, che gestisce l'input tattile. Ha un posizione
attribuisci che il PaintingController
la classe userà per determinare la posizione del dito dell'utente.
Ci sono quattro metodi che devi implementare nel StrokeGestureRecognizer
classe, touchesBegan: withEvent:
, touchesMoved: withEvent:
, touchesEnded: withEvent:
, e touchesCancelled: withEvent:
. Il touchesBegan: withEvent:
il metodo viene chiamato quando l'utente inizia a toccare lo schermo con il dito. Il touchesMoved: withEvent:
il metodo viene chiamato ripetutamente quando l'utente muove il dito e il tasto touchesEnded: withEvent:
il metodo viene invocato quando l'utente solleva il dito dallo schermo. Finalmente, il touchesCancelled: withEvent:
il metodo viene invocato se il gesto viene annullato dall'utente.
Il tuo riconoscitore di gesti deve fare due cose per ogni evento, aggiornare il posizione
attributo e cambia il stato
proprietà.
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
Sia il touchesEnded: withEvent:
e touchesCancelled: withEvent:
i metodi impostano lo stato su UIGestureRecognizerStateEnded
. Questo perché non importa se l'utente viene interrotto, il disegno dovrebbe rimanere intatto.
Al fine di testare il StrokeGestureRecognizer
classe, devi essere in grado di creare un'istanza di UITouch
. Sfortunatamente, non ci sono API pubblicamente disponibili per realizzare questo. Per farlo funzionare, faremo uso della libreria di derisione di Facon.
Inserisci gemma "motion-facon"
al tuo Gemfile e corri installazione bundle
. Quindi aggiungi richiedono "motion-facon"
sotto richiedere "colorazione sugarcube"
nel Rakefile del progetto.
Quindi, implementare il StrokeGestureRecognizer
spec.
descrivere StrokeGestureRecognizer estendere Facon :: SpecHelpers prima di @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] fine descrive" #touchesBegan: withEvent: "fai prima do @ stroke_gesture_recognizer.touchesBegan (@ touches1, withEvent: nil) termina "imposta la posizione sulla posizione del gesto" do @ stroke_gesture_recognizer.position.should == CGPoint.new (100, 200) termina "imposta lo stato del riconoscitore di gesti" do @ stroke_gesture_recognizer.state .should == UIGestureRecognizerStateBegan end end descrive "#touchesMoved: withEvent:" fai prima di @ stroke_gesture_recognizer.touchesBegan (@tasti1, withEvent: nil) @ stroke_gesture_recognizer.touchesMoved (@tasti2, withEvent: nil) termina "imposta la posizione sul posizione del gesto "do @ stroke_gesture_recognizer.pos ition.should == CGPoint.new (300, 400) termina "imposta lo stato del riconoscitore di gesti" do @ stroke_gesture_recognizer.state.should == UIGestureRecognizerStateChanged end end descrive "#touchesEnded: withEvent:" fai prima di @stroke_gesture_recognizer. touchesBegan (@tasti1, withEvent: nil) @ stroke_gesture_recognizer.touchesEnded (@tasti2, withEvent: nil) termina "imposta la posizione sulla posizione del gesto" do @ stroke_gesture_recognizer.position.should == CGPoint.new (300, 400) end "imposta lo stato del riconoscitore di gesti" do @ stroke_gesture_recognizer.state.should == UIGestureRecognizerStateEnded end end descrive "#touchesCancelled: withEvent:" fai prima do @ stroke_gesture_recognizer.touchesBegan (@tasti1, withEvent: nil) @ stroke_gesture_recognizer.touchesCancelled ( @ touches2, withEvent: nil) termina "imposta la posizione sulla posizione del gesto" do @ stroke_gesture_recognizer.position.should == CGPoint.new (300, 400) termina "imposta lo stato del riconoscitore di gesti" do @stroke_gesture_r ecognizer.state.should == UIGestureRecognizerStateEnded end end end
estendere Facon :: SpecHelpers
rende disponibili diversi metodi nelle specifiche, tra cui finto
. finto
è un modo semplice per creare oggetti di prova che funzionano esattamente nel modo desiderato. Nel prima
blocco all'inizio delle specifiche, ti stai prendendo in giro istanze di UITouch
con il locationInView:
metodo che restituisce un punto predefinito.
Quindi, aggiungere un stroke_gesture_changed
metodo per il PaintingController
classe. Questo metodo riceverà un'istanza di StrokeGestureRecognizer
classe ogni volta che il gesto viene aggiornato.
def stroke_gesture_changed (stroke_gesture_recognizer) if 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
Quando lo stato del riconoscitore di gesti è UIGestureRecognizerStateBegan
, questo metodo inizia un nuovo tratto nel Pittura
oggetto usando il StrokeGestureRecognizer
La posizione e selected_color
. Altrimenti, continua il tratto corrente.
Aggiungi le specifiche per questo metodo.
descrivere "#stroke_gesture_changed" fare prima di trascinare (controller.painting_view,: points => [CGPoint.new (100, 100), CGPoint.new (150, 150), CGPoint.new (200, 200)]) terminarlo " aggiunge i punti al tratto "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) termina "imposta il colore del tratto sul colore selezionato" do controller.painting.strokes.first .color.should == controller.selected_color end-end
RubyMotion offre diversi metodi di supporto per simulare l'interazione dell'utente, incluso trascinare
. utilizzando trascinare
, puoi simulare l'interazione di un utente con lo schermo. Il punti
l'opzione ti consente di fornire una serie di punti per il trascinamento.
Se dovessi eseguire le specifiche ora, fallirebbero. Questo perché è necessario aggiungere il riconoscimento dei gesti allo storyboard. Avvia Interface Builder eseguendo bundle exec rake ib: aperto
. Dal Libreria di oggetti, trascina un Oggetto nella tua scena e cambia la sua classe in StrokeGestureRecognizer
nel Identity Inspector sulla destra.
Controlla e trascina da StrokeGestureRecognizer
oggetto al PaintingController
e scegli il select_color
metodo dal menu che appare. Questo garantirà il select_color
il metodo viene chiamato ogni volta che viene attivato il riconoscitore di gesti. Quindi, controlla e trascina da PaintingView
oggetto al StrokeGestureRecognizer
oggetto e selezionare gestureRecognizer
dal menu che appare.
Aggiungi una specifica per il riconoscimento dei gesti al PaintingController
specifiche nel #painting_view
descrivere
bloccare.
descrivere "#painting_view" farlo "è connesso nello storyboard" do controller.painting_view.should.not.be.nil fine "ha un riconoscimento di gesti di movimento" do controller.painting_view.gestureRecognizers.length.should == 1 controller. painting_view.gestureRecognizers [0] .should.be.instance_of StrokeGestureRecognizer end end
Questo è tutto. Con queste modifiche la tua applicazione dovrebbe ora consentire a una persona di disegnare sullo schermo. Esegui la tua applicazione e divertiti.
Ci sono alcuni ultimi ritocchi da aggiungere prima che l'applicazione sia finita. Poiché la tua applicazione è immersiva, la barra di stato è un po 'di distrazione. Puoi rimuoverlo impostando il UIStatusBarHidden
e UIViewControllerBasedStatusBarAppearance
valori in Info.plist dell'applicazione. Questo è facile da fare in RubyMotion impostare
bloccare all'interno del Rakefile del progetto.
Motion :: Project :: App.setup do | app | app.name = 'Paint' app.info_plist ['UIStatusBarHidden'] = true app.info_plist ['UIViewControllerBasedStatusBarAppearance'] = false end
Le icone dell'applicazione e le immagini di avvio sono incluse nei file sorgente di questo tutorial. Scarica le immagini e copiali su risorse directory del progetto. Quindi, imposta l'icona dell'applicazione nella configurazione di Rakefile. Potrebbe essere necessario pulire la build eseguendo bundle exec rake clean: tutto
per vedere la nuova immagine di lancio.
Motion :: Project :: App.setup do | app | app.name = 'Paint' app.info_plist ['UIStatusBarHidden'] = true app.info_plist ['UIViewControllerBasedStatusBarAppearance'] = false app.icons = ["icon.png"] fine
Questo è tutto. Ora hai un'app completa pronta per un milione di download nell'App Store. È possibile visualizzare e scaricare il sorgente per questa applicazione da GitHub.
Anche se la tua app è terminata, c'è molto altro da aggiungere. È possibile aggiungere curve tra le linee, più colori, diverse larghezze delle linee, salvare, annullare e ripetere e qualsiasi altra cosa si possa immaginare. Cosa farai per rendere migliore la tua app? Fatemi sapere nei commenti qui sotto.