12 недель. Неделя 1. FancyTwitter

Итак, вот и настал день первого приложения в разделе «12 недель программирования и дизайна». Как я уже писал ранее  что для первого раза я выбрал Александра Зайцева с его дизайном приложения для Твиттера. Мне понравился сам дизайн, а большим плюсом стало наличие PSD файла к нему. В дальнейшем придется, видимо, всегда выбирать интересные проекты на dribbble.com только c PSD. Наше микро-приложения я назвал FancyTwitter.

Когда мы открываем файл, то сразу видим, с чем нам придется поработать. А именно:

  • кастомный цвет UINavigationBar
  • две кнопочки UIBarButtonItem на нем
  • необычный кастомный элемент с текстом Favorites
  • кастомная клеточка UITableViewCell для таблицы
  • ну и красивый задник под этой табличкой

В принципе, ничего сложного. При разборе файла, я обнаружил, что слой, который прорисовывает navigation bar, состоит просто из белого слоя с 10% прозрачностью. Если я нарисую точь в точь такой в программе, то текст, который будет съезжать вверх при скроле, начнет накладываться на кнопки и заголовок, и это будет некрасиво и нечитаемо. Поэтому я обратился к автору с вопросом, что же он имел в виду. Это следует делать всегда, если вам что-то не понятно. Спрашивать заказчика или дизайнера. Чаще всего, все удается дизайнеру разложить по слоям, и мне как разработчику ничего не надо сливать в Photoshop. Но в данном случае, файл готовился для себя, поэтому совершенно нормально, что автор мне ответил, что он предполагал непрозрачный navigation bar и предложил мне просто самому растрировать этот слой и получить нужный мне png. Кстати, я всегда стараюсь сам резать присылаемые PSD, потому что дизайнер не всегда представляет как я работаю, или мой стиль нарезки, поэтому проще оказывается все резать самому. Также Александр объяснил мне, что наш красивый разблюренный задник статичен по его задумке, но предполагается еще несколько задников, которые пользователь может менять по своему желанию. Этому мы уделим особое внимание дальше.

Снимок экрана 2014-06-20 в 16.22.01

Так как мы будем только симулировать работу Твиттер-клиента, то перво-наперво я создал простой класс FTDataSource, который у нас отвечает за выдачу нам твиттов. Он организован крайне примитивно — это просто массив, содержащий NSDictionary, в которых содержаться все нужные нам данные — имя пользователя, имя его твиттера, картинка профиля, текст и картинка, которую разместил пользователь. Это очень большое упрощение, но на него на приходится идти. Мы можем впоследствии заменить код в этом классе и получать реальные твиты, но пока оставим все как есть. Наша задача просто «натянуть» этот дизайн на код.

Снимок экрана 2014-06-20 в 16.28.39

Далее в первых строках нашего приложения вы видите некий класс ThemeManager. Он служит именно для того, чтобы быстро менять внешний вид всего приложения. Этот паттерн был показан помоему на WWDC 2012, и если кому-то будет интересно, я скажу название сессии. А также к ней есть исходный код. Хотя Apple и не приветствует всякого рода skins в своих приложениях, тем не менее иногда это бывает полезно. Apple в своем гайде по стилю пишет, что всегда старайтесь делать так, чтобы удовлетворить максиально широкую аудиторию всего одной настройкой, а не делать десяток,в которых пользователь утонет. Для чего этот ThemeManager может быть полезен?  Например, вы разрабатываете приложение, и еще не решили на какой варианте остановиться. Вы делаете два-три варианта и показываете их людям. С помощью этого метода вы можете делать разные соборки только меняя один из параметров в свойствах проекта.

Как это работает? Мы делаем singleton класс — ThemeManager который соответствует нашему же протоколу Theme. В этом протоколе мы указываем все-все-все параметры, которые мы планируем менять. В нашем случае это цвета текста, изображение задника, изображение navbar-a. Когда приложение стартует, то инициализирует один из классов, которые и реализуют наш протокол Theme. Также у менеджера есть общая часть, в которой можно прописать, например, все связанное с UIAppearance. Там же есть пример, как можно кастомизировать таблицу.

