2010-01-16 6 views
11

Ich habe Probleme damit, Teile einer App zu schreiben, die sich wie die native iPhone Photo App verhalten sollte. Blick auf iPhone SDK App Entwicklungsbuch von Orielly, die einen Beispiel-Code für die Umsetzung dieser sogenannten Seiten-Flicking gab. Der Code dort erstellte zunächst alle Unteransichten und versteckte sie dann. Zu einer gegebenen Zeit sind nur 3 Untersichten sichtbar, die Ruhe ist verborgen. Nach viel Mühe habe ich mit der App gearbeitet, die damals nur 15 Seiten umfasste.Wie implementiert man UIScrollView mit mehr als 1000 Subviews?

Sobald ich 300 Seiten hinzugefügt habe, wurde klar, dass es Leistungs-/Speicherprobleme bei diesem Ansatz der Vorabzuordnung von so vielen Unteransichten gibt. Dann dachte ich vielleicht, dass ich für meinen Fall nur drei Unteransichten zuordnen sollte, anstatt sie zu verstecken/unsichtbar zu machen. Möglicherweise sollte ich Subviews nur zur Laufzeit entfernen/hinzufügen. Kann aber nicht herausfinden, ob UIScrollView Inhalte dynamisch aktualisieren kann. Zum Beispiel gibt es am Anfang 3 Frames mit unterschiedlichen X-Offsets (0, 320, 640) vom Bildschirm, wie von UIScrollView verstanden. Sobald der Benutzer auf die dritte Seite wechselt, stelle ich sicher, dass ich die 4. Seite hinzufügen und die 1. Seite entfernen kann, und trotzdem wird UIScrollView nicht durcheinander gebracht?

In der Hoffnung, es gibt eine Standardlösung für diese Art von Problem ... kann jemand führen?

+1

keine konkrete Antwort für Sie, aber diese Art von Problem wird oft mit dem Fliegengewicht Muster gelöst: http://en.wikipedia.org/wiki/Flyweight_pattern –

+0

Wer kommt jetzt zu dieser Frage - verwenden Sie die 'UICollectionView' ... es war zur Zeit dieser Frage nicht verfügbar, aber es ist jetzt und wird dies für Sie tun. –

Antwort

7

UIScrollView ist nur eine Unterklasse von UIView, so dass Subviews zur Laufzeit hinzugefügt und entfernt werden können. Angenommen, Sie haben Fotos mit fester Breite (320px) und 300 davon, dann wäre Ihre Hauptansicht 300 * 320 Pixel breit. Wenn Sie die Bildlaufansicht erstellen, initialisieren Sie den Rahmen so weit.

Der Rahmen der Bildlaufansicht hätte also die Dimensionen (0, 0) bis (96000, 480). Wenn Sie eine Unteransicht hinzufügen, müssen Sie den Rahmen ändern, damit er an der richtigen Position in der übergeordneten Ansicht angezeigt wird.

Also sagen wir, wir fügen das 4. Foto der Bildlaufansicht hinzu. Es ist Frame von (960, 480) bis (1280, 480). Das lässt sich leicht berechnen, wenn Sie jedem Bild irgendwie einen Index zuordnen können. Dann ist diese verwenden das Bild des Rahmens zu berechnen, wo Indizes bei 0 beginnen:

Top-Left -- (320 * (index - 1), 0) 

zu

Bottom-Right -- (320 * index, 480) 

das erste Bild/subview Entfernen sollte einfach sein. Behalten Sie ein Array der 3 Teilansichten, die gerade auf dem Bildschirm angezeigt werden. Wenn Sie dem Bildschirm eine neue Unteransicht hinzufügen, fügen Sie sie auch am Ende dieses Arrays hinzu und entfernen Sie dann auch die erste Unteransicht in diesem Array vom Bildschirm.

+0

Was Sie sagen, macht Sinn. Lass mich es versuchen und dann diesen Thread aktualisieren. – climbon

+1

Habe es jetzt funktioniert. Danke für die Klärung, obwohl ich es etwas anders gemacht habe (um Änderungen am bestehenden Code zu minimieren). Mein existierender Code (geliehen von Beispiel in dem Buch, das ich erwähnte) hatte Breite des Rahmens meines scrollViews = 320x3. Mit der myScrollView.contentOffset-Eigenschaft steuert der Code, welche der drei Ansichten angezeigt werden soll. Früher enthielt mein Array 300 Ansichten (beim Start zugewiesen) jetzt enthält es nur 3. Wenn Benutzer auf die 3. Seite kommt, entferne ich die erste Seite und füge Vierte ein und passe die Sichtbarkeit mit der contentOffset-Eigenschaft an. Wenn er zu 1. kommt, entferne ich 3. und führe eins vor 1. usw. ein. – climbon

+0

Ich wusste nichts über die contentOffset-Eigenschaft. Ich brauche etwas, das dem kontinuierlichen Scrollen für meine Apps ähnlich ist, und werde sicher das verwenden – Anurag

