RubyMotion — это фреймворк, позволяющий создавать приложения для iOS в Ruby. Он дает вам все преимущества языка Ruby, но поскольку ваш код скомпилирован в машинный код, вы получаете всю необработанную производительность разработки в Objective-C. RubyMotion позволяет напрямую использовать iOS SDK, что означает, что у вас есть доступ ко всем новейшим функциям платформы. Вы можете включить код Objective-C в свой проект, а RubyMotion даже работает с CocoaPods .
В этом уроке вы создадите приложение для рисования с нуля. Я покажу вам, как включить Interface Builder в ваш рабочий процесс и как правильно протестировать ваше приложение. Если у вас нет опыта работы с iOS или Ruby, я бы порекомендовал вам побольше узнать о них. Tuts + Ruby для новичков и обучающих iOS SDK Development из руководств Scratch — отличное место для начала.
1. Настройка проекта
Прежде чем вы сможете начать кодирование, вам необходимо установить и настроить 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! Черный экран. Вы сделаете это более интересным через минуту.
2. Первая смена
Несмотря на то, что хорошо иметь работающее приложение, черный экран немного скучен. Давайте добавим немного цвета.
Как и родной 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
|
3. Тестирование
Ни одно приложение не является полным без солидного набора тестов. Тестирование позволяет вам быть уверенным, что ваш код работает, и позволяет вносить изменения, не беспокоясь о нарушении существующего кода.
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 спецификация (1 требование), 0 сбоев, 0 ошибок
Это говорит о том, что Бэкон выполнил один тест и не нашел никаких ошибок. В случае сбоя одной из ваших спецификаций вы увидите 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
|
Запустите их, чтобы убедиться, что все работает, прежде чем двигаться дальше.
4. Добавление пользовательского интерфейса
Существует несколько способов создания пользовательского интерфейса с использованием 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
чтобы убедиться, что они все еще проходят. Заметьте, как вам не пришлось менять ни одного из них? Хорошие спецификации проверяют поведение кода, а не его реализацию. Это означает, что вы должны иметь возможность изменить способ реализации своего кода, и ваши спецификации должны работать. Запустите приложение, чтобы протестировать новый пользовательский интерфейс.
5. Кнопки
То, что вы создали до сих пор, прекрасно, но разве не было бы хорошо, если бы ваше приложение действительно что-то делало? В этом разделе вы добавите элементы управления для переключения цвета кисти. Создайте два новых файла, контроллер и его спецификацию, выполнив следующие команды.
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 и организацию вашего приложения. Вы также добавите вид рисования и напишите код, который позволяет пользователю рисовать. Будьте на связи.