Статьи

Давайте напишем приложение RubyMotion: часть 1

Конечный продукт
Что вы будете создавать

RubyMotion — это фреймворк, позволяющий создавать приложения для iOS в Ruby. Он дает вам все преимущества языка Ruby, но поскольку ваш код скомпилирован в машинный код, вы получаете всю необработанную производительность разработки в Objective-C. RubyMotion позволяет напрямую использовать iOS SDK, что означает, что у вас есть доступ ко всем новейшим функциям платформы. Вы можете включить код Objective-C в свой проект, а RubyMotion даже работает с CocoaPods .

В этом уроке вы создадите приложение для рисования с нуля. Я покажу вам, как включить Interface Builder в ваш рабочий процесс и как правильно протестировать ваше приложение. Если у вас нет опыта работы с iOS или Ruby, я бы порекомендовал вам побольше узнать о них. Tuts + Ruby для новичков и обучающих iOS SDK Development из руководств Scratch — отличное место для начала.

Прежде чем вы сможете начать кодирование, вам необходимо установить и настроить RubyMotion. Подробнее о том, как это сделать, читайте в разделе « Предварительные условия » руководства по началу работы с RubyMotion.

После этого откройте свой терминал и создайте новый проект RubyMotion, запустив:

1
2
motion create paint
cd paint

Это создает каталог рисования и несколько файлов:

  • .gitignore: этот файл сообщает Git, какие файлы игнорировать. Поскольку RubyMotion генерирует файлы сборки при запуске, этот файл полезен для того, чтобы сохранить созданные вами файлы сборки из-под контроля исходного кода.
  • Gemfile : этот файл содержит зависимости вашего приложения.
  • Rakefile: RubyMotion использует Rake для сборки и запуска вашего приложения. Rakefile настраивает ваше приложение и загружает его зависимости. Вы можете увидеть все задачи, доступные вашему приложению, запустив rake -T из командной строки.
  • app / app_delegate.rb: делегат приложения является точкой входа в ваше приложение. Когда iOS заканчивает загрузку вашего приложения в память, делегат приложения получает уведомление.

RubyMotion также генерирует файл spec / main_spec.rb . Я покажу вам, как протестировать ваше приложение чуть позже в этом уроке. Сейчас вы можете удалить этот файл, запустив в командной строке rm spec/main_spec.rb .

Установите зависимости вашего приложения, запустив bundle install а затем bundle exec rake чтобы запустить приложение.

Woohoo! Черный экран. Вы сделаете это более интересным через минуту.

Несмотря на то, что хорошо иметь работающее приложение, черный экран немного скучен. Давайте добавим немного цвета.

Как и родной iOS SDK, RubyMotion не заставляет вас организовывать файлы каким-либо определенным образом. Тем не менее, полезно создать несколько папок в каталоге приложения, чтобы сохранить ваш проект организованным. Выполните следующие команды из командной строки, чтобы создать каталог для ваших моделей, представлений и контроллеров.

1
2
3
mkdir app/models
mkdir app/views
mkdir app/controllers

Далее, загляните в файл app / app_delegate.rb :

1
2
3
4
5
class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)
    true
  end
end

Если вы знакомы с разработкой для iOS, вы заметите, что этот метод относится к протоколу UIApplicationDelegate , который обеспечивает несколько хуков в жизненном цикле приложения. Обратите внимание, что класс AppDelegate не объявляет, что он реализует протокол UIApplicationDelegate . Ruby использует утку, так как не поддерживает протоколы. Это означает, что ему все равно, говорит ли ваш класс, что он реализует протокол, его заботит только то, что он реализует правильные методы.

Определение application:didFinishLaunchingWithOptions: метод внутри класса AppDelegate может выглядеть немного странно. В Objective-C этот метод был бы написан так:

1
— (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;

Поскольку имена методов Objective-C можно разбить на несколько частей, Ruby реализует их уникальным способом. Первая часть application:didFinishLaunchingWithOptions: имя метода в MRI. Остальная часть сигнатуры метода написана как ключевые аргументы. В RubyMotion application:didFinishLaunchingWithOptions: написано так:

1
2
def application(application, didFinishLaunchingWithOptions:launchOptions)
end

Давайте реализуем этот метод.

01
02
03
04
05
06
07
08
09
10
class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)
 
    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
    @window.makeKeyAndVisible
    @window.rootViewController = UIViewController.alloc.initWithNibName(nil, bundle: nil)
 
    true
  end
end