15

Nach dem, was gesagt wurde, können Sie Tausende von Elementen mit nur einer begrenzten Menge an Ressourcen zeigen (und ja, es ist in der Tat ein bisschen wie ein Flyweight-Muster). Hier ist ein Code, der Ihnen helfen kann, das zu tun, was Sie wollen.

Die UntitledViewController-Klasse enthält nur ein UIScroll und legt sich selbst als ihren Delegaten fest. Wir haben ein NSArray mit NSString-Instanzen als Datenmodell (möglicherweise sind Tausende von NSStrings darin enthalten), und wir möchten jedes in einem UILabel mit horizontalem Scrolling anzeigen. Wenn der Benutzer scrollt, verschieben wir die UILabels so, dass eine nach links, eine nach rechts verschoben wird, sodass alles für das nächste Scroll-Ereignis bereit ist.

Hier ist die Schnittstelle, ziemlich einfach:

@interface UntitledViewController : UIViewController <UIScrollViewDelegate> 
{ 
@private 
    UIScrollView *_scrollView; 

    NSArray *_objects; 

    UILabel *_detailLabel1; 
    UILabel *_detailLabel2; 
    UILabel *_detailLabel3; 
} 

@end 

Und hier ist die Implementierung für diese Klasse:

@interface UntitledViewController() 
- (void)replaceHiddenLabels; 
- (void)displayLabelsAroundIndex:(NSInteger)index; 
@end 

@implementation UntitledViewController 

- (void)dealloc 
{ 
    [_objects release]; 
    [_scrollView release]; 
    [_detailLabel1 release]; 
    [_detailLabel2 release]; 
    [_detailLabel3 release]; 
    [super dealloc]; 
} 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    _objects = [[NSArray alloc] initWithObjects:@"first", @"second", @"third", 
       @"fourth", @"fifth", @"sixth", @"seventh", @"eight", @"ninth", @"tenth", nil]; 

    _scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 460.0)]; 
    _scrollView.contentSize = CGSizeMake(320.0 * [_objects count], 460.0); 
    _scrollView.showsVerticalScrollIndicator = NO; 
    _scrollView.showsHorizontalScrollIndicator = YES; 
    _scrollView.alwaysBounceHorizontal = YES; 
    _scrollView.alwaysBounceVertical = NO; 
    _scrollView.pagingEnabled = YES; 
    _scrollView.delegate = self; 

    _detailLabel1 = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 460.0)]; 
    _detailLabel1.textAlignment = UITextAlignmentCenter; 
    _detailLabel1.font = [UIFont boldSystemFontOfSize:30.0]; 
    _detailLabel2 = [[UILabel alloc] initWithFrame:CGRectMake(320.0, 0.0, 320.0, 460.0)]; 
    _detailLabel2.textAlignment = UITextAlignmentCenter; 
    _detailLabel2.font = [UIFont boldSystemFontOfSize:30.0]; 
    _detailLabel3 = [[UILabel alloc] initWithFrame:CGRectMake(640.0, 0.0, 320.0, 460.0)]; 
    _detailLabel3.textAlignment = UITextAlignmentCenter; 
    _detailLabel3.font = [UIFont boldSystemFontOfSize:30.0]; 

    // We are going to show all the contents of the _objects array 
    // using only these three UILabel instances, making them jump 
    // right and left, replacing them as required: 
    [_scrollView addSubview:_detailLabel1]; 
    [_scrollView addSubview:_detailLabel2]; 
    [_scrollView addSubview:_detailLabel3]; 

    [self.view addSubview:_scrollView]; 
} 

- (void)viewDidAppear:(BOOL)animated 
{ 
    [super viewDidAppear:animated]; 
    [_scrollView flashScrollIndicators]; 
} 

- (void)viewWillAppear:(BOOL)animated 
{ 
    [super viewWillAppear:animated]; 
    [self displayLabelsAroundIndex:0]; 
} 

- (void)didReceiveMemoryWarning 
{ 
    // Here you could release the data source, but make sure 
    // you rebuild it in a lazy-loading way as soon as you need it again... 
    [super didReceiveMemoryWarning]; 
} 

#pragma mark - 
#pragma mark UIScrollViewDelegate methods 

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView 
{ 
    // Do some initialization here, before the scroll view starts moving! 
} 

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 
{ 
    [self replaceHiddenLabels]; 
} 

- (void)displayLabelsAroundIndex:(NSInteger)index 
{ 
    NSInteger count = [_objects count]; 
    if (index >= 0 && index < count) 
    { 
     NSString *text = [_objects objectAtIndex:index]; 
     _detailLabel1.frame = CGRectMake(320.0 * index, 0.0, 320.0, 460.0); 
     _detailLabel1.text = text; 
     [_scrollView scrollRectToVisible:CGRectMake(320.0 * index, 0.0, 320.0, 460.0) animated:NO]; 

     if (index < (count - 1)) 
     { 
      text = [_objects objectAtIndex:(index + 1)]; 
      _detailLabel2.frame = CGRectMake(320.0 * (index + 1), 0.0, 320.0, 460.0); 
      _detailLabel2.text = text; 
     } 

     if (index > 0) 
     { 
      text = [_objects objectAtIndex:(index - 1)]; 
      _detailLabel3.frame = CGRectMake(320.0 * (index - 1), 0.0, 320.0, 460.0); 
      _detailLabel3.text = text; 
     } 
    } 
} 