#import <UIKit/UIKit.h>

@protocol Theme <NSObject>

- (UIColor *)profileNameColor;
- (UIColor *)twitterNameColor;
- (UIColor *)tweetColor;
- (UIImage *)navigationBackgroundForBarMetrics:(UIBarMetrics)metrics;
- (UIImage *)backgroundImage;

@end

@interface ThemeManager : NSObject

+ (id <Theme>)sharedTheme;

+ (void)customizeAppAppearance;
+ (void)customizeTableView:(UITableView *)tableView;

@end

Едем дальше. В Photoshop я нарезал задник в двух разрешениях — для ретины и не для ретины. Задники получились просто огромных размеров. То есть два задника в сумме порядка 3,5 мегабайт. А потом я сделал еще и зеленый вариант для демонстрации. В результате наше приложение еще ничего не умеет, а уже весит не слабо. Если кто-то, кто меня читает знает, что с этим можно сделать — то милости просим в комментарии. Мое же мнение пока такое, что все переливающиеся вещи очень красивые, но с точки зрения пользователя, они очень затратны. Программист тоже мучается, пытаясь все это реализовать. Самым приемлемым вариантом мне кажется использовать некий паттерн рисунка, а затем делать из него заливку (что-то типа обоев, которые мы все любим клеить на стены). Вообщем, это место оставило у меня чувство некой неудовлетворенности.

В Storyboard я создал ViewController, который содержит UIImageView задника, а на нем прозрачная таблица с прозрачными клеточками. Там же сразу в Storyboard я создал макет нашей клеточки, содержащий все те же 5 элементов, что и наши данные. Я использовал AutoLayout для этой клеточки. Это помогло мне не рассчитывать два раза высоту блока с текстом твитта. Один раз я обязан это сделать при расчете высоты клетки в методе heightForRowAtIndexPath. После того, как я задал высоту, AutoLayout автоматически растягивает многострочный UILabel в моей клеточке.

Снимок экрана 2014-06-20 в 16.35.12

Я создал подкласс UITableViewCell, куда передаю NSDictionary, содержащий наши данные по одной клеточке. Далее я разбираю их по местам. В данном примере мне пришлось работать с методом objectForKey. В обычной практике, я стараюсь свести к минимум такой код. Так как компилятор не может проверить ни правописание вашего key, ни тип данных, который метод возвращает. Поэтому потенциально это очень неприятно. Куда удобнее было бы, если бы мы получали, например, объект Tweet унаследованный от  NSManagedObject, а в нем были бы нужные нам properties. Но для симуляции пока пойдет и так.

Хотелось бы отметить, что в коде клеточки мы обращаемся к нашему ThemeManager, чтобы получить цвет текста. Для скругления изображений мы пользуемся старым добрым setCornerRadius:20.0 но, начиная с iOS 7.1 мы обязаны также принудительно указывать clipsToBounds = YES для данного view. Также вы можете заметить, что у нас есть специальный метод, который находит в тексте гиперссылку, и согласно дизайна отрезает у нее «http://« часть, а также окрашивает ссылку в особый цвет. Также немного пришлось повозиться с межстрочным расстоянием. Оно регулируется с помощью добавления экземпляра NSMutableParagraphStyle к нашей attributed string. Обратите внимание, что ищем вхождение гиперссылки мы с помощью Apple-овского класса NSDataDetector. Рисовать свой сеператор между клеточками я не стал, а просто отодвинул его inset в InterfaceBuilder, чтобы он соответствовал дизайну.

