Как стать автором
Обновить

Разработка iOS приложений на Ruby

Время на прочтение10 мин
Количество просмотров28K
Автор оригинала: Mario Chavez
В последнее время, 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 действительно набирает обороты, хотя многие люди, с которыми я разговаривал, относятся к нему скептично. Да, это дорого, да сыро. Но если Вы пользуетесь Ruby в повседневной разработке, то должны понимать, с какой скоростью обычно развиваются проекты благодаря коммьюнити.

Я купил себе лицензию, и в ближайшем будущем собираюсь разобраться в вопросе несколько глубже. От себя могу добавить ряд интересных ссылок:



Получайте удовольствие от разработки, делайте хорошие приложения и до новых встреч. Спасибо за внимание!
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Пользуетесь ли Вы RubyMotion?
7.84% Да, нравится32
0.74% Да, не нравится3
13.97% Нет, дорого57
44.85% Нет, это не нужно183
32.6% Нет, но хотел бы133
Проголосовали 408 пользователей. Воздержались 164 пользователя.
Теги:
Хабы:
Всего голосов 41: ↑30 и ↓11+19
Комментарии37

Публикации

Истории

Работа

Программист Ruby
4 вакансии
Ruby on Rails
4 вакансии
Swift разработчик
15 вакансий
iOS разработчик
15 вакансий

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань