Популярность приложений для обработки фотографий постоянно растет. Недавно мы предлагали вниманию наших читателей гайд для новичков по написанию собственного фоторедактора. Сегодня мы хотели бы поделиться с хабровчанами опытом наших партнеров – разработчиков компании New Technologies, – полученным в ходе работы над апдейтом их приложения. Этот материал может быть полезен как при работе с приложением-фоторедактором, так и с проектами, в которых обработка изображений является побочной функцией (собственно, в случае, о котором пойдет речь, так оно и было).

«В ходе проектирования My Wardrobe — приложения для хранения коллекции одежды и образов — нашей команде неоднократно приходилось решать вопросы удобства интерфейса: находить баланс между множеством функций и легкостью доступа к ним. Помимо хранения всей одежды из своего гардероба, пользователю предоставлялась возможность создать образ из комбинации вещей и прикрепить к нему фотографию. Однако уже на этапе проектирования было ясно, что, возможно, пользователь не захочет делать новое фото своего образа, а предпочтет создать иконку из фотографий одежды, расположив их на однотонном фоне — это устоявшаяся практика изображения образа в мире моды.



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

Постановка задачи


Задача состоит в предоставлении пользователю следующих функций:

  • добавление картинок в заранее подготовленный шаблон коллажа;
  • изменение размера, поворот, перемещение картинок внутри области коллажа;
  • возможность поменять картинки местами;
  • удаление картинок.


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

Реализация


Для реализации данного интерфейса были созданы 2 контроллера (UIViewController): Контроллер коллажа и Контроллер элемента коллажа. При этом Контроллер коллажа может содержать любую конфигурацию контейнеров с элементами.


На данном этапе такой конфигурации будет достаточно.

Интерфейс Контроллера коллажа также будет достаточно простым: делегат и метод для получения картинок от клиента.

@protocol CollageVCDelegate;

@interface CollageVC : UIViewController
    @property (weak, nonatomic) id<CollageVCDelegate> delegate;
    - (void)imageForIndex:(NSUInteger)index image:(UIImage*)image;
@end

@protocol CollageVCDelegate <NSObject>
    - (void)collageVCNeedsImageForIndex:(NSUInteger)index sender:(CollageVC*)sender;
@end

Получение изображения готового коллажа — отдельная задача, поэтому на данном этапе эта функция не включена в интерфейс.

Поворот, перемещение, увеличение картинок внутри элемента коллажа


Данные возможности реализованы в Контроллере элемента коллажа. Все перемещения изображения сделаны с помощью UIGestureRecognizer. Достаточно разместить в контроллере View и добавить к ней 3 распознавателя жестов: UIRotationGestureRecognizer. UIPinchGestureRecognizer, UIPanGestureRecognizer.

-(void)addGestureActions:(CollageImageView*)view{
    UIRotationGestureRecognizer* rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotate:)];
    UIPinchGestureRecognizer* pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinch:)];
    UIPanGestureRecognizer* panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [self.imageFrameView addGestureRecognizer:rotationGestureRecognizer];
    [self.imageFrameView addGestureRecognizer:pinchGestureRecognizer];
    [self.imageFrameView addGestureRecognizer:panGestureRecognizer];
    panGestureRecognizer.cancelsTouchesInView = NO;
    panGestureRecognizer.maximumNumberOfTouches = 1;
    rotationGestureRecognizer.delegate = self;
    pinchGestureRecognizer.delegate = self;
    panGestureRecognizer.delegate = self;
}

Они будут вызывать методы, в которых будут проведены соответствующие афинные преобразования для перемещения элементов.

Возможность поменять картинки местами


Функция перестановки 2-х изображений на место друг друга будет выполняться на уровне Контроллера коллажа.

Для этого он также должен отслеживать жесты пользователя. Чтобы инициировать процесс перестановки, достаточно отследить момент, когда пользователь, перемещая картинку, передвинул палец на область с другой картинкой. Для этой цели использовались методы UIResponder, передающие UITouches.

Поскольку UITouches отлавливаются с помощью UIGestureRecognizer в Контроллерах элементов коллажа, для них необходимо установить свойство cancelsTouchesInView = NO. Это достаточно сделать только для UIPanGestureRecognizer, так как требуется отслеживать только UITouches, связанные с перемещением картинки.

