Идея
Основа основ — это идея. Сложно что-либо реализовать без четкой идеи, что должно быть в конце. И такая идея у меня была. Мне всегда были интересны количественные показатели моей жизни. Причем именно тех критериев, которые технике померить просто невозможно. Они субъективны и оцениваются нами по ощущениям. Родилась идея сделать приложение, которое позволило бы оценивать прошедшей день именно по таким субъективным идеям. К тому же, было бы неплохо, если бы оно еще и помогало отмечать прогресс на пути к достижению цели.
Описание идеи
Идея сама по себе — это, конечно, очень хорошо. Но она ничто, пока не обретет материальное воплощение. И первый шаг на этом пути — её описание. Составление первых описаний будущего приложение, наброски первого ТЗ, первые наброски интерфейса. Вот тут я совершил самою большую ошибку. Про нее написано в каждой подобной статье, и все равно все ее совершают. Казалось бы, всё приложение у меня в голове, я могу представить каждый экран, каждое действие, но, как выяснилось позже, этого мало. В процессе работы появляется много нюансов, мелочей, которые в голове почему-то не просматривались и вот как раз они-то и отнимают львиную долю времени на разработку. К своему следующему проекту я подошел более основательно и ТЗ на него включает описание переходов, реакций на нажатие, анимаций и т.д. Одним словом, необходимо устранить по возможности все белые пятна в идее. Предусмотреть максимальное количество сценариев использования. Это значительно ускорит работу на следующих этапах разработки.
Разработка
Не следует думать, что вы можете все. К сожалению, это не так. Я решил и поплатился за это потраченными временем и нервами. Мне казалось, что поскольку я представляю себе конечный продукт, то и рисовать мне его не надо, а сделаю я все по ходу программирования. Отличное заблуждение. В итоге получил ужасный не только UI, но и UX. Хотя работающий прототип создал.
Правда, его создание должно было занять неделю или две, но не как не 2.5 месяца. Тут надо отметить, что наличие этого прототипа очень помогло в дальнейшем. Впредь я буду создавать прототипы, но делать это при помощи специализированных инструментов:
Разработка. Перезагрузка
Первый вариант никуда не годился и, более того, у меня была полная уверенность в том, что Apple не должны пускать его в AppStore. Поэтому было решено перезапустить разработку. Первое, что я сделал — это переписал ТЗ. Оно уже не было просто описанием идеи, хотя и до идеала ему было еще далеко. В нем появились описание экранов приложения, переходов между ними.
Я нашел дизайнера для приложение. Про поиски дизайнеров, да и работников, написано достаточно много. Я искал через freelansim.ru, ну и, конечно, со всеми переговорил. Надо отметить, что выбор людей для работы — это всегда субъективное и неподдающееся логическому объяснению решение. Мне даже кажется, что это сродни лотереи.
Мне повезло, что я выбрал человека, который не только прекрасно справился с поставленной задачей, но и сильно помог на этапе тестирования.
Дизайн создавался и потихоньку приложение обретало внешний вид. Тем временем, я решил, что раз уж перезагружать разработку, то делать все максимально по уму. Поскольку из собственного опыта у меня был только один неудачный прототип, я принялся за изучение чужого опыта. Вот что я вынес из прочитанных статей и просмотренных докладов:
- Используйте CocoaPods — отличный и простой инструмент, помогающий управлять сторонними библиотеками;
- Структурируйте проект в xCode — я разбил свой проект довольно примитивно, но, в тоже время, довольно понятно.
- Задумайтесь о статистике еще до начала разработки. Для статистики я использовал проект Yandex.Metrika и Parse. Правда, статистика Parse пошла приятным дополнением. В основном я подключал этот сервис для Push Notification, но теперь планирую использовать его как backend для данных пользователей. Я выписал отдельно все события, которые хочу отслеживать в приложении, и уже по ходу разработки просто добавлял их, не тратя время на решение;
- Вынесите все, что встречается в проекте более, чем в одном классе, в отдельный файл, которым будет легко управлять и изменять. Сюда попадают различные перечисления, дефайны, константы и прочие данные.
Эти несложные подготовительные шаги и хорошая структура проекта экономят массу времени на изменения, в особенности, на навигацию по проекту.
Недочеты, откровенные просчеты в организации проекта и интересные ошибки
Это, как мне кажется, самая интересная и полезная часть. Много из того, что я опишу в ней, должно быть понятно и очевидно опытному разработчику, но когда я только начинал, мне не хватило именно этих знаний.
Локализация приложения
На WWDC14 Apple представила новый способ локализации приложений. Можно экспортировать весь проект в .xliff файл и переводить его. Это практически стандарт в мире переводчиков. По крайней мере, на презентации сказали именно так. Я обрадовался: хороший, легкий способ, и все в одном месте. В том месте программы, где нужен будет перевод, вместо обычной строки пишется макрос:
NSLocalizedString(@“string”, @“comment”).
В начале та строка, что требует перевода, а затем комментарий, в основном, для переводчика. На программу он никак не влияет. Но вот беда, от ошибок никто не застрахован, да и есть места, которые повторяются в программе. В общем, если вы где-то ошиблись, то для исправления придется просмотреть весь проект. Но и это еще не все. Дело в том, что строка для перевода в .xliff файле станет ключем, и если вы ее поменяете в программе, а переводчик не изменит файл перевода, то вы получите две строки с одинаковым переводом и разными ключами. Xcode не найдет перевод и место останется не локализованным.
Решением этой проблемы стало комбинирование старого и нового способа локализации. Все строки, необходимые для перевода, выносятся в файл Localizable.strings в виде ключ-значение. И обращаемся уже к ключу. Теперь мы получаем возможность исправлять исходные тексты, не опасаясь за перевод. А переводчик, опять же, получает знакомый и удобный формат для работы.
Странные ошибки
Все описанное в этом разделе произошло исключительно из-за моего незнания.
Все написанное я тестировал на своем iPhone 5. Программа работала прекрасно, я смотрел на нее и не мог нарадоваться. В приложении есть момент, когда пользователь должен оценить свой день, и для этого ему предлагается использовать слайдер, при этом оценивание может происходить по трем разным шкалам: сто- десяти- и трехбалльной системе. Для ста- и десятибалльной системы используются слайдеры, а для трехбалльной смайлики. Все это выводится в таблицу, в которой каждому критерию выделена своя ячейка. Для удобства выбора я написал следующее:
typedef enum: NSInteger{
EDEvaluatePriceHundredPoint,
EDEvaluatePriceTenPoint,
EDEvaluatePriceThreePoint
}EDEvaluatePrice;
Сами значения хранятся в CoreData и представлены в виде NSNumber. При выборе я решил делать следующие сравнения:
if (c.priceCriterion == [NSNumber numberWithInteger:EDEvaluatePriceHundredPoint]) {
}
Мне казалось это логичным, тем более это работало у меня на телефоне. Хотя тут сравниваются два разных объекта.
А вот на iPhone 5S и новее это уже не работало и появлялись пустые ячейки. Странная история, которую я до сих пор не понимаю. Но решается просто:
if ([c.priceCriterion integerValue] == EDEvaluatePriceHundredPoint) {
}
Вторая ошибка проявлялась только на iPhone 6 Plus и заключалась она в сломанной верстке приложения. К тому же не на всех его экранах, а только на избранных. Проблема оказалась в представленных в xcode 6 constraint to margin. Причем по умолчанию эта функция включена. Опять же не могу сказать, почему именно такое происходило, мне кажется, что проблема в использовании Containers. Но ведь этот же механизм использован Apple, например, в TabBarController. Да и проблема, опять же, проявлялась не всегда.
P.S. Несмотря на все странности и кучу подводных камней, мне все же понравилось разрабатывать для iPhone. Мое приложение находится в сторе всего пару недель, поэтому говорить о каких бы то ни было результатах еще рано. Позже я опубликую отчет о продвижении приложения.
Комментарии (8)
jaguard
29.04.2015 23:10Жуткий рунглиш на скриншотах, надеюсь, не пошел в продакшн?
Konstantint Автор
29.04.2015 23:24Тот вариант, что на скриншотах не пошел. Хотя из-за откровенно кривой реализации локализации, ошибки в английской версии просочились в продакшн. Буду менять весь подход к локализации, что бы в будущем постараться свести количество ошибок к 0.
i_user
30.04.2015 09:14Хотелось бы вставить свои 5 копеек — очень популярно сейчас говорить «прикрутите статистический модуль в начале разработки».
1. Крэш-аналитика однозначно нужна на первом этапе. В этом плане от себя могу порекомендовать HockeyApp — у них есть отличный клиент, облегчающий работу с крэшами. И в хороших деплоймент-скриптах навроде fast lane есть интеграция с этим сервисом в отличии от Яндекс.Метрик
2. Статистика же использования сама по себе абсолютно бесполезна до понимания двух вещей:
-Статистика до 5-10к пользователей попросту нерепрезентативна
-Покрывать приложение статистикой «авось пригодится» рано или поздно приводит к тому, что выдача статистики становится бесполезной и ненужной — крайне рекомендую перед тем как включать сбор статистики — написать на отдельной страничке — а что вы, собственно, хотите узнать. Набор гипотез, которые вы хотите проверить — и уже для этих гипотез формируете минимальный набор лог-ивентов, которые позволяют разделить все гипотезы.Konstantint Автор
30.04.2015 09:38-Покрывать приложение статистикой «авось пригодится» рано или поздно приводит к тому, что выдача статистики становится бесполезной и ненужной — крайне рекомендую перед тем как включать сбор статистики — написать на отдельной страничке — а что вы, собственно, хотите узнать. Набор гипотез, которые вы хотите проверить — и уже для этих гипотез формируете минимальный набор лог-ивентов, которые позволяют разделить все гипотезы.
Полностью согласен. Я не призываю прописывать событие на все экраны и смотреть, что будет. Избыток данных это тоже не очень хорошо.
Но всегда полезно знать, какие части приложения пользуются, спросом. Как раз обдумыванием этих ключевых мест я и призываю заниматься с самого начала. А не хвататься за это в самом конце.
— У яндекса в метре тоже имеются крэш репорты. Этим то она меня и подкупила. Но тут не чего утверждать и рекомендовать не могу, пока сам все не попробую.
ZhukV
06.05.2015 08:08Мне казалось это логичным, тем более это работало у меня на телефоне. Хотя тут сравниваются два разных объекта.
А вот на iPhone 5S и новее это уже не работало и появлялись пустые ячейки. Странная история, которую я до сих пор не понимаю. Но решается просто:
Дело в том, что Objective-C — объектный язык, и уже NSNumber, это ни какой не int, не float и другая фигня. В частности, эта скажим некоторая обертка, над цифровым типом данных, с которого Вы можете получить уже любое значение.
В результате, Ваше сравнение получилось что-то вроде (сравнения числа и объекта):
int == NSNumber (id)
На самом деле, я считаю это очень верным подходом в объектном языке. Вы не паритесь, какая архитектура (32, 64), ну если Вы не пишете там «большущие» вычисления, а также получаете большое количество helper-ов, с помощью которых Вы сможете многое сделать, не напрягаясь.
Аналогичным образом работают и NSString и другие обертки над типами.
В частности, я много видел людей, которые используют базовые типы: int, float, unsigned int… Лично я бы советовал использовать типы объявленые в Objective-C — NSInteger, CGFloat, NSUInteger… При таком подходе Вам пофигу будет, что там потом придумает Apple (ну не прям пофигу, но совместимость наверное таки будет).
Вынесите все, что встречается в проекте более, чем в одном классе, в отдельный файл, которым будет легко управлять и изменять. Сюда попадают различные перечисления, дефайны, константы и прочие данные.
В некоторых кодах, видел, что используют такие классы для быстрого получения инфы, которая необходима в некоторых участках. И думаю, так и нужно делать, чтобы потом не дублировать сотни кодов.
К примеру:
+ (BOOL)isRetina; + (BOOL)isIos8 + (BOOL)isIos7 // .....
Вот как один из примеров: github.com/ericjohnson/canabalt-ios/blob/master/flixel-ios/src/Flixel/FlxG.m
Еще очень хороший подход, это реализация событийной системы. То есть, приложение при каком-то действии отправляет события, а уже подписчики могут делать все что захотят.
А почему не использовали GoogleAnalytics для статистики? Как по мне, очень даже хороший вариант, да и еще в связке с AdMob-ом.
Flanker_4
Помогу немного
В этом случае, Вы сравнивали одинаковость указателей.
Правильно сравнивать объекты по значению нужно так
Еще можно использовать сахар @(EDEvaluatePriceHundredPoint)
и сравнивать сразу методом для NSNumber (если уверены, что оба объекта этого класса)
Что касается этого " Проблема оказалась в представленных в xcode 6 constraint to margin"
Не скажу, нужно смотреть экраны. Может ваш девайс был на iOS7, а айфон 6 на iOS8?
Konstantint Автор
Точно. Сравнение указателей, вот я болда. Спасибо. Именно поэтому не работало на 64 битных системах.
По поводу constraint to margin, вот тут мне непонятно зачем их ставить по умолчанию и везде. И проблема проявлялась только на экране iPhone 6 Plus на остальных устройствах только при наличие системной плашки в верху экрана. В остальных случаях и на разных ios все было нормально.
miksayer
Есть подозрение, что на 32-битных системах простые константы NSNumber(типа @1, @2 и т.д.) создаются один раз и переиспользуются потом, поэтому сравнение по указателю у вас прокатывало без вопросов. А на 64-битных системах используется оптимизация tagged pointers и константы уже по какой-то причине не переиспользуются. О tagged pointers можно почитать тут.