Первые две строки application:didFinishLaunchingWithOptions: метод создает новый объект окна и делает его ключевым окном приложения. Почему @window является переменной экземпляра? RubyMotion будет собирать окно, если мы его не сохраним. Последняя строка метода устанавливает корневой контроллер окна в новый пустой контроллер представления.

Запустите приложение, чтобы все работало.

Хм. Приложение работает, но экран все еще черный. Как вы знаете, ваш код работает? Вы можете сделать быструю проверку application:didFinishLaunchingWithOptions: , добавив следующее в application:didFinishLaunchingWithOptions: перед true . Обязательно удалите это, прежде чем двигаться дальше.

1
@window.rootViewController.view.backgroundColor = UIColor.yellowColor

Ни одно приложение не является полным без солидного набора тестов. Тестирование позволяет вам быть уверенным, что ваш код работает, и позволяет вносить изменения, не беспокоясь о нарушении существующего кода.

RubyMotion поставляется с портом библиотеки тестирования Bacon . Если вы знакомы с Rspec , Бэкон будет чувствовать себя очень знакомым.

Чтобы начать, отразите структуру каталога приложения в каталоге spec , выполнив следующие команды из командной строки.

1
2
3
mkdir spec/models
mkdir spec/views
mkdir spec/controllers

Затем создайте файл спецификации AppDelegate по адресу spec / app_delegate_spec.rb . По соглашению, исходные файлы являются зеркалами в каталоге spec, и к их имени добавляется _spec .

Начните этот класс с определения блока describe который сообщает читателю, что тестирует ваш файл.

1
2
describe AppDelegate do
end

Затем добавьте второй блок describe в первый, чтобы показать, что вы хотите протестировать application:didFinishLaunchingWithOptions: method.

1
2
3
4
describe AppDelegate do
  describe «#application:didFinishLaunchingWithOptions:» do
  end
end

Вы заметили # в начале подписи метода? По соглашению методы экземпляра начинаются с хеша, а методы класса начинаются с точки.

Затем добавьте спецификацию, используя блок it .

1
2
3
4
5
6
7
describe AppDelegate do
  describe «#application:didFinishLaunchingWithOptions:» do
    it «creates the window» do
      UIApplication.sharedApplication.windows.size.should == 1
    end
  end
end

Одна из лучших особенностей Bacon — и других тестовых сред BDD — это то, что спецификации очень четко показывают, что они тестируют. В этом случае вы убедитесь, что application:didFinishLaunchingWithOptions: method создает окно.

Ваша спецификация не должна вызывать application:didFinishLaunchingWithOptions: метод напрямую. Он вызывается автоматически, когда Bacon запускает ваше приложение.

Запустите спецификации вашего приложения, запустив bundle exec rake spec из командной строки. Вы должны увидеть результат примерно так:

Это говорит о том, что Бэкон выполнил один тест и не нашел никаких ошибок. В случае сбоя одной из ваших спецификаций вы увидите 1 failure и Бэкон распечатает подробное описание проблемы.

Вышеуказанное работает, но вы будете использовать UIApplication.sharedApplication для всех своих спецификаций. Разве не было бы неплохо, если бы вы могли взять этот объект один раз и использовать его во всех спецификациях? Вы можете с блоком before .

01
02
03
04
05
06
07
08
09
10
11
12
13
describe AppDelegate do
 
  describe «#application:didFinishLaunchingWithOptions:» do
 
    before do
      @application = UIApplication.sharedApplication
    end
 
    it «creates the window» do
      @application.windows.size.should == 1
    end
  end
end

Теперь вы можете легко добавить остальные спецификации приложения.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
describe AppDelegate do
 
  describe «#application:didFinishLaunchingWithOptions:» do
 
    before do
      @application = UIApplication.sharedApplication
    end
 
    it «creates the window» do
      @application.windows.size.should == 1
    end
 
    it «makes the window key» do
      @application.windows.first.isKeyWindow.should.be.true
    end
 
    it «sets the root view controller» do
      @application.windows.first.rootViewController.should.be.instance_of UIViewController
    end
  end
end

Запустите их, чтобы убедиться, что все работает, прежде чем двигаться дальше.

Существует несколько способов создания пользовательского интерфейса с использованием RubyMotion. Мой личный фаворит — использовать Interface Builder с драгоценным камнем IB . Откройте свой Gemfile и добавьте драгоценный камень IB.

1
2
3
4
source ‘https://rubygems.org’
 
gem ‘rake’
gem ‘ib’

Запустите bundle install из командной строки, чтобы установить гем. Если вы используете Git, добавьте ib.xcodeproj в ваш файл .gitignore .

