Now, at the head of almost every app, you have a ScrollView that scrolls an image indefinitely, and then you click on the image to jump to a different page. Today we will learn how to encapsulate such a control.

demand

  • Three ImageView controls allow infinite scrolling of multiple images
  • Click on the picture to get the information for the caller to use

Infinite scrolling renderings

Click on the picture event

The information corresponding to the image is typically returned by the server, encapsulated into the Model, and then passed to our encapsulated infinite scroll control. When the caller invokes the callback through the proxy method, clicking on each image, we return the information corresponding to the clicked image, so that the caller can take that information and do something with it. As shown below, the name and URL of the clicked image are returned


Infinite scrolling ScrollView package

Let’s look specifically at how to encapsulate an infinitely scrolling UiscrollView and implement click events. The specific implementation code is given below, and a very detailed description is made. But there are two methods that are a little bit more difficult to understand, and I’ll give you separate examples.

InfiniteRollScrollView. H file = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = # import @ class InfiniteRollScrollView; @ protocol infiniteRollScrollViewDelegate @ optional / * * * * * click on the image of the callback events @ param scrollView generally preach self * @ param info The model corresponding to each image is passed by the controller using the imageModelInfoArray property, */ -(void)infiniteRollScrollView:(infiniteRollScrollView *)scrollView tapImageViewInfo:(id)info; @end @interface InfiniteRollScrollView : */ @property (strong, nonatomic) NSMutableArray *imageModelInfoArray; / / @property (strong, nonatomic) NSArray *imageArray; /** * Whether to display scrollView in portrait, The default is no * / @ property (assign nonatomic, getter = isScrollDirectionPortrait) BOOL scrollDirectionPortrait; @property (weak, nonatomic, readonly) UIPageControl *pageControl; @property(assign,nonatomic)NSInteger ImageViewCount; @property(weak,nonatomic)iddelegate; @endCopy the code
InfiniteRollScrollView. M file = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = # import "InfiniteRollScrollView. H" static int const ImageViewCount = 3; @interface InfiniteRollScrollView() @property (weak, nonatomic) UIScrollView *scrollView; @property (weak, nonatomic) NSTimer *timer; @property(assign,nonatomic)BOOL isFirstLoadImage; @end@implementation InfiniteRollScrollView #pragma mark - init - (instancetype)initWithFrame:(CGRect)frame {if (self = [super initWithFrame:frame]) {UIScrollView *scrollView = [[UIScrollView alloc] init]; scrollView.showsHorizontalScrollIndicator = NO; scrollView.showsVerticalScrollIndicator = NO; scrollView.pagingEnabled = YES; scrollView.bounces = NO; scrollView.delegate = self; [self addSubview:scrollView]; self.scrollView = scrollView; For (int I = 0; I - (void)scrollViewDidScroll:(UIScrollView *)scrollView {NSInteger page = 0; CGFloat minDistance = MAXFLOAT; for (int i = 0; I = self. PageControl. NumberOfPages) {/ / scroll to the last one, because the index a, lead to the index is greater than the total number of images, as the index to reset to zero, So if I scroll down to the end and I scroll back I have the first image index = 0; } imageView.tag = index; imageView.image = self.imageArray[index]; } self.isFirstLoadImage =YES; // Each time you scroll an image, set the contentoffset of ScrollView to the height or width of the entire ScrollView, so that you can roll one image at a time. if (self.isScrollDirectionPortrait) { self.scrollView.contentOffset = CGPointMake(0, self.scrollView.frame.size.height);  } else { self.scrollView.contentOffset = CGPointMake(self.scrollView.frame.size.width, 0); } } - (void)displayNextImage { if (self.isScrollDirectionPortrait) { [self.scrollView setContentOffset:CGPointMake(0, 2 * self.scrollView.frame.size.height) animated:YES]; } else { [self.scrollView setContentOffset:CGPointMake(2 * self.scrollView.frame.size.width, 0) animated:YES]; }} #pragma mark - (void)startTimer {NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(displayNextImage) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; self.timer = timer; } - (void)stopTimer { [self.timer invalidate]; Self. timer = nil; self.timer = nil; self.timer = nil; } #pragma mark - setmethod - (void)setImageArray:(NSArray *)imageArray {_imageArray = imageArray; / / Settings page self. PageControl. NumberOfPages = imageArray. Count; self.pageControl.currentPage = 0; // Set the content [self displayImage]; // startTimer [self startTimer]; } @endCopy the code

Difficulty 1. How to find the pictures that occupy more screen space

In the InfiniterollScrollView.m class file there are the following methods. The purpose of this method is to determine when the user drags an image, two images appear on the screen at the same time, and if the user lets go at this point, which image should be fully displayed. At this point, we need to determine which picture occupies a larger proportion of the screen and display that picture.

The situation is as follows:

