Pull to refresh

Лёгкий способ писать iOS приложения на вебе

Reading time 9 min
Views 84K
Всем привет. Не так давно Габриель подарил нам игру 2048. Это тот самый удивительный случай, когда клон игры становится более популярный, чем оригинал. Не малая часть успеха Габриеля — открытый код и, вуаля, MIT лицензия. Набрав чуть больше 20к очков, захотелось поделиться результатом с друзьями, кроме как сделать скриншот не получилось. Глянул аппстор на наличие клона с геймцентром или чем-то подобным — пусто. И тут подумал, почему бы нет?
Забегая вперёд, на создание приложения и всех «ништяков» к нему ушло 4 дня. На выходе универсальный код, который от части работает и в вебе и легко портируется на иос/андроид. Однако, давайте по порядку.



Как вы уже поняли, речь пойдёт о разработке нативных приложений на веб технологиях. Для тех, кто в танке, работает всё достаточно просто. Создаётся проект с нативными SDK, цель которого — разместить на весь экран WebView и открыть конкретный html в нём. Т.е. получается, что всё приложение сводится к браузеру с зашитой адресной строкой. Можно долго спорить, хорошо это или плохо. В сети кучища холиваров по этому поводу, кто-то предпочитает html5, кто-то нативный код. Я пишу и так и так, но тут, имея исходники 2048 именно на вебе, было бы глупо переписывать всё на нативный objc. Да и потом, если речь идёт о небольшом приложении, основная задача которого — фронтенд + работа с сервером, опять же глупо городить objc, когда можно обойтись html5.
Скажем так, wunderlist прекрасно работает на веб технологиях. Как и официальный gmail клиент.

Наша команда на основном рабочем месте делает b2b проекты. Это такие проекты, где при минимальной посещаемости делается хорошая ебида. Так и у нас, минимальные значения уников в сутки. Поэтому мы можем «играть» технологиями. Пока некоторые верстают под ie8, своим клиентам мы сказали, что только firefox или chrome, и они безоговорочно поставили сотрудникам эти браузеры. Поэтому мы достаточно давно юзаем всякие html5 штуки и радуемся. Однако, когда в голову пришла идея сделать такой проект, я даже не представлял, насколько это будет интересно и ново, не смотря на то, что в общем я знал и частично применял почти все технологии, описанные ниже.
Начнём с окружения, далее пройдёмся по технологиям и обсудим ошибки, сделаем выводы.

image

1. Окружение.