Interface Builder является частью XCode. Запустите Interface Builder, запустив bundle exec rake ib:open . Это создает проект Xcode с учетом вашего приложения. Создайте новые файлы пользовательского интерфейса, выбрав New> File … в меню File Xcode и выбрав Storyboard из категории User Interface слева. Нажмите Next дважды, чтобы завершить этот шаг.

Сохраните раскадровку в каталоге ресурсов как main.storyboard . Откройте раскадровку в XCode и перетащите в нее новый View Controller из библиотеки объектов справа. Установите для поля Storyboard ID контроллера значение PaintingController .

Перетащите метку в представление контроллера представления из библиотеки объектов справа и установите для нее текст Hello .

Затем откройте приложение / app_delegate и замените последнюю строку application:didFinishLaunchingWithOptions: на следующее:

1
2
storyboard = UIStoryboard.storyboardWithName(«main», bundle: nil)
@window.rootViewController = storyboard.instantiateInitialViewController

Затем снова запустите тесты вашего приложения с помощью bundle exec rake spec чтобы убедиться, что они все еще проходят. Заметьте, как вам не пришлось менять ни одного из них? Хорошие спецификации проверяют поведение кода, а не его реализацию. Это означает, что вы должны иметь возможность изменить способ реализации своего кода, и ваши спецификации должны работать. Запустите приложение, чтобы протестировать новый пользовательский интерфейс.

То, что вы создали до сих пор, прекрасно, но разве не было бы хорошо, если бы ваше приложение действительно что-то делало? В этом разделе вы добавите элементы управления для переключения цвета кисти. Создайте два новых файла, контроллер и его спецификацию, выполнив следующие команды.

1
2
touch app/controllers/painting_controller.rb
touch spec/controllers/painting_controller_spec.rb

Реализуйте скелет PaintingController вместе с его спецификацией.

1
2
class PaintingController < UIViewController
end
1
2
3
describe PaintingController do
  tests PaintingController, :storyboard => ‘main’, :id => ‘PaintingController’
end

RubyMotion обрабатывает спецификации контроллера особым образом. tests PaintingController, :storyboard => 'main', :id => 'PaintingController' Вы можете использовать переменную controller чтобы проверить это.

Далее вам нужно добавить розетки в ваш контроллер. Они позволяют вам подключать объекты к вашему контроллеру в Интерфейсном Разработчике.

01
02
03
04
05
06
07
08
09
10
11
12
class PaintingController < UIViewController
  extend IB
 
  outlet :black_button
  outlet :purple_button
  outlet :green_button
  outlet :blue_button
  outlet :white_button
 
  def select_color(sender)
  end
end

extend IB добавляет несколько методов для вашего контроллера, в том числе outlet . Вы добавили пять розеток, по одной на каждую кнопку.

Изображения для кнопок включены в исходные файлы этого урока. Загрузите изображения и скопируйте их в каталог ресурсов . Вам нужно перегенерировать ваш проект Xcode, чтобы Интерфейсный Разработчик мог забрать сделанные нами изменения. Самый простой способ сделать это — закрыть Xcode и запустить bundle exec rake ib:open , который снова откроет проект.

Выберите контроллер представления и измените его класс на PaintingController .

Откройте spec / app_delegate_spec.rb и измените последнюю спецификацию, чтобы проверить класс PaintingController .

1
2
3
it «sets the root view controller» do
  @application.windows.first.rootViewController.should.be.instance_of PaintingController
end

Добавьте пять кнопок в представление контроллера представления, перетаскивая объекты Button на представление из библиотеки объектов справа.

Эти кнопки немного скучно. Выберите первую кнопку, измените ее тип на « Custom в Инспекторе атрибутов справа и удалите ее заголовок. Убедитесь, что в раскрывающемся меню « Конфигурация состояния» выбрано состояние « Default и установите фоновое изображение в button_black.png . Установите свойство Tint кнопки прозрачным.

Установите раскрывающееся меню « Конфигурация состояния» на « Selected и измените фоновое изображение на button_black_selected.png .

В инспекторе размера измените ширину и высоту кнопки на 50 .

Повторите этот процесс для других кнопок.

Следующим шагом является подключение кнопок к выходам контроллера представления, которые мы объявили ранее. Удерживая нажатой клавишу « Control» на клавиатуре, перетащите курсор с контроллера на первую кнопку. Когда вы отпустите кнопку мыши, появится меню. Выберите black_button из меню. Затем, удерживая нажатой клавишу « Control», перетащите select_color с кнопки на контроллер представления и выберите метод select_color из всплывающего меню. Повторите эти два шага для других кнопок.

