В последнее время, RubyMotion становится все более популярным инструментом для разработки под iOS. После близкого знакомства с ним становится понятно, почему Ruby гораздо более привлекательный для этих целей язык, чем Objective-C.
Вступление
Это Руби или нет?
RubyMotion — это набор инструментов для разработки под iOS на языке Ruby. Он состоит из среды выполнения, которая реализует функционал Ruby внутри iOS. Хотя сфера применения такого руби кода и отличается от CRuby, реализация построена на базе спецификаций Ruby 1.9.
Знание Руби не гарантирует, что Вы сможете писать iOS приложения, но совершенно точно не будет лишним. Для работы с RubyMotion гораздо важнее быть знакомым с Objective-C и Foundation Framework API. По сути, RubyMotion «рубифицирует» Objective-C.
Работа над проектом
Чтобы работать с RubyMotion, не обязательно использовать XCode, можно пользоваться своим любимым редактором. Вместе с RubyMotion поставляется консольный инструмент (motion
), который поможет Вам создать и настроить папку с проектом.
В нем также есть набор rake тасков, которые помогут Вам собрать проект и запустить его в симуляторе iOS. Он умеет распознавать ресурсы, такие как картинки или pfile, а также подключать и использовать файлы .xib, .storyboard и .xcdatamodeld.
С чего начать?
Если не терпится начать работу, придется купить лицензию (на данный момент она стоит $199, на сайте же указана сумма в рублях по кривому курсу), т.к. это проприетарный софт. После чего не будет лишним пройти Getting Started Guide на сайте RubyMotion.
Также полезным будет вводный скринкаст с сайта Motion Casts. И еще существует 50-минутный скринкаст от Pragmatic Studio, который расскажет Вам как создать простенькое приложение. Этот туториал тоже неплох.
Простое приложение
Мы разобрались с тем, что из себя представляет RubyMotion и чем он может оказаться полезен, так что давайте поработаем над нашим первым приложением.
Так как я не смог найти никакого готового более-менее сложного примера, предлагаю Вам собственную поделку. Исходники прилагаются.
Наше приложение
Давайте притворимся, что мы делаем аппликуху для конференции. Для этого я буду использовать расписание MagmaRails 2012. Количество отображаемой информации ограничим до спикеров и их выступлений, разбитых по дням.
Наш интерфейс
Все ресурсы на сайте для разработчиков RubyMotion показывают как строить интерфейс при помощи кода, существуют даже некоторые DSL специально созданные для сборки пользовательских интерфейсов под iOS. Но вместо этого мы будем использовать XCode Storyboard, что поможет нам построить модель навигации по приложению.
Сториборды привязаны к XCode, и я ранее обещал, что мы сможем разрабатывать приложения без необходимости в нем, но в этом случае мы воспользуемся им просто потому, что Сториборды являются очень удобным средством визуализации.
Готовим проект
Первым шагом, который мы сделаем, станет создание новенького XCode проекта — подразумевается, что для этого Вы будете использовать XCode 4.5.x — на основе Master-Detail Application, так что дайте ему название, а также удостоверьтесь, что в поле Devices выбран iPhone и установлены галочки «Use StoryBoard» и «Use Core Data».
Теперь давайте создадим проект в RubyMotion и назовем его «conference»:
$ motion create conference
Внутри структуры нашего RubyMotion проекта есть папка resources, в нее нужно скопировать файл MainStoryboard.storyboard
, который был создан XCode. Скопируйте файл и затем удалите ссылку на него из XCode.
Так как мы все еще хотим иметь возможность редактировать этот файл в XCode, нужно перетащить его из нашей папочки в интерфейс XCode. Нас спросят, как именно мы хотим это сделать. Удостоверьтесь, что галочка Copy items into destination group's folder (if needed)
снята, а затем нажмите на Finish. Таким образом мы создали симлинк к нашему файлу, и все изменения внутри XCode будут автоматически отражены в проекте.
Теперь давайте настроим Bundler:
$ bundle init
Отредактируйте Gemfile следующим образом:
source :rubygems
gem 'xcodeproj', '~> 0.3.0'
gem 'ib'
gem 'rake'
Запустите Bundler:
$ bundle
Гемы xcodeproj и ib помогут соединить аутлеты из Ruby со Сторибордами из XCode, чем мы позже воспользуемся.
Теперь подправим Rakefile и добавим после строчки require "motion/project"
код, который подключит Bundler к проекту:
require 'rubygems'
require 'ib'
require 'bundler'
Bundler.require
Найдите app_delegate.rb
и откройте в своем любимом редакторе. Внутри него объявлен метод application
, который является входной точкой нашего приложения. Сейчас мы скажем RubyMotion загрузить MainStoryboard.storyboard
:
def application(application, didFinishLaunchingWithOptions:launchOptions)
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
@storyboard ||= UIStoryboard.storyboardWithName('MainStoryboard', bundle:NSBundle.mainBundle)
@window.rootViewController = @storyboard.instantiateInitialViewController
@window.rootViewController.wantsFullScreenLayout = true
@window.makeKeyAndVisible
true
end
Мы инициализируем фрейм экрана, подгружаем Сториборд, устанавливаем главный контроллер Сториборда в качестве нашего rootViewController
и делаем наш экран видимым.
Для проверки, запустите:
$ rake
Эта команда скомпилирует наш проект в iOS приложение, загрузит его в симулятор и запустит. Если все прошло хорошо, то Вы увидите пустую сетку. Чтобы закрыть приложение, наберите quit
в консоли.
Создаем Сториборд
Возвращаясь к XCode, давайте откроем файл MainStoryboard.storyboard
.
Выберите Master View Controller
и даблкликните по заголовку, чтобы изменить его на MagmaRails
. Теперь выберите Table View
и поменяйте значение свойства Content
с Dynamic
на Static
. В табличке должно теперь быть три строки ячеек. Нужно выбрать первую и сделать две ее копии. Поменяйте заголовок каждой из них — двойным кликом — на «Day One», «Day Two», «Day Three», «Speakers» и «Venue». Кликните на переход, соединяющий Master View Controller и Detail View Controller, чтобы удалить его.
Нам нужен еще один View Controller, чтобы отображать конференции по дням, так что давайте перетянем его из Objects Library прямо на холст. Перетяните table view в этот новый контроллер, а table-view cell в table view.
Используя identity inspector, переименуйте контроллер в TalksViewController
. В инспекторе атрибутов поменяйте идентификатор нашей новой ячейки на Talk, а из identity inspector поменяйте класс ячейки на TalkViewCell. Также, убедитесь, что стиль нашей ячейки кастомный и поменяйте ее высоту на 115.
Добавьте в ячейку 3 лейбла и картинку, которые будут отображать необходимую нам информацию. Как только закончите, перетяните курсор с зажатой клавишей ctrl
с ячейки Day One на Master View Controller к новому TalksViewController, чтобы создать переход. В качестве типа переход нужно выбрать push
, а в качестве имени — DayOne
. То же самое сделайте для ячеек со вторым и третьим днем.
Core Data models and application seed
Прежде, чем мы продолжим, давайте создадим модели Core Data, а также сид базовой информации.
Когда мы создавали проект в XCode, у опции Use Core Data стояла галочка. Это значит, что вместе с нашим проектом был создан файл .xcdatamodeld
. Найдите этот файл в XCode и кликните по нему правой кнопкой. Перейдите к нему с помощью Show File in Finder, переместите его в папку resources нашей аппликухи и удалите ссылку на файл в XCode, после чего перетяните его обратно, но уже из нашей папки.
В Xcode откройте Core Data Models и добавьте две новых сущности, Talk и Presenter. Для Talk добавьте атрибуты так, как показано на изображении, и убедитесь с помощью Data Model Inspector, что Class называется Talk. То же самое повторите для Presenter.
Сохраните файл. Теперь мы готовы создавать Модели внутри RubyMotion.
Давайте подключим два гема. Первый — motion-cocoapods — позволит нам управлять зависимостями через CocoaPods, менеджер зависимостей для Objective-C. Да, это значит, что даже если мы пишем код на Руби, мы все еще можем свободно использовать библиотеки на Objective-C.
Библиотека, которую мы будем использовать, называется MagicalRecord. Она позволит нам легко работать с Core Data.
Второй гем называется motion_support. Он предоставляет нам функционал, схожий с ActionSupport в Ruby on Rails (склонения и некоторые расширения ядра), но имеющий свою специфику. Добавьте эти гемы в Gemfile и запустите Bundler.
gem 'motion-cocoapods'
gem 'motion_support'
Чтобы оба гема были доступны, откройте свой Rackfile и добавьте в него две строки сразу под упоминанием rubygems.
require 'motion-cocoapods'
require 'motion_support/all'
Еще нам нужно сообщить RubyMotion, что мы хотим использовать Core Data framework и подключить MagicalRecord из cocoapods. Для этих целей мы заставим RubyMotion подгружать файлы в app/lib
раньше остальных, так что давайте подправим app.files.
Добавьте в конец блока Motion::Project::App.setup:
app.frameworks += %w(CoreData)
app.files.unshift Dir.glob(File.join(app.project_dir, 'app/lib/**/*.rb'))
app.pods do
pod 'MagicalRecord'
end
После этих изменений нужно установить motion-cocoapods:
$ pod setup
$ rake UPDATE=1
Эти две строки настроят cocoapods и установят библиотеки Objective-C, которые мы указали в Rakefile.
После всего этого, можно начать пользоваться Core Data.
Нужно создать по классу для каждой модели, которую мы описали в диграмме Core Data. Создаем директорию model внутри app, а в ней файлы Talk.rb
и Presenter.rb
:
class Talk < NSManagedObject
end
class Presenter < NSManagedObject
end
Для того, чтобы наши модели вели себя как сущности Core Data, нужно объявить пару методов. Важно заметить, что нам не нужно объявлять никаких свойств для хранения данных и связей в модели. Эти методы были объявлены за нас во время настройки диаграммы Core Data.
Нашим моделям понадобятся методы, чтобы наполнять себя данными из хешей, а также парочка файндеров. Для этого нам пригодится MagicalRecord
.
Также, внутри файла app_delegate.rb
нам понадобится метод для наполнения базы в первый раз. Этот метод будет вызван при запуске приложения. Seed-данные хранятся в plist файле, который трансформируется в хеш, а затем используются для наполнения наших моделей и сохранения их в БД.
def seedDatabase
MagicalRecord.setupCoreDataStackWithStoreNamed('database.sqlite')
if Talk.allTalks.size == 0
#https://github.com/Bodacious/PListReadWrite
PListRW.copyPlistFileFromBundle(:seed)
seed = PListRW.plistObject(:seed, Hash)
presenters = []
seed['presenters'].each do |presenter_attrs|
presenters << Presenter.createWithHash(presenter_attrs)
end
seed['talks'].each do |talk_attrs|
talk = Talk.createWithHash(talk_attrs)
presenter = presenters.select{|p| p.presenterId == talk.presenterId }.first
talk.presenter = presenter
talk.save
presenter.addTalk(talk)
presenter.save
end
end
end
Работаем над контроллерами
Ранее мы связали тремя переходами MasterViewController и TalksViewController, по одному на каждый день конференции.
Так как все 3 перехода указывают на один и тот же контроллер, нам нужно использовать идентификаторы, чтобы TalksViewController понимал, о каком дне идет речь в данный момент.
Создайте файл masterviewcontroller.rb
внутри app/controllers
и добавьте в нем метод prepareForSegue
:
class MasterViewController < UITableViewController
def prepareForSegue(segue, sender: sender)
case segue.identifier
when 'DayOne', 'DayTwo', 'DayThree'
segue.destinationViewController.setFilter segue.identifier
end
end
end
Свойство destinationViewController
ссылается на целевой контроллер, в нашем случае — TalksViewController
, и подразумевается, что у нас есть критерий, по которому можно фильтровать выступления.
Поэтому давайте создадим наш TalksViewController
. Этот контроллер будет делегатором TableUIView, следовательно нам нужно добавить еще два метода:
class TalksViewController < UIViewController
attr_accessor :filter
attr_accessor :dataSource
def tableView(tv, numberOfRowsInSection:section)
self.dataSource.count
end
def tableView(tv, cellForRowAtIndexPath:indexPath)
@reuseIdentifier ||= 'TalkCell'
cell = tv.dequeueReusableCellWithIdentifier(@reuseIdentifier) || begin
TalkCell.alloc.initWithStyle(UITableViewCellStyleDefault, reuseIdentifier:@reuseIdentifier)
end
talk = self.dataSource[indexPath.row]
# We will come back to this a bit later
end
def viewDidLoad
day = case filter
when 'DayOne' then 1
when 'DayTwo' then 2
when 'DayThree' then 3
end
self.dataSource = Talk.talksByDay(day)
end
end
В методе viewDidLoad
мы на основании значения фильтра, переданного в prepareSegue
, выбираем из базы подходящие доклады и сохраняем их в свойстве dataSource
.
Метод tableView(tv, numberOfRowsInSection:section)
должен знать, сколько строк мы хотим отобразить в таблице, и эту информацию мы просто узнаем у datasource
.
tableView(tv, cellForRowAtIndexPath:indexPath)
несколько запутаннее, этот метод вызывается каждый раз, когда строка отображается на экране, и нам необходимо передавать сюда объект ячейки со всеми соответствующими данными. Если у нас есть десятки или сотни записей, это может привести к нежелательному расходованию памяти. Поэтому фреймворк хранит пул ранее инстанциированных ячеек, и вместо того, чтобы создавать каждый раз новую ячейку, он берет старую из пула и использует ее повторно. Благодаря этому количество объектов в памяти всегда невелико. Он использует ячейки в соответствии с идентификатором, и если доступных на данный момент нет, то создает новую.
В случае с нашим Сторибордом, мы создали кастомную ячейку для информации о докладе, а также добавили TalkCell в качестве идентификатора, у которого есть несколько лейблов и картинка. Так что давайте создадим новый класс TalkViewCell
внутри /app/cells/talkviewcell.rb
:
class TalkViewCell < UITableViewCell
end
Следующим шагом мы должны создать IB Аутлеты, которые позволят нам соединить наш код с лейблами и картинками Сториборда, в чем нам поможет гем ib:
class TalkViewCell < UITableViewCell
extend IB
outlet :talk, UILabel
outlet :speaker, UILabel
outlet :day, UILabel
outlet :picture, UIImageView
end
Как только наши аутлеты окажутся на своих местах, их нужно соединить на Сториборде. Если у Вас открыт XCode, то закройте его и запустите следующую команду (предоставленную ib):
$ rake ib:open
Это откроет поддельный ib XCode проект, который можно тут же закрыть. Откройте Сториборд в XCode и выберите TalkViewCell, затем кликните на connections inspector. Вы должны увидеть все объявленные нами аутлеты, соединенные друг с другом
Перетащите аутлет на холст, потянув за кружочек, расположенный справа от его названия, и сохраните Сториборд.
Откройте класс TalkViewCell снова и добавьте следующий метод, который будет использоваться для установки содержимого ячейки:
def setupTalk(talk)
self.talk.text = talk.title
self.speaker.text = talk.presenter.name
self.picture.image = UIImage.imageNamed(talk.presenter.picture)
self.day.text = "Day #{talk.day}, #{talk.time}"
end
Теперь вернитесь назад в TalksViewController, и замените комментарий "# We will come back to this a bit later"
внизу метода tableView(tv, cellForRowAtIndexPath:indexPath)
на следующий код:
cell.setupTalk(talk)
cell
Запускаем симулятор:
rake
Должно получиться как на скриншоте.
При нажатии на Day Two, Вы получите список всех выступлений за этот день:
В заключение
Вы читаете эти строки, а это значит, что у Вас получилось собрать приложение под iPhone. Оно использует:
- Bundler и гемы, созданные специально для RubyMotion
- Смесь из Cocoapods и Objective-C библиотек
- Диаграммы XCode Core Data и локальную базу данных sqlite
- Конструктор интерфейсов XCode совместно с кодом на руби
Если Вам хочется поиграть с кодом из этого поста, то добро пожаловать в репозиторий.
RubyMotion делает разработку под iOS удовольствием.
Дополнительная информация
- RubyMotion API Reference
- MacRuby на iOS — обзор RubyMotion
- RubyMotion для быстрой Клиент/Серверной разработки
От переводчика
Если Вы начали проходить туториал и нашли какие-либо неточности или ошибки, буду благодарен, если Вы напишите мне об этом в личку.
RubyMotion действительно набирает обороты, хотя многие люди, с которыми я разговаривал, относятся к нему скептично. Да, это дорого, да сыро. Но если Вы пользуетесь Ruby в повседневной разработке, то должны понимать, с какой скоростью обычно развиваются проекты благодаря коммьюнити.
Я купил себе лицензию, и в ближайшем будущем собираюсь разобраться в вопросе несколько глубже. От себя могу добавить ряд интересных ссылок:
- Выпуск подкаста Giant Robots Smashing into the Other Giant Robots с iOS разработчиками компании thoughtbot
- #inspect 2013 — Первая конференция по RubyMotion. Будет проходить в Брюсселе 25-29 апреля.
- Хабраперевод «RubyMotion: нативные iOS приложения на Ruby». Рубка в комментах обязательна к прочтению.
- Блог RubyMotion.
- Твиттер RubyMotion — вообще сокровищница.
- Курс Codeschool «Try iOS». Отличное начало, если Вы ни разу не общались с Objective-C.
- Поддержка RubyMotion в IDE RubyMine.
- Подоспевшая книжка, с пылу, с жару.
- Довольно полный источник остальной информации по вопросу
Получайте удовольствие от разработки, делайте хорошие приложения и до новых встреч. Спасибо за внимание!
Автор: shebanoff