Écrivons une application RubyMotion Partie 2

Ce que vous allez créer

RubyMotion est un cadre fantastique pour la création d'applications iOS performantes utilisant le langage Ruby. Dans la première partie de ce didacticiel, vous avez appris à configurer et à mettre en œuvre une application RubyMotion. Vous avez travaillé avec Interface Builder pour créer l'interface utilisateur de l'application, implémenté un contrôleur de vue et appris à écrire des tests pour votre application..

Dans ce didacticiel, vous découvrirez le modèle de conception Model-View-Controller ou MVC et comment vous pouvez l'utiliser pour structurer votre application. Vous allez également implémenter une vue en peinture et ajouter un outil de reconnaissance des gestes permettant à l'utilisateur de dessiner à l'écran. Lorsque vous avez terminé, vous obtenez une application complète et pleinement opérationnelle..

1. Modèle-Vue-Contrôleur

Apple encourage les développeurs iOS à appliquer le modèle de conception Model-View-Controller à leurs applications. Ce modèle divise les classes en trois catégories, modèles, vues et contrôleurs..

  • Les modèles contiennent la logique métier de votre application, le code qui détermine les règles de gestion et d'interaction des données. Votre modèle est la base de la logique de votre application.
  • Les vues affichent des informations pour l'utilisateur et lui permettent d'interagir avec l'application.
  • Les contrôleurs sont responsables de la liaison des modèles et des vues. Le SDK iOS utilise des contrôleurs de vue, des contrôleurs spécialisés avec un peu plus de connaissances des vues que d’autres frameworks MVC..

Comment MVC s'applique-t-il à votre application? Vous avez déjà commencé à implémenter le PeintureContrôleur classe, qui connectera vos modèles et vues ensemble. Pour la couche modèle, vous allez ajouter deux classes:

  • Accident vasculaire cérébral Cette classe représente un seul trait dans la peinture.
  • La peinture Cette classe représente le tableau entier et contient un ou plusieurs traits.

Pour la couche de vue, vous allez créer un PaintingView classe qui est responsable de l'affichage d'un La peinture objet à l'utilisateur. Vous allez aussi ajouter un StrokeGestureRecongizer qui capture la saisie tactile de l'utilisateur.

2. AVC

Commençons par le Accident vasculaire cérébral modèle. Un trait sera composé d'une couleur et de plusieurs points représentant le trait. Pour commencer, créez un fichier pour le Accident vasculaire cérébral classe, app / models / stroke.rb, et un autre pour ses spécifications, spec / models / stroke.rb.

Ensuite, implémentez le squelette de classe de traits et un constructeur.

classe Stroke attr_reader: points,: couleur end

le Accident vasculaire cérébral la classe a deux attributs, points, une collection de points, et Couleur, la couleur de la Accident vasculaire cérébral objet. Ensuite, implémenter un constructeur.

classe Stroke attr_reader: points,: color def initialize (start_point, color) @points = [start_point] @color = color end end

Cela a l'air bien jusqu'à présent. Le constructeur accepte deux arguments, point de départ et Couleur. Il établit points à un tableau de points contenant point de départ et Couleur à la couleur fournie.

Lorsqu'un utilisateur fait glisser son doigt sur l'écran, vous avez besoin d'un moyen d'ajouter des points à la Accident vasculaire cérébral objet. Ajouter le add_point méthode pour Accident vasculaire cérébral.

def add_point (point) points << point end

C'était facile. Pour plus de commodité, ajoutez une méthode supplémentaire à la Accident vasculaire cérébral classe qui retourne le point de départ.

def start_point points.first end

Bien sûr, aucun modèle n'est complet sans un ensemble de spécifications qui l'accompagnent.

decrire 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 décrit "#initialize" do before do @stroke = Stroke.new (@start_point, @color) end it "définit les paramètres suivants: color "do @ stroke.color.should == @color end end" #start_point "do it" renvoie le point de départ du tracé "do @ stroke.start_point.should == @start_point end end" "add_point" ajoute les points au trait "do @ stroke.points.should == [@start_point, @middle_point, @end_point] end end décrit" #start_point "do it" renvoie le point de départ "do @ stroke.start_point.should == @start_point end end end