Наконец, выберите первую кнопку и установите флажок « Выбрано» в разделе « Управление» в Инспекторе атрибутов .

Сейчас самое время добавить несколько полезных спецификаций в spec / painting_controller_spec.rb .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
describe PaintingController do
  tests PaintingController, :storyboard => ‘main’, :id => ‘PaintingController’
 
  describe «#black_button» do
    it «is connected in the storyboard» do
      controller.black_button.should.not.be.nil
    end
  end
 
  describe «#purple_button» do
    it «is connected in the storyboard» do
      controller.purple_button.should.not.be.nil
    end
  end
 
  describe «#green_button» do
    it «is connected in the storyboard» do
      controller.green_button.should.not.be.nil
    end
  end
 
  describe «#blue_button» do
    it «is connected in the storyboard» do
      controller.blue_button.should.not.be.nil
    end
  end
 
  describe «#white_button» do
    it «is connected in the storyboard» do
      controller.white_button.should.not.be.nil
    end
  end
end

Эти спецификации обеспечивают правильное подключение розеток в Интерфейсном Разработчике. Как всегда, рекомендуется запустить их перед тем, как убедиться, что все они прошли.

Далее вы реализуете метод select_color в PaintingController . Когда вызывается этот метод, выбранная кнопка выбирается, а ранее выбранная кнопка отменяется.

1
2
3
4
5
6
7
8
def select_color(sender)
 
  [ black_button, purple_button, green_button, blue_button, white_button ].each do |button|
    button.selected = false
  end
 
  sender.selected = true
end

Добавьте спецификации в spec / controllers / painting_controller_spec.rb .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
describe «#select_color» do
 
  before do
    controller.select_color(controller.green_button)
  end
 
  it «deselects the other colors» do
    controller.black_button.state.should == UIControlStateNormal
    controller.purple_button.state.should == UIControlStateNormal
    controller.blue_button.state.should == UIControlStateNormal
    controller.white_button.state.should == UIControlStateNormal
  end
 
  it «selects the color» do
    controller.green_button.state.should == UIControlStateSelected
  end
end

Запустите приложение и убедитесь, что кнопка выбора работает. Когда вы нажимаете на кнопку, она должна увеличиваться в размере. Хотя это круто, вы действительно хотите, чтобы цвет был выбран при нажатии кнопки. Это легко сделать с помощью нескольких дополнений.

Sugarcube — это набор iOS-расширений для RubyMotion, которые упрощают выполнение нескольких задач, таких как создание цветов. Добавьте gem 'sugarcube' в ваш Gemfile и запустите пакетную bundle install . Затем добавьте require "sugarcube-color" в ваш Rakefile выше Motion::Project::App.setup .

Драгоценный камень позволяет легко создавать цвета, используя их шестнадцатеричный код. В классе PaintingController добавьте следующий фрагмент кода ниже объявления торговых точек:

1
2
3
4
5
6
7
COLORS = [
  «#333333».uicolor,
  «#7059ac».uicolor,
  «#196e76».uicolor,
  «#80a9cc».uicolor,
  «#fafafa».uicolor
]

Затем select_color рефакторинг массива кнопок в select_color в приватный вспомогательный метод:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
def select_color(sender)
 
  buttons.each do |button|
    button.selected = false
  end
 
  sender.selected = true
  @color = COLORS[sender.tag]
end
 
private
 
def buttons
  [ black_button, purple_button, green_button, blue_button, white_button ]
end

Наконец, добавьте новый метод ниже select_color который возвращает выбранный цвет.

1
2
3
def selected_color
  COLORS[buttons.find_index { |button|
end

Этот метод захватывает индекс выбранной кнопки и выбирает цвет, соответствующий ей. Конечно, этот метод не будет полным без тестов.

01
02
03
04
05
06
07
08
09
10
describe «#selected_color» do
 
  before do
    controller.select_color(controller.green_button)
  end
 
  it «returns the correct color» do
    controller.selected_color.should == PaintingController::COLORS[2]
  end
end

Запустите приложение еще раз, чтобы убедиться, что все работает как положено.

Вы рассмотрели много вопросов в этом уроке. Вы узнали, как настроить и запустить приложение RubyMotion, вы работали с Interface Builder и создали пользовательский интерфейс.

Во второй части этого учебника вы углубитесь в паттерн Model-View-Controller на iOS и организацию вашего приложения. Вы также добавите вид рисования и напишите код, который позволяет пользователю рисовать. Будьте на связи.