События touches могут быть вызваны одновременными нажатиями на разные области коллажа, поэтому необходимо исключить обработку ненужных событий. Это можно сделать путём сохранения указателя на элемент коллажа с которым ведётся взаимодействие в методе touchesBegan:withEvent:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    UITouch* touch = [touches anyObject];
    CGPoint location = [touch locationInView:self.view];
    [collageItems enumerateObjectsUsingBlock:^(CollageItem * object, NSUInteger idx, BOOL *stop) {
        //проверка попадания точки нажатия на элемент коллажа
        if ([object.imageFrameView pointInside:[self.view convertPoint:location toView:object.imageFrameView] withEvent:event]) {
            [self startedInteractionInItem:object];
            *stop = YES;
        }
    }];
}

- (void)startedInteractionInItem:(CollageItem*)item{
    if (interactingItem == nil) {
        interactingItem = item;
    }
}

Метод touchesMoved вызывает проверку, не хочет ли пользователь поменять картинки местами. Здесь необходимо определить, не передвинул ли пользователь изображение в другой элемент коллажа. Если это произошло, нужно запомнить текущее состояние элементов коллажа и отобразить пользователю результат перестановки изображений. Если после этого пользователь снова поместил изображение в стартовую область, нужно вернуть состояние элементов обратно.

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    UITouch* touch = [touches anyObject];
    CGPoint location = [touch locationInView:self.view];
        if ([touch.view isEqual:interactingItem.imageView] || interactingItem == nil) {
            [collageItems enumerateObjectsUsingBlock:^(CollageItem * object, NSUInteger idx, BOOL *stop) {
                if ([object.imageFrameView pointInside:[self.view convertPoint:location toView:object.imageFrameView] withEvent:event]) {
                    [self processInteractionInItem:object];
                    *stop = YES;
                }
            }];
        }
}

- (void)processInteractionInItem:(CollageItem*)item{
        if ([interactingItem isEqual:item]) {
            if (swapTargetItem) {
                 [self cancelSwap];
            }
        } else {
            if (![swapTargetItem isEqual:item]) {
                [self prepareSwapWith:item];
            }
        }
}

В методе touchesEnded:withEvent: применяется состояние после перестановки картинок, указатель на элемент взаимодействия освобождается.

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    UITouch* touch = [touches anyObject];
    CGPoint location = [touch locationInView:self.view];
    if (interactingItem) {
        if ([touch.view isEqual:interactingItem.imageView]) {
            [collageItems enumerateObjectsUsingBlock:^(CollageItem * object, NSUInteger idx, BOOL *stop) {
                if ([object.imageFrameView pointInside:[self.view convertPoint:location toView:object.imageFrameView] withEvent:event]) {
                    [self finishedInteractionInItem:object];
                    *stop = YES;
                }
            }];
        }
    }
}

- (void)finishedInteractionInItem:(CollageItem*)item{
    if (![interactingItem isEqual:item]) {
        [self applySwap];
    }
    [self unlockItems];
    interactingItem = nil;
}

Таким образом, мы избегаем конфликтов обработки разных жестов.

Заключение


На выходе мы имеем интерфейс для удобного создания и редактирования коллажей.
При использовании разработанного таким образом редактора не возникает коллизий между жестами, управление интуитивно понятно, а отсутствие лишних кнопок и состояний экрана облегчает процесс взаимодействия с приложением для пользователя. Мы планируем совершенствовать полученный компонент и в дальнейшем внедрять его в другие проекты компании».
Поделиться с друзьями
-->

Комментарии (2)


  1. IvanVorobei
    20.04.2017 12:30

    Спасибо за статью! Но как по мне, выглядит для компонента слабо.

    Было бы отлично расширить функционал лейатуа, и по возможности предоставить легко-кастомизируемое (эдакий генератор) решение.
    А еще, чтобы этот лейаут можно было менять после того, как он появился на экране. Конечно же анимировано


    1. elder_cat
      20.04.2017 13:02

      Спасибо за комментарий!
      В данный момент функциональность компонента дорабатывается и расширяется. Разработчики подумают над идеей предоставления пользователю возможности изменить лейаут.