34

Próbuję utworzyć interfejs podobny do trampoliny w mojej aplikacji. Próbuję użyć UIButtons dodane do UIScrollView. Problem, na który napotykam, polega na tym, że przyciski nie przechodzą żadnych zmian do UIScrollView - jeśli spróbuję przesuwać/ślizgać się i zdarzyło mi się nacisnąć przycisk, nie rejestruje się on do UIScrollView, ale jeśli poruszę spację między przyciski to zadziała. Przyciski działają/klikam, jeśli ich dotknę.UIScrollview z UIButtons - jak odtworzyć trampolinę?

Czy istnieje właściwość lub ustawienie wymuszające na przycisku wysyłanie zdarzeń dotyku do jego rodzica (superview)? Czy przed dodaniem UIScrollView należy dodać przyciski do czegoś innego?

Oto mój kod:

//init scrolling area 
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 480, 480)]; 
scrollView.contentSize = CGSizeMake(480, 1000); 
scrollView.bounces = NO; 
scrollView.delaysContentTouches = NO; 

//create background image 
UIImageView *rowsBackground = [[UIImageView alloc] initWithImage:[self scaleAndRotateImage:[UIImage imageNamed:@"mylongbackground.png"]]]; 
rowsBackground.userInteractionEnabled = YES; 

//create button 
UIButton *btn = [[UIButton buttonWithType:UIButtonTypeCustom] retain]; 
btn.frame = CGRectMake(100, 850, 150, 150); 
btn.bounds = CGRectMake(0, 0, 150.0, 150.0); 
[btn setImage:[self scaleAndRotateImage:[UIImage imageNamed:@"basicbutton.png"]] forState:UIControlStateNormal]; 
[btn addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside]; 

//add "stuff" to scrolling area 
[scrollView addSubview:rowsBackground]; 
[scrollView addSubview:btn]; 

//add scrolling area to cocos2d 
//this is just a UIWindow 
[[[Director sharedDirector] openGLView] addSubview:scrollView]; 

//mem-mgmt 
[rowsBackground release]; 
[btn release]; 
[scrollView release]; 
+0

Należy zauważyć, że choć Apple wolno go wcześniej, '' 10.4' Aplikacje, które tworzą alternatywne środowiska pulpit/home lub symulują wielowątkowych aplikacji widgetów doświadczenia będą rejected'. Na wypadek, gdyby niektórzy nowoprzybyli planowali wysłać aplikację produkcyjną, która ma własną trampolinę. – user

Odpowiedz

28

Aby określić różnicę między kliknięciem przechodzącym do widoku treści i dotknięciem, które zamienia się w przesunięcie lub szczypanie, musi opóźnić dotyk i sprawdzić, czy palec został przesunięty podczas to opóźnienie. Ustawiając delaysContentTouches na NO w powyższym przykładzie, uniemożliwiasz to. W związku z tym widok przewijania zawsze przesuwa dotknięcie przycisku, zamiast go anulować, gdy okaże się, że użytkownik wykonuje gest machnięcia. Spróbuj ustawić delaysContentTouches na YES.

Warto również strukturalnie dodać wszystkie widoki, które będą hostowane w widoku przewijania do wspólnego widoku treści i używać tylko tego widoku jako widoku podrzędnego widoku przewijania.

+3

Jednym z możliwych problemów z wykonaniem opóźnieńContentTouches jest dodanie opóźnienia przed podświetleniem przycisku. Odpowiedź Romana K poniżej pozwoli ci nie mieć opóźnienia, ale nadal będzie w stanie przewijać po naciśnięciu przycisku. – GhostM

+0

Porady dotyczące konstrukcji są niesamowite, ale chciałbym wiedzieć dlaczego. Dzięki. –

1

UIScrollView obsługuje wiele o sobie zdarzeń. Musisz ręcznie obsłużyć touchchesDidEnd i test trafień dla przycisków wewnątrz UIScrollView.

0

Innym sposobem jest:
1. Zastąpić przycisk de przez proste niestandardowe UIView
2. Umieść flagę "userInterationEnable = yes;" w metodzie init
3. W widoku nadpisania metody UIResponder "touchesEnded" w tym miejscu można wywołać akcję, której potrzebujesz, jak przycisk.

0

Z mojego doświadczenia wynika, że ​​pierwsza odpowiedź, tj. Po prostu ustawienie delaysContentTouches na YES, nie zmienia niczego w odniesieniu do problemu. Przyciski nadal nie dostarczają wyników śledzenia do widoku przewijania. Trzecia odpowiedź jest zarówno prosta, jak i bardzo użyteczna. Dzięki sieroaoj!

Jednak w przypadku trzeciej odpowiedzi do pracy trzeba również ustawić delaysContentTouches na YES. W innym przypadku metoda touchesEnded będzie również wywoływana w celu śledzenia w widoku. Dlatego mogę rozwiązać ten problem:

  1. Zastępstwo de przycisku zwykłą niestandardowego UIView
  2. umieścić flagę "userInterationEnable = yes;" na metodzie init,
  3. W widoku zastąpić metodę UIResponder „touchesEnded” tu można wywołać akcję

czwarty. ustawić delaysContentTouches do YES

+0

opóźnieniaContentsTouches = YES domyślnie. – Andy

1

OK tu jest odpowiedź:

podklasa UIButton. (UWAGA: zadzwoń [super ....] na początku każdego przesłonięcia.

  • Dodaj obiekt. Jeden z typu BOOL (o nazwie enableToRestore)
  • Dodaj obiekt. Jeden z rodzaju CGPoint (tzw startTouchPosition)
  • w awakeFromNib i initWithFrame ustaw enableToRestore do IsEnabled mienia)
  • Override "touchesBegan: withEvent:" przechowywać rozpoczęcia dotykowym pozycji.
  • Zastąp "touchesMoved: withEvent:" na sprawdź, czy jest ruch poziomy .
  • Jeśli TAK, ustaw włączony na NIE i wybrany na NIE.

