How to implement UIScrollView with 1000+ subviews?

前端 未结 3 1608
隐瞒了意图╮
隐瞒了意图╮ 2020-12-07 19:30

I am struggling with writing portion of an app which should behave like the native iphone photo app. Looked at iphone sdk app development book from Orielly which gave an exa

相关标签:
3条回答
  • 2020-12-07 20:00

    Many thanks to Adrian for his very simple and powerfull code sample. There was just one issue with this code : when the user made a "double scroll" (I.E. when he did not wait for the animation to stop and rescroll the scrollview again and again).

    In this case, the refresh for the position of the 3 subviews is effective only when the "scrollViewDidEndDecelerating" method is invoked, and the result is a delay before the apparition of the subviews on the screen.

    This can be easily avoided by adding few lines of code :

    in the interface, just add this :

    int refPage, currentPage;
    

    in the implementation, initialize refPage and currentPage in the "viewDidLoad" method like this :

    refpage = 0; 
    curentPage = 0;
    

    in the implementation, just add the "scrollViewDidScroll" method, like this :

    - (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à !

    Now, the subviews are correctly replaced in the correct positions, even if the user never stop the animation and if the "scrollViewDidEndDecelerating" method is never invoked !

    0 讨论(0)
  • 2020-12-07 20:13

    UIScrollView is just a subclass of UIView so it's possible to add and remove subviews at runtime. Assuming you have fixed width photos (320px) and there are 300 of them, then your main view would be 300 * 320 pixels wide. When creating the scroll view, initialize the frame to be that wide.

    So the scroll view's frame would have the dimensions (0, 0) to (96000, 480). Whenever you are adding a subview, you will have to change it's frame so it fits in the correct position in its parent view.

    So let's say, we are adding the 4th photo to the scroll view. It's frame would be from (960, 480) to (1280, 480). That is easily to calculate, if you can somehow associate an index with each picture. Then use this to calculate the picture's frame where indexes start at 0:

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

    to

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

    Removing the first picture/subview should be easy. Keep an array of the 3 subviews currently on-screen. Whenever you are adding a new subview to the screen, also add it to the end of this array, and then remove the first subview in this array from the screen too.

    0 讨论(0)
  • 2020-12-07 20:16

    Following what has been said, you can show thousand of elements using only a limited amount of resources (and yes, it's a bit of a Flyweight pattern indeed). Here's some code that might help you do what you want.

    The UntitledViewController class just contains a UIScroll and sets itself as its delegate. We have an NSArray with NSString instances inside as data model (there could be potentially thousands of NSStrings in it), and we want to show each one in a UILabel, using horizontal scrolling. When the user scrolls, we shift the UILabels to put one on the left, another on the right, so that everything is ready for the next scroll event.

    Here's the interface, rather straightforward:

    @interface UntitledViewController : UIViewController <UIScrollViewDelegate>
    {
    @private
        UIScrollView *_scrollView;
    
        NSArray *_objects;
    
        UILabel *_detailLabel1;
        UILabel *_detailLabel2;
        UILabel *_detailLabel3;
    }
    
    @end
    

    And here's the implementation for that class:

    @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 讨论(0)
提交回复
热议问题