- (void)replaceHiddenLabels 
{ 
    static const double pageWidth = 320.0; 
    NSInteger currentIndex = ((_scrollView.contentOffset.x - pageWidth)/pageWidth) + 1; 

    UILabel *currentLabel = nil; 
    UILabel *previousLabel = nil; 
    UILabel *nextLabel = nil; 

    if (CGRectContainsPoint(_detailLabel1.frame, _scrollView.contentOffset)) 
    { 
     currentLabel = _detailLabel1; 
     previousLabel = _detailLabel2; 
     nextLabel = _detailLabel3; 
    } 
    else if (CGRectContainsPoint(_detailLabel2.frame, _scrollView.contentOffset)) 
    { 
     currentLabel = _detailLabel2; 
     previousLabel = _detailLabel1; 
     nextLabel = _detailLabel3; 
    } 
    else 
    { 
     currentLabel = _detailLabel3; 
     previousLabel = _detailLabel1; 
     nextLabel = _detailLabel2; 
    } 

    currentLabel.frame = CGRectMake(320.0 * currentIndex, 0.0, 320.0, 460.0); 
    currentLabel.text = [_objects objectAtIndex:currentIndex]; 

    // Now move the other ones around 
    // and set them ready for the next scroll 
    if (currentIndex < [_objects count] - 1) 
    { 
     nextLabel.frame = CGRectMake(320.0 * (currentIndex + 1), 0.0, 320.0, 460.0); 
     nextLabel.text = [_objects objectAtIndex:(currentIndex + 1)]; 
    } 

    if (currentIndex >= 1) 
    { 
     previousLabel.frame = CGRectMake(320.0 * (currentIndex - 1), 0.0, 320.0, 460.0); 
     previousLabel.text = [_objects objectAtIndex:(currentIndex - 1)]; 
    } 
} 

@end 

hoffe, das hilft!

+0

Danke Adrian für dieses Codebeispiel auch. Das nächste Mal oder wenn ich eine Chance bekomme, meinen Code sauberer zu machen, würde ich wahrscheinlich auch auf dieses Stück verweisen. – climbon

+0

Hallo @akosma, Ich habe Ihren obigen Code implementiert, aber ich brauche auch unendliches Scrollen in beiden Seiten bedeutet, wenn ich in 0 Position bin und rechts streichen dann auf 10 Position bewegen. Kannst du mir helfen? Wie kann ich diese Funktionalität in Ihrem Code implementieren? –

6

Vielen Dank an Adrian für sein sehr einfaches und leistungsstarkes Codebeispiel. Es gab nur ein Problem mit diesem Code: wenn der Benutzer einen "doppelten Bildlauf" machte (I. E., wenn er nicht auf das Stoppen der Animation wartete und die Bildansicht immer wieder neu scrollte).

In diesem Fall ist die Aktualisierung für die Position der 3 Teilansichten nur wirksam, wenn die Methode "scrollViewDidEndDecelerating" aufgerufen wird und das Ergebnis eine Verzögerung vor dem Erscheinen der Teilansichten auf dem Bildschirm ist.

in der Schnittstelle, fügen Sie einfach diese:

int refPage, currentPage; 

in der Umsetzung, initialisieren refPage und current in den "viewDidLoad" Verfahren wie

Dies kann leicht durch Zugabe von wenigen Zeilen Code vermieden werden dies:

refpage = 0; 
curentPage = 0; 

in der Umsetzung, fügen Sie einfach die "scrollViewDidScroll" Methode, wie folgt aus:

- (void)scrollViewDidScroll:(UIScrollView *)sender{ 
    int currentPosition = floor(_scrollView.contentOffset.x); 
    currentPage = MAX(0,floor(currentPosition/340)); 
//340 is the width of the scrollview... 
    if(currentPage != refPage) { 
     refPage = currentPage; 
     [self replaceHiddenLabels]; 
     } 
    } 

und voilà!

Jetzt werden die Unteransichten korrekt an den richtigen Positionen ersetzt, auch wenn der Benutzer die Animation nie beendet und die Methode "scrollViewDidEndDecelerating" nie aufgerufen wird!

+0

Hallo @Chrysotribax, ich habe oben Code verwendet und es hat gut funktioniert, aber ich muss es unendlich scrollen machen bedeutet Benutzer Daten wischen und das letzte Objekt zu erreichen es wird zu ersten Daten gehen und wenn es rechts wischen wird, dann wird es das letzte Objekt erreichen. Kannst du mir bitte helfen ? –

Verwandte Themen