-(NSAttributedString*)getAttributedTweet:(NSString*)tweet
{
    NSError *error = NULL;
    NSDataDetector *detector =
      [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:&error];

    NSArray *matches = [detector matchesInString:tweet
                                         options:0
                                           range:NSMakeRange(0, )];

    NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:tweet];

    for (NSTextCheckingResult *match in matches) {

        NSString *stringWithoutHttp = [[[match URL] absoluteString]
                                       stringByReplacingOccurrencesOfString:@"http://" withString:@""];
        [string replaceCharactersInRange:[match range] withString:stringWithoutHttp];
        NSRange oldRange = [match range];
        NSRange newRange = NSMakeRange(oldRange.location, oldRange.length-7);
        [string addAttribute:NSForegroundColorAttributeName
                       value:[UIColor colorWithRed:253.0/255.0 green:166.0/255.0 blue:118.0/255.0 alpha:1.0]
                       range:newRange];

    }

    [string addAttribute:NSParagraphStyleAttributeName value:paragrahStyle range:NSMakeRange(0, [string length])];

    return string;
}

Дальше я нарезал две кнопочки, которые на navigation bar. Дизайнер совершенно правильно сделал из размером 20×20 points. Но вот только, если мы создадим на их основе кнопки, то увидим, что они смещены чуть-чуть больше к центру, чем в нашем дизайне. У нас они гораздо ближе к краю экрана. Я оставил их как есть, потому что мне этот вариант показался более правильным. Я стараюсь всегда говорить дизайнер или заказчику, что это мне кажется более правильным. Если мы нарезали нужную картинку правильного размера, сделали кнопку по умолчанию, и поставили ее на панель, а она стоит не там, то может ее правильный вариант именно этот? Заказчик всегда барин, и он может сказать, а я хочу все-таки ближе к краю экрана. И тогда мы берем один из методов, и двигаем кнопку вбок, но зато мы высказали свои замечания, что кнопка слишком близко к краю, и это может быть не удобно пользователю. Ниже мы видим наложение двух изображений в фотошопе. Так я обычно проверяю, что соответствую дизайну.

Снимок экрана 2014-06-20 в 16.39.16

Что касается Favorites со стрелочкой вниз, то такого элемента у Apple не существует, и такой навигации тоже. Но заказчик хочет именно так. Тем более, что некоторые распространенные приложения используют это — например, ВКонтакте. При нажатии на кнопочку-заголовок, у нас выезжает таблица с вариантами, мы выбираем какой-то, таблица уезжает обратно, а мы обновляем таблицы с новыми данными, а также меняем текст в этой кнопке-заголовке. Это все сделано у меня с помощью двух классов NavBarTitleView и SectionsTableViewController.

NavBarTitleView создает view, на котором расположен текст текущего раздела и изображение стрелочки вниз. К этому вью прикручен UITapGestureRecognizer, благодаря которому, мы знаем, когда пользователь нажимает на наше view. Информацию о нажатии мы передаем в наш «главный» контроллер через делагата. Этот контроллер с помощью анимации показывает нам таблицу выбора разделов и прячет ее, если мы выбрали раздел или снова нажали на середину navigation bar-a. Фантазировать на тему внешнего вида выпадающего экрана не хотелось, поэтому оставил по умолчанию. Белый фон, черный текст. В идеале, это надо все было бы заменить на соответствующий дизайнерский вид.

Хотя у нас и заранее подготовленные данные, я переживал, что очень большое количество прозрачности скажется на быстродействии скролла нашей основной таблицы. Чтобы все проверить, когда уже было закончено приложение, я запустил его на телефоне 4S и с помощью Instruments замерил FPS скролла. Он был не 60, но 57-58, что в принципе нормально, но уже сейчас мы видим просадку. Это еще раз нам говорит о том, что все-таки лучше стараться использовать непрозрачность где только возможно.

Снимок экрана 2014-06-20 в 16.43.11

Если вы выставите в свойствах проекта в разделе Apple LVVM 5.1 — Preprocessing параметр  NEW_THEME = 1, то увидите зеленый вариант приложения, о чем я вам говорил в самом начале этой статьи.

