2010-01-16 29 views
11

Tôi đang vật lộn với phần viết của một ứng dụng sẽ hoạt động giống như ứng dụng ảnh iphone gốc. Nhìn vào cuốn sách phát triển ứng dụng sdk iphone từ Orielly đã đưa ra một mã ví dụ để thực hiện cái gọi là trang flicking này. Trước tiên, mã đã tạo tất cả các bản xem trước và sau đó ẩn/bỏ ẩn chúng. Tại một thời điểm nhất định chỉ có 3 phần phụ là phần còn lại có thể nhìn thấy được ẩn. Sau nhiều nỗ lực tôi nhận được nó làm việc với các ứng dụng mà tại thời điểm đó chỉ có khoảng 15 trang.Cách triển khai UIScrollView với hơn 1000 bản xem trước?

Ngay sau khi tôi thêm 300 trang, rõ ràng là có các vấn đề hiệu năng/bộ nhớ với cách tiếp cận phân bổ trước rất nhiều lượt xem phụ. Sau đó, tôi nghĩ có thể cho trường hợp của tôi, tôi chỉ nên phân bổ 3 subviews và thay vì ẩn/bỏ ẩn chúng. Có thể tôi chỉ nên loại bỏ/thêm các bản xem trước khi chạy. Nhưng không thể biết được liệu UIScrollView có thể tự động cập nhật nội dung hay không. Ví dụ, lúc bắt đầu có 3 khung hình ở các màn hình x khác nhau (0, 320, 640) từ màn hình được hiểu bởi UIScrollView. Khi người dùng chuyển sang trang thứ 3, tôi làm cách nào để đảm bảo rằng tôi có thể thêm trang thứ 4 và xóa trang thứ nhất nhưng UIScrollView không bị nhầm lẫn?

Hy vọng có giải pháp chuẩn cho loại sự cố này ... ai đó có thể hướng dẫn?

+1

không phải là câu trả lời cụ thể cho bạn, nhưng loại sự cố này thường được giải quyết bằng cách sử dụng mẫu cân bằng: http://en.wikipedia.org/wiki/Flyweight_pattern –

+0

Hiện tại, bất kỳ ai đến câu hỏi này - sử dụng 'UICollectionView' ... nó không có sẵn tại thời điểm của câu hỏi này, nhưng nó là bây giờ và sẽ làm điều này cho bạn. –

Trả lời

7

UIScrollView chỉ là lớp con của UIView để có thể thêm và xóa các bản xem phụ khi chạy. Giả sử bạn có ảnh có chiều rộng cố định (320px) và có 300 ảnh trong số đó, thì chế độ xem chính của bạn sẽ có chiều rộng là 300 * 320 pixel. Khi tạo chế độ xem cuộn, hãy khởi tạo khung hình rộng.

Vì vậy, khung của chế độ xem cuộn sẽ có kích thước (0, 0) đến (96000, 480). Bất cứ khi nào bạn thêm một subview, bạn sẽ phải thay đổi khung của nó để nó phù hợp với vị trí chính xác trong khung nhìn cha mẹ của nó.

Vì vậy, giả sử, chúng tôi đang thêm ảnh thứ tư vào chế độ xem cuộn. Khung của nó sẽ là từ (960, 480) đến (1280, 480). Đó là dễ dàng để tính toán, nếu bạn bằng cách nào đó có thể kết hợp một chỉ mục với mỗi hình ảnh. Sau đó sử dụng để tính toán khung của bức tranh mà chỉ số bắt đầu từ 0:

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

để

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

Loại bỏ các hình ảnh đầu tiên/subview nên dễ dàng. Giữ một loạt các 3 subviews hiện tại trên màn hình. Bất cứ khi nào bạn thêm một subview mới vào màn hình, cũng thêm nó vào cuối mảng này, và sau đó loại bỏ subview đầu tiên trong mảng này từ màn hình quá.

+0

Những gì bạn đang nói không hợp lý. Hãy để tôi cho nó một shot và sau đó cập nhật chủ đề này. – climbon

+1