Cela devrait commencer à se sentir familier. Vous avez ajouté quatre blocs décrivant qui testent la initialiser, point de départ, add_point, et point de départ méthodes. Il y a aussi un avant bloc qui définit quelques variables d'instance pour les spécifications. Remarquez le décrire bloquer pour #initialiser a un avant bloquer qui réinitialise le @accident vasculaire cérébral objet. C'est très bien. Avec les spécifications, vous n'avez pas à vous soucier de la performance comme vous le faites avec une application régulière.

3. dessin

C'est le moment de vérité, il est temps de faire dessiner quelque chose par votre application. Commencez par créer un fichier pour le PaintingView cours à app / views / painting_view.rb. Parce que nous faisons un dessin spécialisé, le PaintingView la classe est difficile à tester. Par souci de brièveté, je vais sauter les spécifications pour l'instant..

Ensuite, implémentez le 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

Ouf, c'est beaucoup de code. Décomposons cela pièce par pièce. le PaintingView la classe étend la UIView classe. Ceci permet PaintingView à ajouter en tant que sous-vue de PeintureContrôleurla vue. le PaintingView la classe a un attribut, accident vasculaire cérébral, qui est une instance de la Accident vasculaire cérébral classe de modèle.

En ce qui concerne le modèle MVC, lorsque vous utilisez le SDK iOS, il est acceptable pour une vue de connaître un modèle, mais ce n'est pas normal pour un modèle de connaître une vue..

dans le PaintingView classe, nous avons remplacé UIViewde drawRect: méthode. Cette méthode vous permet d'implémenter du code de dessin personnalisé. La première ligne de cette méthode, super, appelle la méthode sur la super classe, UIView dans cet exemple, avec les arguments fournis.

Dans drawRect:, nous vérifions également que le accident vasculaire cérébral attribut n'est pas néant. Cela évite les erreurs si accident vasculaire cérébral n'a pas encore été défini. Nous récupérons ensuite le contexte de dessin actuel en appelant UIGraphicsGetCurrentContext, configurer le trait que nous sommes sur le point de dessiner, déplacez le contexte de dessin vers le point de départ du trait et ajoute des lignes pour chaque point de la accident vasculaire cérébral objet. Enfin, nous invoquons CGContextStrokePath tracer le chemin en le dessinant dans la vue.

Ajouter un point de vente à PeintureContrôleur pour la vue en peinture.

sortie: painting_view

Lancez Interface Builder en lançant bundle exec rake ib: ouvert et ajouter un UIView objecter à la PeintureContrôleurvue depuis le Bibliothèque Ojbect sur la droite. Définissez la classe de la vue sur PaintingView dans le Inspecteur d'identité. Assurez-vous que la vue de peinture est positionnée sous les boutons que vous avez ajoutés précédemment. Vous pouvez ajuster l'ordre des sous-vues en modifiant les positions des vues dans la hiérarchie des vues à gauche..

Contrôlez et faites glisser du contrôleur de vue vers le PaintingView et sélectionnez le painting_view sortie du menu qui apparaît.

Sélectionnez la vue en peinture et définissez sa couleur d'arrière-plan sur 250 rouge, 250 vert et 250 bleu.

N'oubliez pas d'ajouter une spécification à spec / controllers / painting_controller_spec.rb pour le painting_view sortie.

décris "#painting_view" do it "est connecté au storyboard" do controller.painting_view.should.not.be.nil end end

Pour vous assurer que votre code de dessin fonctionne correctement, ajoutez l’extrait de code suivant à la PeintureContrôleur classe et lance ton application. Vous pouvez supprimer cet extrait de code lorsque vous avez vérifié que tout fonctionne comme prévu..

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 peinture_view.setNeedsAffichage de fin

4. peinture

Maintenant que vous pouvez dessiner un trait, il est temps de niveler le tableau en entier. Commençons par le La peinture modèle. Créez un fichier pour la classe à app / models / painting.rb et mettre en œuvre le La peinture classe.