Отцом WebView для мобильников, несомненно, является phoneGap. Он не плохо документирован (чего там документировать то?) и достаточно прост. Что же он делает? Как и другие фреймворки, позволяющие делать нативные приложения на базе веб технологий, phonegap проталкивает нативные возможности в JS код и обратно. Т.е. вы пишите как писали раньше, адаптируете дизайн под мобильное устройство + имеете JS вызовы, позволяющие работать с камерой, нотификейшенами, контактами, СМС и прочим. Список возможностей и платформ доступен на спецстранице. Построен он на базе Cordova.
Так же советаю посмотреть в сторону Sencha, ребята пошли немного дальше и дают возможность не просто получить доступ к нативным фишкам телефонов/планшетов через JS, но так же дают нативные UI. Т.е. делая одно приложение, выглядеть оно будет как нативное и в андройде и в ios`е. Однако, сенча это extjs со всеми вытекающими. Это как с кинзой, есть люди, которые без ума от неё и люди, которые её терпеть не могут ) Однако для общего ознакомления я советую посмотреть и её.
Так же я бы хотел подробней остановиться на Cordova. Фонгэп, это больше окружение. Там есть платная поддержка, облачные сервисы для генерации приложений итд итп. Cordova же это платформа. Непосредственно нативные модули и JS обвязка.

В данном случае, мы не будем использовать ничего из вышеперечисленного. Во-первых, хотелось попробовать сделать всё с нуля. Для себя я решил, дальнейшие приложения только Cordova, идеальное соотношение гибкости и скорости. Во-вторых, у меня не получилось быстро разобраться с окружением фонгэпа или кордовы. Не говоря уже про сенчу. Все они предлагали делать приложения с вшитыми html, а мы сейчас рассмотрим ситуацию, когда код лежит на внешнем сервере.
Почему именно внешний сервер? Мне хотелось сделать приложение, которое я бы смог обновлять не тогда, когда его промодерируют в appstore (обычно занимает неделю), а тогда, когда я нахожу в нём баги. Давайте по порядку.

1.1 TopCoat
В сети много фреймворков для создания мобильных UI, в том числе с аля-нативными UI. Уже говорили про сенчу, так же стоит обратить внимание на PhoneJS, JQ Mobile, Intel AF и многие другие. Гугл подскажет )
В данном случае я выбрал topcoat. Я уже встречался с ним на другом проекте и мне он очень понравился. Во-первых, это не плохие кастомные UI, которые отлично смотрятся и на андроиде и в иосе. Во-вторых, топкоат очень простой, отсюда гибкий. После Bootstrap было немного не привычно, в виду отсутствия сетки, напирмер, итп. Но к этому быстро привыкаешь )
Топкоат имеет ветку с иконками icomatic, не рекоммендую. Font Awesome лучший.

1.2 iScroll
UPD: Хабрапользователь radist2s подсказал, что iScroll в целом и не нужен, если требуется реализовать близкий к нативному скролинг. Оказывается, достаточно заюзать
-webkit-overflow-scrolling: touch;
Несомненно, свайпы и скролинг — визитная карточка мобильных решений. iScroll даёт очень близкий к нативному скролинг. В сети распространена iScroll4 не меньше, чем последняя iScroll5. Соответственно, новая более продвинутая и безбажная. На оффсайте много примеров, в общем всё понятно.

1.3 Zepto
Замена jQuery, более быстрые селекторы и для мобильных приложений самое оно. В крупных проектах мы юзаем jQuery из-за огромного числа плагинов, тут же нам нужны только селекторы )

1.4 Lodash
Просто вспомогательная надстройка над JS. Мы часто юзаем backbonejs в проектах, lodash там дефакто обязателен. Ну и на практике он ни сколько не медленнее нативного кода. Порой быстрее.

1.5 Moment
Работа с датами/временем в JS. Без комментариев, без этой библиотеки головной боли было бы в разы больше.

Я не стал использовать Backbone в проекте, хотя он тут очень даже просится. Опять же, экономим на спичках. Бэкбон удобен и незаменим в более-менее сложных приложениях, однако тут он ни к чему. По крайней мере мне так казалось )
Не могу не отметить rad-js, это кастомные backbone объекты, заточенные под разработку мобильных приложений. Приятная штука, но достаточно замудрённая. Так же очень жаль, что RAD во многом используется старый код. Например, цепляет старый iScroll и undercore, вместо lodash. Опять же, в данном проекте её использование показалось лишним.
С окружением разобрались. У нас нет MVC или подобного фреймворка, весь функционал приложения кладётся в простой js объект. Мы имеем готовый css фреймворк и необходимый набор JS библиотек для быстрой разработки.

image

2. Технологии

Удивительно, как за последние 2 года выросли технологии и браузеры. 2 года назад я выбирал между веб технологиями и нативным кодом. Под большой проект я выбрал нативный код. И на тот момет это было верным решением. Сейчас, по хорошему, переписать всё на веб и развивать дальше веб. Но уже жалко, тысячи строк нативного obj-c, будь он не ладен. Про яву молчу.

2.1 LocalStorage
Ну тут всё понятно, хранилище. 5 метров более чем, тем более, что нам он нужен под текст/js-объекты. Те, кто работал хоть раз с мемкешом/редисом, будет счастлив. Если 5 метров недостаточно, можно сделать обвязку через нативный UserData, например. В сети много решений. Кто-то предлагает кеш складывать в файлы, кто-то через UserData, кто-то рекомендует WebSQL, что, кстати, тоже удобно. Нам хватит localStorage.

2.2 AppCache
Пожалуй, только благодаря этой технологии мы и делаем приложение с внешним кодом. Ещё раз, наше приложение хостится не в локальных html файлах, а на удалённом сервере. Логично, что если у вас в телефоне в данный момент нет интернета, внешние сайты не откроются. Тут то нас и спасёт appCache. Про Application cache вышло много статей, кто не сталкивался с этой технологией, советую ознакомиться. Смысл достаточно простой. С помощью специального manifest файла, мы указываем браузеру все файлы, что нужно разместить в кеш. Прелесть в том, что размещённые в кеше файлы будут работать, даже если в устройстве нет интернета. Логика работы аппкеша в приложении простая.
Клиент первый раз запускает приложение, оно подсасывает контент в webview, webview кеширует приложение. Дальше при каждом запуске приложения, проверяется изменение manifest файла. Если он изменился — кеш полностью перезаписывается. Если изменений не было, работа продолжается. Соответственно, если у пользователя нет интернета, просто цепляется кеш без обновлений.
Таким образом мы можем делать не просто нативные приложения на базе веб технологий, но и размещать непосредственно код у себя на сервере. Для чего это нужно я писал выше — моментальный багфикс и быстрое публикование новых фишек.

2.3 CSS3
Ну и, конечно, глупо игнорировать все возможности вебкита. Вообще, делая на вебе мобильное приложение стоит всегда помнить, что мы делаем его под один-два браузера, которые супер современны и чаще поддерживают новые технологии, чем игнорируют их. Т.е. будет хорошим тоном использовать flexbox, свободно юзать анимацию и забыть про pngfix )
В данном приложении почти вся анимация сделана на базе css3. Во-первых, это проще и быстрее, чем писать километры JS кода. Во-вторых, работает сверх быстро благодаря аппаратному ускорению, которое можно включить в вебките для анимации.

Пожалуй, этого хватит ) Далее, надо подготовить проект в XCode.
Как уже говорил, всё достаточно просто, нам нужен только UIWebView на весь экран и ничего более. К проекту я подключил JSONKit, т.к. он быстрее нативного парсера + VK-SDK, Facebook-SDK для интеграции с соц.сетями. Конечно, можно было сделать интеграцию на вебе, однако для привлекательности я хотел сделать нативную поддержку vk+facebook. Не стоит забывать о возможностях ios sdk. Т.е. веб технологии это круто и мощно, но есть задачи, которые лучше реализовывать нативно. Интеграция с соц.сетями — одна из таких задач, как мне кажется.
Всё, что нам остаётся — наладить связь между нашим html кодом и приложением. Тут тоже всё достаточно просто. WebView, что мы разместили, нужно сделегировать в наш контроллер.
Сообщения из приложения в веб всё очень просто, из любого места дёргаем:
[webView stringByEvaluatingJavaScriptFromString:@"alert(123);"]

Сообщения из веба в приложение работают следующим образом. Проделегированный WebView снифает все переходы, мы можем делать нужные нам ссылки и перехватывать их в методе shouldStartLoadWithRequest:
<button ontouchend="window.location='vk:auth';"></button>

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSMutableURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    
    NSString *requestString = [[request URL] absoluteString];
    if ([requestString isEqualToString:@"vk:auth"]) {
        [VKSdk authorize:@[VK_PER_FRIENDS, VK_PER_WALL] revokeAccess:YES forceOAuth:YES inApp:YES display:VK_DISPLAY_IOS];
        return NO;
    }
}

Соответственно, если мы перехватили нужный нам адрес, не забываем делать return NO, иначе веб версия попытается таки перейти на адрес vk:auth.
Вот в целом и всё, у нас есть веб морда, которая умеет жёстко кешироваться и работать в оффлайне и общаться с нативным приложением. Что ещё нужно для счастья? )

Ошибки и заметки на будущее


В следующей статья я опишу, как использовать Cordova с внешним кодом. Несомненно, ошибкой #1 было решение писать свою оболочку на xcode. Но опять же, задачей было изучить процесс, чтобы понимать всю подноготную. Поэтому это не совсем ошибка. Я не рекомендую использовать свои оболочки, разве что для ознакомления. В любых других проектах нет ничего лучше, чем Cordova. А всё, что она не умеет, можно реализовать в виде плагинов для ней.
Так же игнорирование backbone было ошибкой. Приложение уже начинает разрастаться, дольнейшая доработка была бы намного проще и понятней, если бы сразу был бы использован mvc фреймворк типа backbone.
Помимо прочего, я жёстко лоханулся с настройкой NSURLRequest. Это объект запроса для WebView, который, собственно, и открывает нужный нам урл. Один из параметров которого cachePolicy, куда нужно передавать политику кеширования. Скажем, вы можете использовать стандартную политику (примерно соответствует браузерной), либо, например, указать, что запрос вообще никогда не должен кешироваться или наоборот, браться только из кеша вне зависимости от заголовков. Прочитав про флаг NSURLRequestReloadIgnoringLocalAndRemoteCacheData, решил использовать именно его. Приложение работало очевидно, его я и оставил. И только потом оказалось, что данный флаг не реализован в ios sdk )))

Что касается самой игры, на момент отправки бинарника на модерацию, в аппсторе не было ни одной игры 2048. На момент публикации их было больше 60. Сначала я встроил в html код мобильный adsense. Однако перед самой публикацией я его убрал и сделал приложение платным, т.к. все эти 60+ игр были бесплатные с рекламой внутри. Вроде как какой-то кастом ) метод замены переменной. Стоит понимать, что у меня нет задачи заработать на этой игре, однако поддерживать её бесплатно смысла тоже нет. Основное отличие от конкурентов — интеграция не с GameCenter, а с соцсетями. Мне лично интересней посмотреть кого я обогнал среди своих друзей во вконтакте, чем в незаполненном профиле GameCenter. Ну и проще следить за «хакерами», т.к. база у себя на сервере. Если подобные штуки покажутся группе пользователей интересней и народ будет не против потратить 33 рубля за игру, её можно развивать и дальше. Были идеи строить глобальные рейтинги, кто больше набирает. Например, мужчины или женщины, взрослые или подростки. МГУ или МГИМО итп. Все эти данные собираются из соцсетей.

В любом случае, это лишь «проба пера». Больше всего геморроя я заработал прорисовывая иконки в разных размерах. Всего 18 иконок разных размеров + 13 стартовых скринов.

UPD: Что касается законности подобных приложений в AppStore, рекомендую почитать комментарии @mifki, если всё так, как он говорит, то приложению достаточно пройти ревью и его не удалят за использование кода на стороне. Однако, alamantrah утверждает, что его приложение удалили за внешние lua.
Так же на StackOverlow найдено подтверждение, что раньше за внешние скрипты приложение не пускали, однако сейчас политика безопасности поменялась и всё ок.
Tags:
Hubs:
+52
Comments 41
Comments Comments 41

Articles