Снимок экрана 20 июня 2014 г., 15.47.31 с Симулятора iOS

В заключение хочется сказать, что мне до сих пор нравится этот дизайн, и то, что у меня получилось сделать с этим, но играясь на телефоне с этим «приложением» я понял, что реально читать Твиттер в нем мне было бы не удобно. Очень много цветного, что глаза разбегаются и трудно сосредоточиться на чтении. Но это лишь мое личное мнение.

Полный проект FancyTwitter к этой неделе на github.com

Маленькое видео с работой приложения:

До следующей недели!

Реклама

12 недель программирования и дизайна для iOS

Друзья, привет!

Давно придумал для себя одну штуку, но только сейчас дошли руки. Я полон желания довести дело до конца. Суть в следующем: каждую неделю я буду выбирать некий дизайн приложения с сайта dribbble.com и с согласия автора (а может и без) я пока не знаю, я буду писать именно этот экран (всего один) в коде, так чтобы все работало, как в настоящем приложении. То есть этот дизайн сойдет с экрана сайта в симулятор iOS и даже на экраны телефонов и планшетов, если у вас есть лицензия разработчика. Статью, описывающую код, я буду выкладывать сюда. А код на github. Так за 12 дней у нас соберется большая коллекция различных дизайнов приложений. В голове уже масса планов — и графики, и collection views, и реализация «скинов» для приложения, хотя Apple это не одобряет 🙂 короче много всего интересного.

Работать мы будем с iOS7, хотя этого немного уже устаревшая ОС 🙂 возможно, что-то ближе к сентябрю напишем и для iOS8. Формат, который бы мне был удобен такой, я сначала пишу целиком весь код, и не отвлекаюсь на написание статьи, а затем в статье рассказываю основные моменты, почему я сделал так или иначе. Так я сэкономлю время себе, да и вам, не рассказывая по порядку «берем вот эту view и тянем сюда, потом снова берем и тянем туда». Я кстати, сам очень люблю учиться именно на примерах. В свое время мне очень помогла именно такая книга.  Вот ссылка, но сейчас книга во многом устарела. Это сборник совершенно готовых рабочих программ. Мне кажется, такие примеры дают понимание, как отдельные элементы работают вместе.

На этой неделе мы начнем вот с этого дизайна. Дизайнер Александр Зайцев дал свое согласие на это мероприятие. Посмотрим, что из этого выйдет. Увидимся ближе к пятнице! Всем хорошей рабочей недели!

Дизайнер Александр Зайцев. Дизайн Твиттер клиента

Как найти вызовы «свежих» API, которые ломают ваше приложение на старых SDK

Итак, как же найти вызовы «свежих» API, которые ломают ваше приложение на старых SDK. На самом деле я знаю только один из ответов на этот вопрос. Надеюсь, кто-то из читателей блога расскажет мне еще один или два. Сегодня утром пришло письмо от одного из заказчиков, что я обещал им безоблачную работу приложения на iOS SDK не ниже версии 5.0. На самом деле они использовали всегда версию 6.0 и выше. Это приложение ддля внутренних нужд внутри компании, и оно не размещается в AppStore. Но вдруг им потребовалось запустить приложение на iPod 3Gen с iOS 5.1.1 на борту … и бабах! Все упало. Они сразу же связались со мной, и прислали несимволизованный crashlog. На самом деле даже из такого лога было видно в чем проблема. Я использовал метод dequeueReusableCellWithIdentifier: forIndexPath:, введенный только в 6-ой версии, вместо хотя бы этого метода dequeueReusableCellWithIdentifier:

Можно было бы все быстро поправить и отослать, но где гарантия, что дальше не будет проблем, а симулятора 5.1.1 или устройства, чтобы проверить у меня нет. Тогда я начал лазить по stackoverflow с целью найти какое-то решение, которое мне поможет выловить все мои «проколы». Оказалось, что подобной встроенной функциональности в XCode 5 не существует. Надо сказать, что хотя я шапочно знаком с разработкой под Android, я знаю, что там такая функциональность сразу включена в их analyzer. Вот этот ответ на stackoverflow меня заинтересовал — http://stackoverflow.com/questions/19111934/get-xcode-5-to-warn-about-new-api-calls . Но к сожалению, как я не пробовал, мне не удалось получить никаких warnings компилятора. Если кто-то знает, как заставить работать эту штуку с макросами, буду рад услышать. Тут я заметил, что есть какой-то платный софт для этого дела, и перешел на сайт — http://www.deploymateapp.com и после недолго игры с демкой, купил программу. Относительно этого моего проекта, трата 20 баксов оказалась напрасной, так как больше серьезных проблем с совместимостью между версиями не осталось кроме использования параметров NSLineBreakByWordWrapping (6.0) вместо UILineBreakModeWordWrap (5.0), но так как числовые значения этих констант не поменялись, то я не стал ничего менять. Но о покупке я совершенно не пожалел, так как мне понравилось, что я практически мгновенно получил информацию по моему проекту, и быстро ответил заказчику. А это, порой, самое главное.

Core Data и mogenerator

Давно я хотел написать этот пост, но все руки не доходили. А все потому, что хотел все подробно рассказать со скринами, тестовыми проектами и прочими радостями. Потом решил сделать этот анти-пост, в котором не будет ни одного скрина и ни строчки кода.

Я очень люблю фреймворк Core Data.  Он мне нравится своей простотой и красотой одновременно. Там безусловно есть свои тонкости, но в целом штука очень удобная, и не зря Apple старается нам ее подсунуть почти в каждый ваш проект с данными. Так как я уже делал несколько проектов с использованием Core Data, то стараюсь периодически посматривать что-то интересное на эту тему. И чуть ли не полтора года назад я напал на mogenerator. Что же это такое? Это скрипт, который на основе вашей модели данных в проекте генерирует код классов сущностей. Но ведь мы можете делать все тоже самое сами в Xcode? Конечно да, но этот скрипт генерирует не один, а два класса на каждую сущность (entity). Один класс генерируется для поддержки вашей модели данных, и его может править только сам скрипт — вы туда не лезете. А второй класс, являющийся подклассом предыдущего, как раз служит для нас разработчиков, где мы вольны описывать любые манипуляции с этим NSManagedObject. Настроив вашу систему на работу с этим скриптом в проекте, вы забываете о перегенерации класса вашей entity — скрипт делает это за вас, и одновременно с этим скрипт не переписывает ваш «человеческий» файл, в котором у вас расписано кастомное поведение объекта. Причем удобно сделать в проекте новый aggregate target, в него вписать выполнение скрипта, а ваш основной тарджет поставить в зависимость. Благодаря этому какие бы изменения вы не вносили в свою модель данных, кроме добавления новых entity — вы автоматически получаете рабочий проект (при прочих равных условиях). В случае добавления новой сущности, вам надо будет добавить эти файлы к проекту, как впрочем и без использования mogenerator.

Основные три ссылки на эту тему вот:

https://github.com/rentzsch/mogenerator

http://rentzsch.github.com/mogenerator/

http://raptureinvenice.com/getting-started-with-mogenerator/

Кому будет интересно, разберется сам — там все супер просто. Если же все-таки будут вопросы, то пишите, и я расскажу подробнее.

Для меня плюсы очевидны — я получаю гораздо лучше читаемый и управляемый код. Остальные несколько плюсов в третьей ссылке.

Advanced iOS Programming