class Peinture attr_accessor: strokes def initialize @strokes = [] end def start_stroke (point, couleur) traits << Stroke.new(point, color) end def continue_stroke(point) current_stroke.add_point(point) end def current_stroke strokes.last end end

le La peinture le modèle est similaire à la Accident vasculaire cérébral classe. Le constructeur s'initialise coups à un tableau vide. Lorsqu'une personne touche l'écran, l'application commence un nouveau trait en appelant course_de_route. Ensuite, lorsque l'utilisateur fait glisser son doigt, des points avec continuer_stroke. N'oubliez pas les spécifications pour le La peinture classe.

décrire Peinture faire avant de faire @ 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 décrit "#initialize" do beforepainting = Painting.new end it "définit le trait sur un tableau vide "do @ painting.strokes.should == [] end end décrit" #start_stroke "avant de faire @ painting.start_stroke (@ point1, UIColor.redColor) @ painting.start_stroke (@ point2, UIColor.blueColor) "commence de nouveaux traits" 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 décrit "#continue_stroke" avant de faire @ painting.start_stroke (@ point1 , UIColor.redColor) @ painting.continue_stroke (@ point2) @ painting.start_stroke (@ point3, UIColor.blueColor) @ painting.con tinue_stroke (@ point4) end it "ajoute des points aux traits actuels" do @ painting.strokes [0] .points.should == [@point1, @point2] @ painting.strokes [1] .points.should == [ @ point3, @ point4] fin fin fin

Ensuite, modifiez le PaintingView classe pour dessiner un La peinture objet au lieu d'un Accident vasculaire cérébral objet.

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

Vous avez changé le accident vasculaire cérébral attribuer à La peinture. le drawRect: méthode itère maintenant sur tous les traits de la peinture et dessine chacun en utilisant draw_stroke, qui contient le code de dessin que vous avez écrit précédemment.

Vous devez également mettre à jour le contrôleur de vue pour qu'il contienne La peinture modèle. Au sommet de la PeintureContrôleur classe, ajouter attr_reader: peinture. Comme son nom l'indique, le viewDidLoad méthode du UIViewController classe-la super-classe de la PeintureContrôleur class-est appelé lorsque le contrôleur de vue a fini de charger sa vue. le viewDidLoad méthode est donc un bon endroit pour créer un La peinture par exemple et définir le La peinture attribut du PaintingView objet.

def viewDidLoad @painting = Painting.new painting_view.painting = fin de peinture

Comme toujours, n’oubliez pas d’ajouter des tests pour viewDidLoad à spec / controllers / painting_controller_spec.rb.

decrire "#viewDidLoad" do it "définit la peinture" do controller.painting.should.be.instance_of Peinture end it "définit l'attribut de peinture de la vue de peinture" do controller.painting_view.painting.should == controller.painting end end

5. Reconnaissance des gestes

Votre application sera assez ennuyeuse à moins que vous ne laissiez les gens dessiner à l'écran avec leurs doigts. Ajoutons cette fonctionnalité maintenant. Créer un fichier pour le StrokeGestureRecognizer classe avec ses spécifications en exécutant les commandes suivantes à partir de la ligne de commande.

appuyez sur app / views / stroke_gesture_recognizer.rb appuyez sur spec / views / stroke_gesture_recognizer_spec.rb

Ensuite, créez le squelette pour la classe.

classe StrokeGestureRecognizer < UIGestureRecognizer attr_reader :position end

le StrokeGestureRecognizer la classe étend la UIGestureRecognizer classe, qui gère la saisie tactile. Il a un position attribuer que le PeintureContrôleur la classe utilisera pour déterminer la position du doigt de l'utilisateur.