Implementation method

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {// when two images are displayed on the screen at the same time, find the image that is more than half of the screen. NSInteger page = 0; CGFloat minDistance = MAXFLOAT; for (int i = 0; iCopy the code

How do we find the imageView that corresponds to the minimum distance

Suppose the three imageView frames have the following x values:

image1-x: 0 image2-x: 100 image3-x: 200

PS:

When you move the ScrollView, you don't change the frame of the Image view, you just change the bounds of the ScrollView, and the position of the image View's child control changes as well. This creates the impression that the Image View is constantly moving.

The absolute value of the difference between the CONTENtoffset of ScrollView and the X value of imageView has the following cases

Case 1:

offset : 20

ABS(offset-image1-x): ABS(20-0) = 20

ABS(offset-image2-x): ABS(20-100) = 80

ABS(offset-image3-x): ABS(20-200)= 180

The difference of image3 is greater than 100, so it is off-screen. The minimum difference is 20 of image1, at this time, image1 occupies 80 screen, image2 occupies 20 screen, image1 occupies more, release the hand should display image1.

Here's an example:


Situation 2:

offset : 50

ABS(offset-image1-x): 50

ABS(offset-image2-x): 50

ABS(offset-image3-x): 150

At this point, the critical point, image1 and image2 each occupy half of the screen, and image3 exceeds the screen

Here's an example:


Case 3:

offset : 60

ABS(offset-image1-x): 60

ABS(offset-image2-x): 40

ABS(offset-image3-x): 140

Image3 exceeds the screen, the minimum difference is 40 of image2, at this time, image1 occupies 40 of the screen, image2 occupies 60 of the screen, image2 occupies more, release the hand should display image2

Here's an example:


Situation 4:

offset : 150

ABS(offset-image1-x): 150

ABS(offset-image2-x): 50

ABS(offset-image3-x): 50

Image1 is off-screen. This is the critical point where image2 and image3 each take up half of the screen

Here's an example:


Situation 5:

offset : 160

ABS(offset-image1-x): 160

ABS(offset-image2-x): 60

ABS(offset-image3-x): 40

Image1 is off-screen. The minimum difference value is 40, at this time, image3 occupies 40 screen, image1 occupies 60 screen, image3 occupies more, release the hand should display image3

Here's an example:

Through the analysis of the above five cases, we can see that the above method can be used to find more imageViews on the screen.


Difficulty 2. How to use three ImageViews to achieve infinite scrolling

You can see from the initial sample diagram that there are five images, but only three ImageViews are used for recycling.

The implementation code

- (void)displayImage {// Set imageView to display an infinite number of images. I = self. PageControl. NumberOfPages) {/ / scroll to the last one, because the index a, lead to the index is greater than the total number of images, as the index to reset to zero, So if I scroll down to the end and I scroll back I have the first image index = 0; } imageView.tag = index; imageView.image = self.imageArray[index]; } self.isFirstLoadImage =YES; / / let the scrollview display in the middle of the imageview if (self. IsScrollDirectionPortrait) {self. The scrollview. ContentOffset = CGPointMake (0, self.scrollView.frame.size.height); } else { self.scrollView.contentOffset = CGPointMake(self.scrollView.frame.size.width, 0); }}Copy the code

Let's look at the diagram. Let's say we have four images and we're going to loop through three ImageViews (more images are similar).

By doing this, you can create an infinite number of images from three ImageViews.

Combining the above code with the sample diagram should not be difficult to understand.


How to use

Suppose we use the InfiniteRollScrollView class in the ViewController class. Example code is as follows:

#import "ViewController.h" #import "ImageModel.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; InfiniteRollScrollView *scrollView = [[InfiniteRollScrollView alloc] init]; scrollView.frame = CGRectMake(30, 50, 300, 130); scrollView.delegate = self; scrollView.pageControl.currentPageIndicatorTintColor = [UIColor orangeColor]; scrollView.pageControl.pageIndicatorTintColor = [UIColor grayColor]; Scrollview. imageArray = @[[UIImage imageNamed:@"0"], [UIImage imageNamed:@"1"], [UIImage imageNamed:@"2"], [UIImage imageNamed:@"3"], [UIImage imageNamed:@"4"] ]; // We add the information of each image manually. The actual environment is usually returned by the server, and then encapsulated into the model. scrollView.imageModelInfoArray = [NSMutableArray array]; for (int i = 0; i<5; i++) { ImageModel *mode = [[ImageModel alloc]init]; mode.name = [NSString stringWithFormat:@"picture-%zd",i]; mode.url = [NSString stringWithFormat:@"http://www.baidu.com-%zd",i]; [scrollView.imageModelInfoArray addObject:mode]; } [self.view addSubview:scrollView]; } // proxy method -(void)infiniteRollScrollView:(infiniteRollScrollView *)scrollView tapImageViewInfo:(id)info{ImageModel *model  = (ImageModel *)info; NSLog(@"name:%@---url:%@", model.name, model.url); } @endCopy the code

Conclusion:

In fact, the above encapsulation is not perfect, because the caller needs to pass in the image to be displayed and the corresponding model of the image, which requires the caller to download the image, and then pass in. In fact, we can tell the caller to just pass in the model of all the images that need to be displayed, and we download them for him.

Demo address: github.com/XiMuYouZi/I...