Przykładowy kod:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
{ 
    UITouch *touch = [touches anyObject]; 

    [super touchesBegan:touches withEvent:event]; 
    [self setStartTouchPosition:[touch locationInView:self]]; 
} 


// 
// Helper Function 
// 
- (BOOL)isTouchMovingHorizontally:(UITouch *)touch 
{ 
    CGPoint currentTouchPosition = [touch locationInView:self]; 
    BOOL  rValue = NO; 

    if (fabsf([self startTouchPosition].x - currentTouchPosition.x) >= 2.0) 
    { 
     rValue = YES; 
    } 

    return (rValue); 
} 

// 
// This is called when the finger is moved. If the result is a left or right 
// movement, the button will disable resulting in the UIScrollView being the 
// next responder. The parrent ScrollView will then re-enable the button 
// when the finger event is ended of cancelled. 
// 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 
{ 
    [super touchesMoved:touches withEvent:event]; 
    if ([self isTouchMovingHorizontally:[touches anyObject]]) 
    { 
     [self setEnabled:NO]; 
     [self setSelected:NO]; 
    } 
} 

ten aktywuje UIScrollView.

Podklasa UIScrollView. (UWAGA: call [Super ....] na początku każdego ręcznym

  • zastępują zarówno "touchesEnded: withEvent". I "touchesCancelled: withEvent:"
  • W ręcznym, wyzerować wszystkie subviews (i ich subviews) włączona flagę
  • . UWAGA: Za pomocą kategorii i dodać metodę UIView.

- (void) restoreAllEnables 
{ 
    NSArray *views = [self subviews]; 

    for (UIView *aView in views) 
    { 
     if ([aView respondsToSelector:@selector(restoreEnable)]) 
     { 
      [aView restoreEnable]; 
     } 
    } 
} 

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 
{ 
    [super touchesEnded:touches withEvent:event]; 
    [self restoreAllEnables]; 
} 

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event 
{ 
    [super touchesEnded:touches withEvent:event]; 
    [self restoreAllEnables]; 
} 
  • W kategorii:

.

-(void) restoreEnable 
{ 
    NSArray *views = [self subviews]; 

    if ([self respondsToSelector:@selector(enableToRestore)]) 
    { 
     [self setEnabled:[self enableToRestore]]; 
    } 

    for (UIView *aView in views) 
    { 
     if ([aView respondsToSelector:@selector(restoreEnable)]) 
     { 
      [aView restoreEnable]; 
     } 
    } 
} 

EDIT Uwaga: Nigdy nie dostałem odpowiedzi od 3 do pracy. Podobnie: zestaw setDelaysContentTouches: NO (ustawiony w kontrolerze widoku lub w innym miejscu) ma być ustawiony dla uzyskania najlepszych rezultatów w odpowiedzi 4. Zapewnia to bardzo szybką reakcję na przyciski. Ustawienie setDelaysContentTouches: YES wywiera poważny wpływ (150ms) na czas reakcji na przyciski i sprawia, że ​​lekkie i szybkie dotykanie nie jest możliwe.

5

Mam podobny przypadek, że liczba przycisków na UIScrollView, i chcę, aby przewinąć te przyciski. Na początku podklasowałem zarówno UIScrollView, jak i UIButton. Zauważyłem jednak, że mój subklasowy UIScrollView nie otrzymał zdarzenia touchesEnded, więc zmieniłem go tylko na UCLutton podklasy.


@interface MyPhotoButton : UIButton { 
} 

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; 
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; 
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; 
@end 

@implementation MyPhotoButton 

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { 
    [super touchesMoved:touches withEvent:event]; 
    [self setEnabled:NO]; 
    [self setSelected:NO]; 
} 

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 
    [super touchesEnded:touches withEvent:event]; 
    [self setEnabled:YES]; 
} 

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { 
    [super touchesCancelled:touches withEvent:event]; 
    [self setEnabled:YES]; 
} 

@end 
+0

To rozwiązanie sprawdziło się doskonale. – GroovyCarrot

44

Rozwiązanie że pracował dla mnie były:

  1. Ustawianie canCancelContentTouches w UIScrollView do YES.
  2. Poszerzenie UIScrollView, aby zastąpić touchesShouldCancelInContentView:(UIView *)view, aby zwrócić YES, gdy view jest UIButton. .

Według dokumentacji touchesShouldCancelInContentView zwraca "YES anulować dalszych wiadomości Dotknij, aby zobaczyć, NO mieć pogląd nadal otrzymywać te wiadomości Domyślna wartość zwracana jest YES jeśli widok nie jest UIControl obiekt, w przeciwnym razie zwraca NO . "

Od UIButton jest UIControl rozszerzenie jest konieczne, aby uzyskać efekt canCancelContentTouches, który umożliwia przewijanie.

+8

Dziękuję bardzo! Aby wyjaśnić, działa to najlepiej, gdy opóźnieniaContentTouches = NO. – Schrockwell

+7

To jest droga! (przynajmniej dla iOS 4+) Stwierdziłem, że najbardziej uniwersalne jest przesłonięcie w ten sposób: '- (BOOL) touchesShouldCancelInContentView: (UIView *) view { return! [view isKindOfClass: [klasa UISlider]]; } ' –

+0

Nadal działa idealnie w iOS 7. Wielkie dzięki! – RyanG