Il y a quatre méthodes que vous devez implémenter dans le StrokeGestureRecognizer classe, touchesBegan: withEvent:, touchesMoved: withEvent:, touchesEnded: withEvent:, et touchesCancelled: withEvent:. le touchesBegan: withEvent: La méthode est appelée lorsque l'utilisateur commence à toucher l'écran avec son doigt. le touchesMoved: withEvent: Cette méthode est appelée à plusieurs reprises lorsque l'utilisateur déplace son doigt et le touchesEnded: withEvent: Cette méthode est appelée lorsque l'utilisateur lève son doigt de l'écran. Finalement, le touchesCancelled: withEvent: La méthode est invoquée si le geste est annulé par l'utilisateur.

Votre reconnaissance de gestes doit faire deux choses pour chaque événement, mettre à jour le position attribuer et changer le Etat propriété.

classe 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

Les deux touchesEnded: withEvent: et touchesCancelled: withEvent: les méthodes définissent l'état sur UIGestureRecognizerStateEnded. En effet, peu importe que l'utilisateur soit interrompu, le dessin doit rester intact..

Afin de tester le StrokeGestureRecognizer classe, vous devez pouvoir créer une instance de UITouch. Malheureusement, il n'y a pas d'API disponible publiquement pour accomplir cela. Pour que cela fonctionne, nous allons utiliser la bibliothèque moqueuse Facon.

Ajouter gem 'motion-facon' à votre Gemfile et courir installation groupée. Puis ajouter nécessite "motion-facon" au dessous de nécessite une "couleur sucre" dans le rakefile du projet.

Ensuite, implémentez le StrokeGestureRecognizer spec.

describe StrokeGestureRecognizer étend Facon :: SpecHelpers avant 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] end décrivent" #touchesBegan: withEvent: "faire avant de faire @ stroke_gesture_recognizer.touchesBegan (@ touches1, withEvent: nil) end it "définit la position sur la position du geste" do @ stroke_gesture_recognizer.position.should == CGPoint.new (100, 200) end it "définit l'état de la reconnaissance de geste" do @ stroke_gesture_recognizer.state .should == UIGestureRecognizerStateBegan end end décrire "#touchesMoved: withEvent:" doit être précédé de @ stroke_gesture_recognizer.touchesBegan (@ touches1, avecEvénement: nil) définit le positionnement de la fonction * stroke_gesture_recognizer.touchesMoved (@ touches2, withEvent: nil). position du geste "do @ stroke_gesture_recognizer.pos ition.should == CGPoint.new (300, 400) end it "définit l’état de la reconnaissance de geste" do @ stroke_gesture_recognizer.state.should == UIGestureRecognizerStateChanged end décrivent "#touchesEnded: withEvent:" à faire avant: @stroke_gest_regognizer. touchesBegan (@ touches1, withEvent: nil) @ stroke_gesture_recognizer.touchesEnded (@ touches2, withEvent: nil) end it "définit la position sur la position du geste" do @ stroke_gesture_recognizer.position.should == CGPoint.new (300, 400) end it "définit l'état de la reconnaissance de geste" do @ stroke_gesture_recognizer.state.should == UIGestureRecognizerStateEnded end end décrit "#touchesCancelled: withEvent:" do before do_cut_gesture_recognizer.touchesBegan (@ touches1, withEvent: nil) @ touches2, withEvent: nil) end it "définit la position sur la position du geste" do @ stroke_gesture_recognizer.position.should == CGPoint.new (300, 400) end it "définit l'état de la reconnaissance de geste" do @stroke_gesture_r ecognizer.state.should == UIGestureRecognizerStateEnded end end end

extend Facon :: SpecHelpers rend plusieurs méthodes disponibles dans vos spécifications, y compris moquer. moquer est un moyen simple de créer des objets de test qui fonctionnent exactement comme vous le souhaitez. dans le avant bloquer au début des spécifications, vous vous moquez des instances de UITouch avec le locationInView: méthode qui retourne un point prédéfini.

Ensuite, ajoutez un stroke_gesture_changed méthode à la PeintureContrôleur classe. Cette méthode recevra une instance du StrokeGestureRecognizer classe chaque fois que le geste est mis à jour.

