Czasami w aplikacjach pojawia się potrzeba by pewna interakcja z aplikacją była potwierdzona przez użytkownika. W szczególności jeżeli  ma daleko posunięte efekty na przykład usunięcie dużej ilości danych. Można użytkownika zabezpieczyć przed przypadkowym kliknięciem za pomocą tzw. długiego przyciśnięcia. By taka akcja była zrozumiała dla użytkownika dobrze jest przedstawić mu to graficznie. Stąd na przycisku musi pojawić się informacja, że przycisk reaguje na długie przyciśnięcie na przykład za pomocą  tekstu. Oprócz tego ważne jest by pokazać użytkownikowi informację o upływającym czasie i ile zostało do końca. Poniżej pokażę przykładową realizację tej idei na platformie iOS poniżej.

Algorytm

Schemat realizacji tej funkcjonalności jest prosty. Po pierwsze mamy przezroczysty przycisk, który jest na wierzchu i ma napis „Long press”. Pod spodem mamy tło przycisku  – w przykładowej aplikacji jest to czerwony przycisk. Natomiast na nim położona jest warstwa która przykryje stały przycisk i będzie rosła od początku przyciśnięcia (od wysokości 0). Dzięki takiemu ułożeniu warstw animacja będzie pod spodem tekstu przycisku. Pozostaje oprogramowanie naciśnięcia oraz przerwania procesu przez użytkownika.

Elementami ukrytymi przed użytkownikiem jest to, że po dojechaniu animacji do 4/5 kończy się już sama. Zapobiega to przypadkowemu przerwaniu na samym końcu animacji. Zbyt wczesne oderwanie palca powoduje powrót do stanu poprzedniego.

Implementacja

Realizacja zadania odbywa się w dwóch prostych funkcjach realizujących animację odpowiedniej warstwy. Reszta to połączenia między kodem a interfejsem. I tak na zdarzenie Touch Down na przycisku trzeba rozpocząć animację o ile żadna inna w danej chwili nie jest uruchomiona. Co sprawdzamy własną właściwością w klasie canBeAnimated, ustawioną na YES w viewDidLoad. Definiujemy i uruchamiamy animację wzrostu zielonego tła (greenButtonImage) do 4/5 wysokości.

- (IBAction)longPress:(UIButton *)sender
{
    if(!canBeAnimated) {
        return;
    }
    [UIView animateWithDuration:1.6
                          delay:0.0
                        options: UIViewAnimationOptionCurveLinear
                     animations:^{
                         greenButtonImage.frame =
                             CGRectMake(
                                 greenButtonImage.frame.origin.x,
                                 greenButtonImage.frame.origin.y - BUTTON_HEIGHT * 0.8,
                                 greenButtonImage.frame.size.width,
                                 BUTTON_HEIGHT * 0.8
                             );
                     }
                     completion:^(BOOL finished){
                         NSLog(@"Done animation");
                         if(finished) {
                           ...
                         }

                     }];
    canBeAnimated = NO;
}

Czas ustawiłem na 1,6 s co wydaje się optymalną wartością. Dodatkowo trzeba dodać reakcję w chwili, gdy animacja jest zakończona i nie została przerwana, co możemy sprawdzić za pomocą zmiennej finished. Dla takiego zdarzenia pozostaje uruchomić pozostałą część animacji oraz  zrealizować zadanie, które wyzwalał ten przycisk. W moim przypadku jest to po prostu wyświetlenie okna dialogowego.

...
     NSLog(@"Done animation");
     if(finished) {
         NSLog(@"Run rest of animation");
         animationFinished = YES;
         [UIView animateWithDuration:0.4
                               delay:0.0
                             options: UIViewAnimationOptionCurveLinear
                          animations:^{
                                           greenButtonImage.frame =
                                           CGRectMake(
                                                      greenButtonImage.frame.origin.x,
                                                      greenButtonImage.frame.origin.y - BUTTON_HEIGHT * 0.2,
                                                      greenButtonImage.frame.size.width,
                                                      BUTTON_HEIGHT
                                                      );
                                      }
                           completion:nil
         ];

         UIAlertView *av =[[UIAlertView alloc] initWithTitle:@"Run"
                                                     message:@"Succes!"
                                                    delegate:nil
                                           cancelButtonTitle:nil
                                           otherButtonTitles:@"OK",nil];
         [av show];
}
...

Ostatnim elementem do obsłużenia pozostaje anulowanie animacji w chwili oderwania palca od ekranu. Realizujemy to na zdarzenie Touch Up Inside wywołane na przycisku. Wystarczy, że przerwiemy animacje o ile animacja do 4/5 wysokości nie jest oznaczona w aplikacji jako zakończona. Kod takiego zdarzenia jest bardzo prosty:

- (IBAction)cancelLongPress:(UIButton *)sender
{
    if (animationFinished == NO) {
        NSLog(@"Cancel animation");
        [greenButtonImage.layer removeAllAnimations];
        greenButtonImage.frame =
        CGRectMake(
                   redButtonImage.frame.origin.x,
                   redButtonImage.frame.origin.y+BUTTON_HEIGHT,
                   redButtonImage.frame.size.width,
                   0.f
                   );
        canBeAnimated = YES;
    }
}

 Podsumowanie

Jak widać realizacja prostego efektu z długim przyciśnięciem nie jest trudna. A efekt końcowy jest przyjemny dla użytkownika. Kwestia jaka nie została tutaj zrealizowana to powrót po akcji do stanu poprzedniego. Jednak to zależy od tego gdzie i do czego służy taki Long Press. Można na przykład od razu na zakończenie przywracać stan początkowy lub zrealizować to z opóźnieniem za pomocą metody:

[self performSelector:@selector(bringButtonsBack)
           withObject:self
           afterDelay:2];

Pozostawiam tę kwestię czytelnikom.

P.S. Pełen kod aplikacji możecie pobrać z GitHuba.