Выбор средств разработки
Изначально выбор у iOS-разработчика небольшой. Использовать:
- Objective-C в качестве языка программирования и iOS-SDK в качестве набора различных инструментов
- Один из фреймворков, большинство из которых используют JavaScript, HTML и CSS
Подробнее постараюсь рассказать о них в одном из следующих постов, а пока с помощью сравнительной таблицы в английской википедии вы можете сами прийти к выводу, что для разработки iOS-приложений самый подходящий из них — Appcelerator Titanium. В частности, потому что с его помощью можно делать именно встроенные, а не веб-приложения, и потому что классы и методы, которые предлагаются разработчику являются не симуляцией нативых аналогов при помощи движка WebKit, как это делает, к примеру, PhoneGap, а самыми что ни на есть JS-обертками милых сердцу аналогов из Objective-C. Также у разработчика имеется возможность расширения Titanium путем написания собственных модулей на Objective-C (или на Java для Android). К сожалению, в Titanium реализованы в большинстве своем наиболее типичные классы и методы. В нашем случае задача была весьма творческой и потребовала бы написания довольно-таки объемного модуля, возможно даже более, чем одного. В итоге, мы получили бы приложение не самое быстрое и, наверняка, плохо расширяемое. Таким образом, выбор был ограничен рамками Objective-C и iOS SDK.
Карта наша очень красива и своеобразна, поэтому далеко не факт, что получится реализовать ее средствами стандартного фреймворка от Apple — MapKit, в основе которого лежат Google Maps. Второй вариант — использовать библиотеку RouteMe с открытым исходным кодом. Ну и третий — написать карту с нуля самим.
- MapKit позволяет использовать пользовательские наложения на карту, начиная с iOS 4.0, хотя, конечно, скорее всего разработчики предполагали их использование для чего-то другого, например, наложения маршрутов (где-то в лицензионном соглашении использования Google Maps можно это уточнить, хотя, конечно, уточнить могут это и работники Apple, когда будут писать вам, по какой причине они отклонили ваше приложение). Тем не менее, теоретически, задача решаема с помощью MapKit. Но тут встает ряд других вопросов, среди которых:
- Можно ли заставить работать Google Maps полностью в offline-режиме (зачем нам подгружать под нашу красивую карту какую-то другую?)
- Можно ли ограничить карту пределами Ижевска?
Решения, безусловно, можно найти. Но может стоить рассмотреть другие варианты?
- Многие рекомендуют использовать библиотеку RouteMe, которая позволяет помимо Google Maps использовать также OpenStreetMaps и ряд других движков для карт. Данный вариант подходит уже гораздо лучше. Однако, у него есть и свои минусы. В частности, на момент реализации приложения библиотека не поддерживала offline-вариант работы. Сейчас это возможно при использовании DBMap и MBTiles. Ну и поскольку библиотека разрабатывается opensource-сообществом, то она далеко не идеальна, хоть и развивается довольно-таки активно. Возможно, данный вариант сегодня имеет смысл использовать
- В итоге, было принято решение написать все с нуля. Подробнее об этом варианте ниже, сразу после описания подготовительной работы.
Подготовительная работа
Очень хорошо, когда все участники проекта работают в одной компании. Это отличный повод показать друг другу, какое в компании замечательное взаимодействие между людьми. В данном случае речь идет о менеджере, дизайнере, разработчике интерфейса и программистах под две платформы (iOS, Android).
И не откладывая дело в долгий ящик, я попросил нашего дизайнера и идейного вдохновителя проекта, Ксению Докучаеву, сделать удобный psd-файл: сделать отдельные слои для названий крупных улиц и улиц поменьше, парков, стадионов и прочих прудов; отдельную папку с отдельным слоем для каждой достопримечательности и адекватным ей названием. Ксюша любезно сделала небольшой (чуть более 500 мегабайт) psd-файл, в котором лежала карта, совпадающая по своим пропорциям с экраном iPhone (таким образом, Ижевск подвергся айфонизации). Далее был написан скрипт на JScript, с помощью IDE Adobe ExtendScript Toolkit (входит в дистрибутив Photoshop) и позволяет работать с API Photoshop, в котором покрыты многие из фотошоповских функций. Об этом есть статья на хабре. Скрипт вычислял центры координат иконок для достопримечательностей (видимых слоев) и нормировал координаты по осям x и y, заключая их область определения в отрезок [0, 1]. В базе хранились целые значения. Поскольку при наибольшем увеличении масштаба карты ее размер не превосходит 10000 пикселей в обоих направлениях, то эти целые значения получились умножением нормированных координат на 10000:
<layer name="Не кантовать" coord_x="8924" coord_y="3115"/>
После чего все это попадало в базу SQLite (которая использовалась прежде всего потому, что является универсальным хранилищем данных для iOS и для Android).
Поскольку, ресурсы iPhone, как и других мобильных устройств значительно уступают ресурсам таких компьютеров, как, к примеру, iMac, единственный способ отображать карту быстро — это разрезать ее на множество кусков и сменять их по мере необходимости, что и делает Google Maps, но в отличие от их 20 уровней детализации, Ижевску хватило всего 4-х. В пикселях:
- 512*768
- 1024*1536
- 2048*3072
- 4096*6144
Изображения были разрезаны инструментом TileCutter на т.н. тайлы (плитки) , размером 256*256 пикселей. Полученные изображения были названы в соответствии со следующим форматом: уровень увеличения_номер столбца_номер строки. Все они были добавлены к ресурсам приложения и заняли не так много места: чуть более 6 мегабайт.
Разработка
Карта
Основа карты была реализована при помощи класса CATiledLayer, который входит в состав фреймворка QuartzCore. Класс очень полезен для работы с изображениями высокого разрешения. Механизм работы примерно следующий:
- Создаем основу для нашей карты — это экземпляр класса UIView, который завернут в экземпляр UIScrollView
- Переопределяем метод класса layerClass:
+ layerClass {
return [CATiledLayer class];
} - Задаем 4 уровня детализации для карты:
tiledLayer.levelsOfDetail = 4;
- Переопределяем метод
- (void)drawRect:(CGRect)rect
экземпляра UIView таким образом, что в зависимости от прямоугольника rect отрисовываются нужные тайлы. Прелесть CATiledLayer заключается в том, что благодаря ему все это происходит ассинхронно, причем CATiledLayer сам решает, сколько необходимо запустить потоков, а также кэширует изображения для нескольких уровней детализации. В итоге все работает очень быстро! И даже на iPhone 3G.Более подробно о классе можно почитать в документации Apple. А сам подход был отлично описан на WWDC 2010. Видео доступно для скачивания в iTunes, там можно включить английские субтитры. Код проекта доступен в Sample Code, который в новом Xcode 4 можно загружать прямо из раздела документации в органайзере. Также этому подходу посвящен пост в блоге cocoa is my girlfriend.
Когда карта была готова, это показалось очень крутым, но самое интересное было впереди.
Иконки, всплывающие подсказки
Сложность отрисовки иконок и подсказок заключалась в том, что они лежали в иерархии на самом верхнем уровне. То есть на том же, что и сама карта, иначе бы они масштабировались бы при изменении масштаба карты, что нам совсем не нужно. Таким образом место действия развернулось, условно говоря, в трех системах координат:
- Система координат карты с началом координат в левом нижнем углу. Максимальные значения: по x — 4096, по y — 6144:
- Система координат UIScrollView. Начало координат там же, максимальные значения зависят от текущего масштаба:
- Система координат экрана. Начало в левом нижнем углу экрана. Максимальные значения в пределах экрана: 320 и 480, в пределах карты: 4096 и 6144. Соответственно минимально возможные: 320-4096 и 480-6144:
Таким образом, вся математика основана на преобразовании координат между этими тремя системами. В делегатных методах мы запускаем код, который при каждом движении UIScrollView пересчитывает координаты иконок, попадающих в экран и подсказки, если она активна:
-(void)scrollViewDidZoom:(UIScrollView *)pScrollView {
[mapViewController calcCoord];
}
- (void)scrollViewDidScroll:(UIScrollView *)pScrollView {
[mapViewController calcCoord];
}
Но всегда найдется какой-нибудь лось. Наш лось стоит на самом краю города и карты, поэтому при минимальном масштабе иконка в карту не влезала:
Ради лося пришлось рассмотреть этот частный случай и хитро решить проблему. Когда пользователь уменьшает карту и иконка какой-либо достопримечательности не влезает, мы незаметно сдвигаем ее в пределы карты. С невлезающими подсказками поступили немного иначе. Решили сдвигать галочку от центра к краям:
Геолокация
Все в том же замечательном psd-файле был обнаружен еще один слой. Это был слой Яндекс карт, поэтому узнать координаты краев нашей карты было делом несложным. Правда, тестирование все-таки пришлось провести. В конце концов, прокатиться по городу на машине с iPhone — не самое утомительное занятие в жизни iOS-разработчиков.
Что дальше?
Приложение уже в appStore и уже было выпущено небольшое обновление, в котором мы немного улучшили совместимость с iOS 5. В перспективе мы планируем сделать некоторые вещи из этого списка (тут, конечно, не все, потому что мы любим делать сюрпризы):
- Английская локализация приложения
- Небольшое API для приема данных с сервера, что неизбежно влечет за собой появление фотогалерей у примечательностей и оперативное обновление базы данных. Сейчас приложение работает полностью offline
- Дальнейшая работа с геолокацией: расстояние до примечательностей и прокладка маршрутов. Последнее, как и обновление базы зависит, как вы понимаете, не только от нас=)
В комментах охотно отвечу на вопросы и передам всей нашей команде ваши предложения о дальнейшем развитии карты.
Отдельная благодарность в подготовке иллюстраций нашему UX-дизайнеру и просто хорошему человеку Ивану Белобородову.
Примечательности Ижевска в портфолио ЦВТ
Метки: iOS, iPhone, мобильные приложения