def stroke_gesture_changed (stroke_gesture_recognizer) si stroke_gesture_recognizer.state == UIGestureRecognizerStateBegan painting.start_stroke (stroke_gesture_recognizer.position, selected_color) else painting.continue_stroke (stroke_gesture_recognizer.position).

Lorsque l'état de reconnaissance du geste est UIGestureRecognizerStateBegan, cette méthode commence un nouveau coup dans le La peinture objet en utilisant le StrokeGestureRecognizerla position de et couleur_sélectionnée. Sinon, il continue le trait en cours.

Ajouter les spécifications pour cette méthode.

décrivez "#stroke_gesture_changed" avant de faire glisser (controller.painting_view,: points => [CGPoint.new (100, 100), CGPoint.new (150, 150), CGPoint.new (200, 200)]) le terminez " ajoute les points au trait "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) end it "définit la couleur du trait sur la couleur sélectionnée" do controller.painting.strokes.first .color.should == controller.selected_color end end

RubyMotion fournit plusieurs méthodes d’aide pour simuler une interaction utilisateur, notamment: traîne. En utilisant traîne, vous pouvez simuler l'interaction d'un utilisateur avec l'écran. le points l'option vous permet de fournir un tableau de points pour le glisser.

Si vous deviez exécuter les spécifications maintenant, elles échoueraient. C'est parce que vous devez ajouter la reconnaissance de geste au storyboard. Lancer Interface Builder en lançant bundle exec rake ib: ouvert. Du Bibliothèque d'objets, traîner un Objet dans votre scène, et changer sa classe à StrokeGestureRecognizer dans le Inspecteur d'identité sur la droite.

Contrôlez et faites glisser depuis le StrokeGestureRecognizer objecter à la PeintureContrôleur et choisissez le sélectionnez la couleur méthode du menu qui apparaît. Cela assurera la sélectionnez la couleur La méthode est appelée chaque fois que la reconnaissance de geste est déclenchée. Puis, contrôlez et faites glisser depuis le PaintingView objecter à la StrokeGestureRecognizer objet et sélectionnez gestureRecognizer dans le menu qui apparaît.

Ajouter une spécification pour la reconnaissance de geste au PeintureContrôleur spécifications dans le #painting_view décrire bloc.

décris "#painting_view" do it "est connecté dans le storyboard" do controller.painting_view.should.not.be.nil end it "a un identificateur de mouvements de traits" faire controller.painting_view.gestureRecognizers.length.should = shield == 1 controleur. painting_view.gestureRecognizers [0] .should.be.instance_of de StrokeGestureRecognizer end end

C'est tout. Avec ces modifications, votre application devrait maintenant permettre à une personne de dessiner à l'écran. Lancez votre application et amusez-vous.

6. Touches finales

Il reste quelques touches finales à ajouter avant la fin de votre candidature. Parce que votre application est immersive, la barre d'état est un peu gênante. Vous pouvez le supprimer en réglant la UIStatusBarHidden et UIViewControllerBasedStatusBarAppearance valeurs dans Info.plist de l'application. C'est facile à faire dans le RubyMotion installer bloquer à l'intérieur du Rakefile du projet.

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

Les icônes de l'application et les images de lancement sont incluses dans les fichiers source de ce didacticiel. Téléchargez les images et copiez-les sur le Ressources répertoire du projet. Ensuite, définissez l'icône de l'application dans la configuration de Rakefile. Vous devrez peut-être nettoyer la construction en exécutant bundle exec rake clean: tous afin de voir la nouvelle image de lancement.

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

Conclusion

C'est tout. Vous avez maintenant une application complète prête pour un million de téléchargements dans l'App Store. Vous pouvez afficher et télécharger le code source de cette application à partir de GitHub..

Même si votre application est terminée, vous pouvez en ajouter beaucoup plus. Vous pouvez ajouter des courbes entre les lignes, davantage de couleurs, différentes largeurs de ligne, enregistrer, annuler et rétablir, et tout ce que vous pouvez imaginer. Que ferez-vous pour améliorer votre application? Laissez-moi savoir dans les commentaires ci-dessous.