Làm việc ngay bây giờ. Cảm ơn bạn đã làm rõ mặc dù tôi đã thực hiện nó một cách hơi khác (để giảm thiểu những thay đổi đối với mã hiện có). Mã hiện tại của tôi (mượn từ ví dụ trong sách tôi đã đề cập) có chiều rộng của khung scrollView của tôi = 320x3. Sử dụng thuộc tính myScrollView.contentOffset mã kiểm soát của 3 khung nhìn để hiển thị. Trước đó mảng của tôi chứa 300 lượt xem (được phân bổ khi bắt đầu) bây giờ nó chỉ chứa 3. Nếu người dùng truy cập trang thứ 3, tôi xóa trang thứ nhất và chèn thứ 4 và điều chỉnh chế độ hiển thị bằng thuộc tính contentOffset. Nếu anh ấy đến vị trí số 1, tôi xóa mục thứ 3 và chèn một cái trước 1, v.v. – climbon

+0

tôi không biết về thuộc tính contentOffset. Tôi cần một cái gì đó tương tự như cuộn liên tục cho các ứng dụng của tôi, và chắc chắn sẽ sử dụng rằng – Anurag

15

Theo những gì đã được nói, bạn có thể hiển thị hàng ngàn phần tử chỉ sử dụng một lượng tài nguyên giới hạn (và có, đó thực sự là một mô hình Flyweight). Dưới đây là một số mã có thể giúp bạn làm những gì bạn muốn.

Lớp UntitledViewController chỉ chứa UIScroll và tự đặt làm đại biểu của nó. Chúng ta có một NSArray với các cá thể NSString bên trong như là mô hình dữ liệu (có thể có khả năng hàng ngàn NSStrings trong nó), và chúng tôi muốn hiển thị từng cái trong một UILabel, sử dụng cuộn ngang. Khi người dùng cuộn, chúng tôi chuyển UILabels để đặt một ở bên trái, một ở bên phải, để mọi thứ đã sẵn sàng cho sự kiện cuộn tiếp theo.

Dưới đây là giao diện, chứ không phải đơn giản:

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

    NSArray *_objects; 

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

@end 

Và đây là việc thực hiện cho lớp đó:

@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 

Hope this helps!

+0

Cảm ơn Adrian cho mẫu mã này quá. Thời gian tới hoặc nếu tôi có cơ hội để làm cho mã của tôi sạch hơn, tôi cũng có thể tham khảo phần này. – climbon

+0

Hi @akosma, Tôi đã triển khai mã ở trên nhưng tôi cũng cần cuộn vô hạn ở cả hai phương tiện bên nếu tôi ở vị trí 0 và vuốt sang phải rồi di chuyển đến vị trí 10. Bạn có thể giúp tôi được không? Làm thế nào tôi có thể thực hiện chức năng này trong mã của bạn ..? –

6

Rất cám ơn Adrian vì mẫu mã rất đơn giản và mạnh mẽ của anh ấy. Chỉ có một vấn đề với mã này: khi người dùng thực hiện "cuộn kép" (I.E. khi anh ta không đợi cho hoạt ảnh dừng lại và hủy cuộn lại scrollview một lần nữa).

Trong trường hợp này, làm mới vị trí của 3 bản xem phụ chỉ có hiệu lực khi phương thức "scrollViewDidEndDecelerating" được gọi và kết quả là sự chậm trễ trước khi hiển thị các bản xem phụ trên màn hình.

này có thể dễ dàng tránh được bằng cách thêm vài dòng mã:

trong giao diện, chỉ cần thêm này:

int refPage, currentPage; 

trong việc thực hiện, khởi refPage và currentPage trong phương pháp "viewDidLoad" như này:

refpage = 0; 
curentPage = 0; 

trong việc thực hiện, chỉ cần thêm phương pháp "scrollViewDidScroll", như thế này:

- (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]; 
     } 
    } 

et voilà!

Bây giờ, các bản xem trước được thay thế chính xác ở đúng vị trí, ngay cả khi người dùng không bao giờ ngừng hoạt ảnh và nếu phương thức "scrollViewDidEndDecelerating" không bao giờ được gọi!

+0

Hi @Chrysotribax, tôi đã sử dụng mã trên và nó hoạt động tốt nhưng tôi cần phải làm cho nó cuộn vô hạn có nghĩa là dữ liệu người dùng swipe và tiếp cận đối tượng cuối cùng nó sẽ đi đến dữ liệu đầu tiên và nếu nó sẽ swipe bên phải sau đó nó sẽ đạt được đối tượng cuối cùng. Bạn có thể giúp tôi không ? –

Các vấn đề liên quan