Прокручиваемая по горизонтали лента с
последними фотографиями из памяти устройства
Во-первых, очень грамотно, что с самого начала в ленте видно не ровно 4 или не ровно 5 снимков, а 4 и 1/3. Это дает пользователю моментально понять, что список фотографий прокручивается по горизонтали.
Возникает несколько вопросов:
- Сколько фотографий загружать в эту ленту?
- Как организовать динамическую подгрузку фотографий, чтобы они все не висели в оперативной памяти?
Сначала я решил что буду отображать в ленте все фотографии из памяти устройства, в случае проблем со скоростью загрузки обещал себе вернуться к этому вопросу.
Сразу же возникла проблема с получением всех фотографий из памяти устройства в правильном порядке, ведь требовалось в начале ленты отобразить самые новые фотографии. Сразу же оказалось, что самые новые фотографии у меня находятся не в альбоме с сохраненными фотографиями, а в Фотопотоке, которым со мной в этот день поделился мой брат.
Было принято решение в начале ленты отображать последние фотографии из альбома с сохраненными фотографиями, а уже за этим альбомом все остальные. Внутри каждого альбома фотографии я стал располагать начиная с последней. Вот исходник, получающий массив ALAsset
-ов в описанном порядке:
@implementation ALAssetsLibrary (Extension) - (void)latestAssetsAndCall:(void (^)(NSMutableArray *))callback { __block NSMutableArray * assets = [NSMutableArray arrayWithCapacity:5000]; [self enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) { if (group == nil) { callback(assets); return; } ALAssetsGroupType groupType = [[group valueForProperty:ALAssetsGroupPropertyType] intValue]; int insertIndex = (groupType == ALAssetsGroupSavedPhotos) ? 0 : assets.count; [group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) { if (result != nil) [assets insertObject:result atIndex:insertIndex]; }]; } failureBlock:^(NSError *error) { if (error) NSLog(@"%@", error); }]; } @end
Что касается второго вопроса, я не нашел ничего лучше, чем использовать UITableView
, ведь он просто создан для прокрутки длинных списков, динамической подгрузки контента и повторного использования похожих ячеек таблицы. Единственное, что — таблицу необходимо повернуть на 90° против часовой стрелки. Учитывая, что трансформация осуществляется относительно центра объекта, располагаем центр UITableView
в предполагаемом месте центра ленты и выполняем:
self.tableView.transform = CGAffineTransformMakeRotation(-M_PI_2);
При создании ячеек таблицы, необходимо выполнить обратную трансформацию — вращение на 90° по часовой стрелке:
- (UItableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BottomRollCell"]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"BottomRollCell"]; cell.contentView.transform = CGAffineTransformMakeRotation(M_PI_2); // тут создание элементов ячейки } // тут заполнение элементов ячейки конкретным контентом }
Что мы имеем в итоге? Таблица по мере прокрутки запрашивает у нас содержимое своих ячеек. Имея массив ALAsset
-ов, получаем thumbnail-ы изображений и заполняем ими ячейки таблицы. Таблица прокручивается очень плавно, лагов с подгрузкой фотографий не замечено. По поводу времени получения всех фотографий — получение 2500 фотографий занимает менее 1 секунды времени, но для запуска приложения это критично. Делаем анимацию выпадения таблицы справа налево по факту получения ALAsset
-ов. Получается очень мило и задержка в полсекунды практически не заметна. Тем более что запрос не всех ассетов, а через задание множества индексов прироста скорости не дает, это меня даже несколько обескуражило. Таким образом оптимизация с быстренькой предзагрузкой первых фотографий — не покатила.
Анимация открытия и закрытия фотографий
Было принято решение разворачивать фотографии прямо из миниатюр с ленты при их открытии и сворачивать их обратно при отмене редактирования. Для того чтобы получить координаты прямоугольника определенной ячейки таблицы, я использовал метод класса UITableView
:
- (CGRect)rectForRowAtIndexPath:(NSIndexPath *)indexPath;
Пришлось потратить немного манны, чтобы определить точные координаты картинки с учетом её поворота и т.д. После получения координат миниатюрного изображения в главном виде, поверх миниатюры я располагал уменьшенное полноразмерное изображение и анимированно менял размер и позицию изображения. В идеале, я хотел бы чтобы изображение выпрыгивало из ленты с нелинейной траекторией, но на это времени так и не осталось…
Для того чтобы отслеживать изменения в фотографиях устройства, необходимо подписаться на событие ALAssetsLibraryChangedNotification, по которому необходимо перезагружать массив ALAsset
-ов. Чтобы лента не начала обновляться при сохранении фотографии самим приложением — необходимо использовать внутренний флаг для отмены перерисовки ленты при следующем обновлении и добавлять вручную новый ALAsset
в начало массива ассетов.
Сохранение у меня осуществляется в самую левую позицию, я сдвигаю таблицу вправо на ширину одного изображения, осуществляю анимацию улета изображения в ленту, двигаю таблицу обратно без анимации и вручную вызываю reloadData
.
Для того чтобы анимации открытия и закрытия изображений выполнялись плавно и максимально быстро, пришлось сделать одну интересную вещь. Если открыть фотографию, изменить её масштаб и нажать кнопку отмены — фотография улетит в ленту именно в том виде в котором мы её оставили после масштабирования. Фотография будет оставаться там в этом виде до тех пор, пока она не будет скрыта за границей экрана и не перезагружена вновь. Для достижения этого эффекта я использовал NSMutableDictionary
с URL-ами ассетов в качестве ключей и NSValue
, содержащий CGRect
, в качестве значений. К сожалению, я забыл снять это свойство в видео-обзоре, но это была одна из самых интересных проблем для меня.
Плавное масштабирование и позиционирование фотографии с применением эффектов
Очень хотелось сделать масштабирование и позиционирование фотографии с одновременным применением выбранного эффекта и в предпросмотрах тоже все двигать синхронно и накладывать эффект. Вобщем, если попытаться так сделать — тормозить эта радость будет безбожно. Было найдено интересное решение, применить выбранный эффект к основной фотографии и взять фото уменьшенное в пять раз (точнее 320.0/56.0) и применить остальные эффекты к нему, а в процессе масштабирования и позиционирования синхронизировать скроллы миниатюр с главным UIScrollView
. Этот способ работает быстро, плавно и без косяков.
Код, выполняющий синхронизацию скроллов миниатюр с главным скроллом (это методы делегата UIScrollViewDelegate
):
- (void)scrollViewDidScroll:(UIScrollView *)scrollView { for (UITableViewCell * cell in [self.filtersTable visibleCells]) { UIScrollView * filterScrollView = (UIScrollView *)[cell.contentView viewWithTag:125]; filterScrollView.contentOffset = CGPointMake(scrollView.contentOffset.x*56/320, scrollView.contentOffset.y*56/320); } } - (void)scrollViewDidZoom:(UIScrollView *)scrollView { for (UITableViewCell * cell in [self.filtersTable visibleCells]) { UIScrollView * filterScrollView = (UIScrollView *)[cell.contentView viewWithTag:125]; filterScrollView.zoomScale = self.scrollView.zoomScale; filterScrollView.contentOffset = CGPointMake(scrollView.contentOffset.x*56/320, scrollView.contentOffset.y*56/320); } }
Сохранение результата
Так как для нас самое главное скорость работы приложения и качество снимков в целом условиями конкурса не регламентированы, я решил сохранять снимки размером 640х640 (на ретине). И проще всего это сделать через рендеринг главного вида в контексте изображения со смещением вверх:
- (UIImage *)renderImageForSaving { UIGraphicsBeginImageContextWithOptions(self.scrollView.bounds.size, YES, 0.0); CGContextTranslateCTM(UIGraphicsGetCurrentContext(), 0, -self.scrollView.frame.origin.y); [self.view.layer renderInContext:UIGraphicsGetCurrentContext()]; UIImage * image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; }
Это быстро и без дополнительных проблем с надписями и т.д. Да, разрешение можно было бы сохранять и побольше — но это уже совсем другая проблема, требующая времени и терпения)
Видео с моей непослушной собакой, демонстрирующее работу приложения:
Думаю ссылку дать можно (ок?). Приложение бесплатное и без рекламы, соответственно.
Ссылка на приложение: https://itunes.apple.com/app/pictography/id570470169
P.S. Ну и напоследок, большое спасибо Вконтакте за организацию и проведение подобных конкурсов. Ведь они мотивируют/стимулируют программистов начинать разрабатывать под новые для них перспективные платформы (мне почему-то кажется что среди участников много новичков по отношению к платформе). Очень порадовали входные данные для конкурса — все изображения были как на подбор. Ни одного лишнего пикселя нигде не торчало…
ссылка на оригинал статьи http://habrahabr.ru/post/154209/
Добавить комментарий