Получил по почте новости от сайта Big nerd Runch, про который я писал, что мне нравятся их книги. Увидел ссылку на их новый класс Advanced iOS Programming. Немного прифигел от ценничка в $4050, но вот программа мне очень понравилась. Поэтому я решил сам себя подтянуть прямо по ней по списочку.

  • Store documents, application data, and settings in iCloud
  • Write concurrent applications using Grand Central Dispatch and NSOperationQueue
  • Customize Storyboard view transitions
  • Use GLKit based controllers for OpenGL 3D rendering
  • Process and enhance images using Core Image and OpenGL ES shaders
  • Efficiently process data sets with the Accelerate framework
  • Asynchronously communicate with and parse data from JSON based web services
  • Post to Twitter and authenticate with other web services using the Twitter framework
  • Access the accelerometer, gyroscope and compass using Core Motion
  • Write Augmented Reality applications that place virtual markers on real-world objects
  • Record, process and playback audio in real time using Core Audio
  • Display and process real time video in OpenGL ES
  • Display content on external displays using AirPlay streaming and wired video connections
  • Use web views to render content and pass information and events between Javascript and Objective-C
  • Master the iOS performance optimization tools and LLDB debugger
  • Secure files on disk and credentials in the keychain

Начал с iCloud. Если что-то интересное по этим темам будет, то буду делиться 🙂

Как ловить еxception в Xcode 4.2 и получить наглядный stack call

Может быть вы, как и я, не заметили что теперь при креше программы в консоли не пишется бэктрейс, а пишется вот так странно: набор букв и цифр адресов объектов. Как говориться, дебаггер не смог сделать «symbolicate stack call».

Я сначала не обратил на это внимание, так как при тестировании было всего одно-два падения программы, и я точно знал, где оно произошло и почему. А затем озадачился этим вопросом. Оказалось, что для нас опять сделали как лучше, а мы и не заметили. Идем в наше закладку Breakpoints в Xcode и добавляем крестиком внизу новый брекпоинт. Там появился новый тип точки остонова — Exception Breakpoint, вот его и добавляем. В большинстве случаев, можете сразу жать Done и новый брекпоинт добавлен. Теперь, когда ваше приложение, например, выйдет за границы массива, то ваш приложение остановится точно на той строчке в дебаггере, на которой произошла проблема. Чтобы получить бэктрейс всего стека вызовов, как вы сюда попали, просто напишите в консоли bt, и вам вывалиться этот список. К тому же все ваши переменные в этот момент «живы» и может изучить проблемы, вызвавшую падение приложения.

Совместимость с iOS 5. Часть 2

В другом приложении, в котором я участвовал, а именно iPif, я поставил на экран загрузки списка пифов элемент UIActivityIndicator. Я не знаю, чем я руководствовался тогда, но я поставил его на белый фон UITableView со свойствами Large White Activity Indicator. Но в iOS 4 все выглядело прекрасно. Реально все выглядело как серый индикатор активности на белом фоне, поэтому я не заметил ничего подозрительного. Однако установив программу на iOS 5, я увидел, что индикатор пропал, и пользователь больше не имеет возможности знать идет закачка списка или нет. Я начал разбираться в вопросе, и когда узнал в чем дело, то стал практически прыгать от радости. Все дело в том, что Apple наконец сделал возможность присваивать цвет этому индикатору. Теперь он может быть хоть зеленый, хоть красный и хоть серобуромалиновый. Но если цвет не задан, то в таких местах, где белое на белом — его теперь действительно не видно. Что же надо теперь сделать, чтобы решить проблему. Например, мы можем задать в iOS 5 серый цвет индикатора, но тогда при запуске iOS 4 наше приложение упадет, так как в 4-ой версии этого нет. Многие программисты начинают выискивать версию iOS, на которой сейчас работает человек и вызывать методы в зависимости от номера версии. Apple выступает категорически против этого. Для этого есть очень простой метод: мы проверяем отвечает ли наш объект на setColor, если да — то это iOS 5 и выше, и мы присваиваем нужный нам цвет. Если же не отвечает, то оставляем все как есть. Вот пример кода:


if ([loading respondToSelector:@selector(setColor:)]) {
    [loading setColor:[UIColor lightGrayColor]];
}

Все, теперь мой UIActivityIndicator прекрасно отображается на всех версиях.