Scriviamo un'app RubyMotion parte 2

Cosa starai creando

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.

1. Modello-View-Controller

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.

  • I modelli contengono la logica aziendale dell'applicazione, il codice che determina le regole per la gestione e l'interazione con i dati. Il tuo modello è dove vive la logica di base per l'applicazione.
  • Le visualizzazioni visualizzano le informazioni all'utente e consentono loro di interagire con l'applicazione.
  • I controllori sono responsabili per legare insieme i modelli e le viste. L'SDK di iOS utilizza i controller di visualizzazione, i controller specializzati con una conoscenza leggermente maggiore delle visualizzazioni rispetto ad altri framework MVC.

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.

2. Strokes

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.

3. Disegno

È 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 PaintingControllervista. 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 PaintingControllerVista 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

4. Pittura

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

5. Riconoscimento di gesti

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 StrokeGestureRecognizerLa 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.

6. Tocchi finali

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

Conclusione

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.