iOS: Calendar Day View transition - iphone

How does Apple do the iPhone calendar day view transition between dates? When you swipe your finger on the day view it looks a bit like a carousel and when the day view is half way across the screen the date at the top changes.
I have recreated the day view myself but need to figure out how to do the transition between days.

If I had to guess? Using 3 panes in a UIScrollView like this method.
- (void)viewDidLoad
{
[super viewDidLoad];
documentTitles = [[NSMutableArray alloc] init];
// create our array of documents
for (int i = 0; i < 10; i++) {
[documentTitles addObject:[NSString stringWithFormat:#"Document %i",i+1]];
}
// create placeholders for each of our documents
pageOneDoc = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
pageTwoDoc = [[UILabel alloc] initWithFrame:CGRectMake(320, 0, 320, 44)];
pageThreeDoc = [[UILabel alloc] initWithFrame:CGRectMake(640, 0, 320, 44)];
pageOneDoc.textAlignment = UITextAlignmentCenter;
pageTwoDoc.textAlignment = UITextAlignmentCenter;
pageThreeDoc.textAlignment = UITextAlignmentCenter;
// load all three pages into our scroll view
[self loadPageWithId:9 onPage:0];
[self loadPageWithId:0 onPage:1];
[self loadPageWithId:1 onPage:2];
[scrollView addSubview:pageOneDoc];
[scrollView addSubview:pageTwoDoc];
[scrollView addSubview:pageThreeDoc];
// adjust content size for three pages of data and reposition to center page
scrollView.contentSize = CGSizeMake(960, 416);
[scrollView scrollRectToVisible:CGRectMake(320,0,320,416) animated:NO];
}
- (void)loadPageWithId:(int)index onPage:(int)page
{
// load data for page
switch (page) {
case 0:
pageOneDoc.text = [documentTitles objectAtIndex:index];
break;
case 1:
pageTwoDoc.text = [documentTitles objectAtIndex:index];
break;
case 2:
pageThreeDoc.text = [documentTitles objectAtIndex:index];
break;
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender
{
// All data for the documents are stored in an array (documentTitles).
// We keep track of the index that we are scrolling to so that we
// know what data to load for each page.
if(scrollView.contentOffset.x > scrollView.frame.size.width)
{
// We are moving forward. Load the current doc data on the first page.
[self loadPageWithId:currIndex onPage:0];
// Add one to the currentIndex or reset to 0 if we have reached the end.
currIndex = (currIndex $gt;= [documentTitles count]-1) ? 0 : currIndex + 1;
[self loadPageWithId:currIndex onPage:1];
// Load content on the last page. This is either from the next item in the array
// or the first if we have reached the end.
nextIndex = (currIndex $gt;= [documentTitles count]-1) ? 0 : currIndex + 1;
[self loadPageWithId:nextIndex onPage:2];
}
if(scrollView.contentOffset.x $lt; scrollView.frame.size.width) {
// We are moving backward. Load the current doc data on the last page.
[self loadPageWithId:currIndex onPage:2];
// Subtract one from the currentIndex or go to the end if we have reached the beginning.
currIndex = (currIndex == 0) ? [documentTitles count]-1 : currIndex - 1;
[self loadPageWithId:currIndex onPage:1];
// Load content on the first page. This is either from the prev item in the array
// or the last if we have reached the beginning.
prevIndex = (currIndex == 0) ? [documentTitles count]-1 : currIndex - 1;
[self loadPageWithId:prevIndex onPage:0];
}
// Reset offset back to middle page
[scrollView scrollRectToVisible:CGRectMake(320,0,320,416) animated:NO];
}

You don't need a scroll view to do this; just create a view with 3 subviews inside it arranged side by side, and add a pan gesture recognizer.
If the user moved to the right view, chuck out the leftmost view, add a view on the right, and make the present right view the center view. You need to do a similar thing for the left view as well.

Related

Set Uibutton Random Position on UIView

I want to set 5 buttons on uiview at random position. Buttons need maintain some spacing to each other. I mean buttons should not overlap to each other.
All buttons set on UIView come from corners with rotation animation.
btn1.transform = CGAffineTransformMakeRotation(40);
btn2.transform = CGAffineTransformMakeRotation(60);
btn3.transform = CGAffineTransformMakeRotation(90);
btn4.transform = CGAffineTransformMakeRotation(30);
btn5.transform = CGAffineTransformMakeRotation(20);
I Can rotate buttons using above code but can you pls. help me for set buttons on random position with out overlapping by each other.
If points are fix than I can set buttons with animation by this code but I want random position of buttons.
[AnimationView moveBubble:CGPointMake(18, 142) duration:1 : btn1];
[AnimationView moveBubble:CGPointMake(118, 142) duration:1 : btn2];
[AnimationView moveBubble:CGPointMake(193, 142) duration:1 : btn3];
[AnimationView moveBubble:CGPointMake(18, 216) duration:1 : btn4];
Thanks in advance.
1st, add buttons to an NSArray, only to make things easier:
NSArray *buttonArray = #[btn1,btn2,btn3,btn4,btn5];
Now, this code tries to Arrange them at random positions.
int xTemp, yTemp;
for (int i = 0; i < 5; i++) {
while (YES) {
xTemp = arc4random_uniform(view.frame.size.width - [buttonArray[i] frame].size.width);
yTemp = arc4random_uniform(view.frame.size.height - [buttonArray[i] frame].size.height);
if (![self positionx:xTemp y:yTemp intersectsAnyButtonTillIndex:i inButtonArray:buttonArray]) {
[AnimationView moveBubble:CGPointMake(xTemp, yTemp) duration:1 : buttonArray[i]];
break;
}
}
}
Implement this function somewhere too:
- (BOOL) positionx:(int)xTemp y:(int)yTemp intersectsAnyButtonTillIndex:(int)index inButtonArray:(NSArray *)buttonArray {
//Again please change the < to <= , I'm sorry, doing too many things at once.
for (int i = 0; i <= index; i++) {
CGRect frame = [buttonArray[i] frame];
//EDIT : In the logic earlier, I had wrongly done a minus where I should have done a plus.
if ((xTemp > frame.origin.x && xTemp < (frame.size.width + frame.origin.x)) && (yTemp > frame.origin.y && yTemp < (frame.size.height + frame.origin.y))) return YES;
}
return NO;
OK this is a workign soln., I hope, just added something to WolfLink's answer. Check This.
for (UIButton *button in buttonArray) {
button.frame = CGRectMake(arc4random_uniform(view.frame.size.width - button.frame.size.width), arc4random_uniform(view.frame.size.height - button.frame.size.height), button.frame.size.width, button.frame.size.height);
while ([self button:button intersectsButtonInArray:buttonArray]) {
button.frame = CGRectMake(arc4random_uniform(view.frame.size.width - button.frame.size.width), arc4random_uniform(view.frame.size.height - button.frame.size.height), button.frame.size.width, button.frame.size.height);
}
//another function
-(BOOL)button:(UIButton *)button intersectsButtonInArray:(NSArray *)array {
for (UIButton *testButton in array) {
if (CGRectIntersectsRect(button.frame, testButton.frame) && ![button isEqual:testButton]) {
return YES;
}
}
return NO;
}
Based on spiritofmysoul.wordpress's code:
//in the function where you randomize the buttons
NSArray *buttonArray = #[btn1,btn2,btn3,btn4,btn5];
for (UIButton *button in buttonArray) {
float widthOffset = self.frame.size.width-button.frame.size.width;
float heightOffset = self.frame.size.height-button.frame.size.height;
button.frame = CGRectMake(arc4random()%widthOffset, arc4random()%heightOffset, button.frame.size.width, button.frame.size.height);
while ([self button:button intersectsButtonInArray:buttonArray]) {
button.frame = CGRectMake(arc4random(), arc4random(), button.frame.size.width, button.frame.size.height);
}
//another function
-(BOOL)button:(UIButton *)button intersectsButtonInArray:(NSArray *)array {
for (UIButton *testButton in array) {
if (CGRectIntersectsRect(button.frame, testButton.frame) && ![button isEqual:testButton]) {
return YES;
}
}
return NO;
}
Beware: This will work well for small amounts of buttons on a large space but as you add buttons and you run out of space, this method will take much longer to run. If there is not enough space for all the buttons, it will become an infinite loop.

Custom UIPickerView with three Components each showing label on Selection Indicator

In my app I am trying to make an custom UIPickerView which contains three components(days, hours and minutes). I have already made the custom picker with three components. And Now I am stuck at how I can add the labels to the selection indicator which shows which component is for days, hours or minutes.
I have already gone through each and every link or question posted on this site but none them helped me.
I am trying to implement something like this image
Can any one suggest me how can I achieve this?
Thats how I achieve this....I have made my Custom PickerView with the help of some code I found...
In .h file:
// LabeledPickerView.h
// LabeledPickerView
#import <UIKit/UIKit.h>
#interface LabeledPickerView : UIPickerView
{
NSMutableDictionary *labels;
}
/** Adds the label for the given component. */
-(void)addLabel:(NSString *)labeltext forComponent:(NSUInteger)component forLongestString:(NSString *)longestString;
#end
and In the .m file...
// LabeledPickerView.m
// LabeledPickerView
#import "LabeledPickerView.h"
#implementation LabeledPickerView
/** loading programmatically */
- (id)initWithFrame:(CGRect)aRect {
if (self = [super initWithFrame:aRect]) {
labels = [[NSMutableDictionary alloc] initWithCapacity:3];
}
return self;
}
/** loading from nib */
- (id)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
labels = [[NSMutableDictionary alloc] initWithCapacity:3];
}
return self;
}
- (void) dealloc
{
[labels release];
[super dealloc];
}
#pragma mark Labels
// Add labelText to our array but also add what will be the longest label we will use in updateLabel
// If you do not plan to update label then the longestString should be the same as the labelText
// This way we can initially size our label to the longest width and we get the same effect Apple uses
-(void)addLabel:(NSString *)labeltext forComponent:(NSUInteger)component forLongestString:(NSString *)longestString {
[labels setObject:labeltext forKey:[NSNumber numberWithInt:component]];
NSString *keyName = [NSString stringWithFormat:#"%#_%#", #"longestString", [NSNumber numberWithInt:component]];
if(!longestString) {
longestString = labeltext;
}
[labels setObject:longestString forKey:keyName];
}
//
- (void) updateLabel:(NSString *)labeltext forComponent:(NSUInteger)component {
UILabel *theLabel = (UILabel*)[self viewWithTag:component + 1];
// Update label if it doesn’t match current label
if (![theLabel.text isEqualToString:labeltext]) {
NSString *keyName = [NSString stringWithFormat:#"%#_%#", #"longestString", [NSNumber numberWithInt:component]];
NSString *longestString = [labels objectForKey:keyName];
// Update label array with our new string value
[self addLabel:labeltext forComponent:component forLongestString:longestString];
// change label during fade out/in
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.75];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
theLabel.alpha = 0.00;
theLabel.text = labeltext;
theLabel.alpha = 1.00;
[UIView commitAnimations];
}
}
/**
Adds the labels to the view, below the selection indicator glass-thingy.
The labels are aligned to the right side of the wheel.
The delegate is responsible for providing enough width for both the value and the label.
*/
- (void)didMoveToWindow {
// exit if view is removed from the window or there are no labels.
if (!self.window || [labels count] == 0)
return;
UIFont *labelfont = [UIFont boldSystemFontOfSize:15];
// find the width of all the wheels combined
CGFloat widthofwheels = 0;
for (int i=0; i<self.numberOfComponents; i++) {
widthofwheels += [self rowSizeForComponent:i].width;
}
// find the left side of the first wheel.
// seems like a misnomer, but that will soon be corrected.
CGFloat rightsideofwheel = (self.frame.size.width - widthofwheels) / 2;
// cycle through all wheels
for (int component=0; component<self.numberOfComponents; component++) {
// find the right side of the wheel
rightsideofwheel += [self rowSizeForComponent:component].width;
// get the text for the label.
// move on to the next if there is no label for this wheel.
NSString *text = [labels objectForKey:[NSNumber numberWithInt:component]];
if (text) {
// set up the frame for the label using our longestString length
NSString *keyName = [NSString stringWithFormat:#"%#_%#", [NSString stringWithString:#"longestString"], [NSNumber numberWithInt:component]];
NSString *longestString = [labels objectForKey:keyName];
CGRect frame;
frame.size = [longestString sizeWithFont:labelfont];
// center it vertically
frame.origin.y = (self.frame.size.height / 2) - (frame.size.height / 2) - 0.5;
// align it to the right side of the wheel, with a margin.
// use a smaller margin for the rightmost wheel.
frame.origin.x = rightsideofwheel - frame.size.width -
(component == self.numberOfComponents - 1 ? 5 : 7);
// set up the label. If label already exists, just get a reference to it
BOOL addlabelView = NO;
UILabel *label = (UILabel*)[self viewWithTag:component + 1];
if(!label) {
label = [[[UILabel alloc] initWithFrame:frame] autorelease];
addlabelView = YES;
}
label.text = text;
label.font = labelfont;
label.backgroundColor = [UIColor clearColor];
label.shadowColor = [UIColor whiteColor];
label.shadowOffset = CGSizeMake(0,1);
// Tag cannot be 0 so just increment component number to esnure we get a positive
// NB update/remove Label methods are aware of this incrementation!
label.tag = component + 1;
if(addlabelView) {
/*
and now for the tricky bit: adding the label to the view.
kind of a hack to be honest, might stop working if Apple decides to
change the inner workings of the UIPickerView.
*/
if (self.showsSelectionIndicator) {
// if this is the last wheel, add label as the third view from the top
if (component==self.numberOfComponents-1)
[self insertSubview:label atIndex:[self.subviews count]-3];
// otherwise add label as the 5th, 10th, 15th etc view from the top
else
[self insertSubview:label aboveSubview:[self.subviews objectAtIndex:5*(component+1)]];
} else
// there is no selection indicator, so just add it to the top
[self addSubview:label];
}
if ([self.delegate respondsToSelector:#selector(pickerView:didSelectRow:inComponent:)])
[self.delegate pickerView:self didSelectRow:[self selectedRowInComponent:component] inComponent:component];
}
}
}
And call this addLabel: method with the label text and component tag and thats it..!!
Download the Source code Of custom UIPickerView Control .
Custom UiPickerView.
Hope it Helps to You :)

UIScrollVIew View

I'm using iCarousel as a slot machine, for those who doesn't know iCarousel it is a UIScrollview with (paged, scrolling views). So in my app, I scroll it, then when it stops it shows the Image(Result) for 3 seconds, then a button pop-ups, if you press the button, it will delete the View(Image Result) then Rotates again.
Here is my way of deleting my carousel view:
NSInteger CurrentImage = carousel.currentItemIndex;
[carousel removeItemAtIndex:CurrentImage animated:YES];
[images removeObjectAtIndex:CurrentImage];
But then when the carousel.numberOfItems has 1 item left, it doesnt scroll, instead its just stuck there.
So I made a way to make it scroll even it has only 1 Item(index) left.
I inserted it with the last index, so I tried this:
[self.carousel insertItemAtIndex:0 animated:NO];
(but doesnt work)
then I tried another:
int lastObject = [images objectAtIndex: ([images count]-1)]
[self.carousel insertItemAtIndex:lastObject animated:NO];
(still doesnt work)
and also this:
int count = [images count];
[images objectAtIndex:count - 1];
(still no luck)
Am I doing wrong? or What are other ways?
Or can I just duplicate the last View? Thanks for the help.
EDIT:
Methods
- (NSUInteger)numberOfItemsInCarousel:(iCarousel *)carousel
{
if ([images count] == 1 || [self.carousel numberOfItems] == 1)
return 2;
return [images count];
}
- (NSUInteger)numberOfVisibleItemsInCarousel:(iCarousel *)carousel
{
//limit the number of items views loaded concurrently (for performance reasons)
return 7;
}
- (UIView *)carousel:(iCarousel *)_carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view
{
if (index >= [images count] || index >= [carousel numberOfItems]) {
index = 0;
}
NSDictionary *obj = [images objectAtIndex:index];
view = [[UIImageView alloc] initWithImage:[obj objectForKey:#"image"]];
view.tag = index;
return view;
}
- (NSUInteger)numberOfPlaceholdersInCarousel:(iCarousel *)carousel
{
//note: placeholder views are only displayed on some carousels if wrapping is disabled
return 0;
}
- (CGFloat)carouselItemWidth:(iCarousel *)carousel
{
//usually this should be slightly wider than the item views
return 400;
}
- (BOOL)carousel:(iCarousel *)carousel shouldSelectItemAtIndex:(NSInteger)index{
return 5;
}
- (BOOL)carouselShouldWrap:(iCarousel *)carousel
{
//wrap all carousels
return wrap;
}
Delete Method:
-(void) deleteItem{
//Removes the object chosen
if (carousel.numberOfItems >= 1)
{
NSInteger index = carousel.currentItemIndex;
[carousel removeItemAtIndex:index animated:YES];
//[images removeObjectAtIndex:index];
//[images replaceObjectAtIndex:index withObject:[NSNull null]];
}
}
What you could do is make
- (NSUInteger)numberOfItemsInCarousel:(iCarousel*)carousel;
return always a value >1 (eg. 2); then make sure that
- (UIView*)carousel:(iCarousel*)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView*)view;
always returns a correct view for the minimum number of elements you return from numberOfItemsInCarousel.
Eg.
- (NSUInteger)numberOfItemsInCarousel:(iCarousel*)carousel {
if (numberOfViews == 1)
return 2;
return numberOfViews;
}
- (UIView*)carousel:(iCarousel*)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView*)view {
if (index >= numberOfViews) {
index = 0;
}
NSDictionary *obj = [images objectAtIndex:index];
view = [[UIImageView alloc] initWithImage:[obj objectForKey:#"image"]];
view.tag = index;
return view;
}
You should also make sure that the last element is never deleted of add some guards in the code above to manage the case when no elements are left.

Paged UIScrollview with lazy delayed loading

I need help with the uiscrollview implementation with the following requirements:
1) pagingEnabled = true.
2) lazy pages loading.
3) pages are loading in the background. So i need at first run loading the page X, then get the notification that the page is fully loaded and only then allow the user to scroll to it.
4) ability to change the current page.
My first attempt was to override the scrollViewDidEndDeacelerating and scrollViewDidScroll, but I had troubles with stucking on half of pages (when you stop the scroll on the half of the page and then wait for new page to add to the scroll) and empty pages (when the user scrolled too fast).
My second attempt was to override the layoutSubviews method of the UIScrollView and do all calculations there. But it seems to be very sofisticated.
So, I'd love to find any examples of similar implementations.
Now I have the code like this:
I've implemented scrollViewWillBeginDragging and scrollViewDidEndDecelerating:
- (void)scrollViewWillBeginDragging:(UIScrollView *)aScrollView
{
isScrolling = YES;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)aScrollView
{
isScrolling = NO;
// Here we load the page that was prepared when the user was scrolling
if (needDisplay > -1) {
//NSLog(#"Loading queued page %d", needDisplay);
[self loadScrollViewWithPage:needDisplay forceAddToSuperview:NO animated:YES];
needDisplay = -1;
}
// minLoadedPageIndex - index of the first loaded page.
int selectedIndex = MIN(floor((aScrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1 + minLoadedPageIndex, [photoItems count] - 1);
[self loadScrollViewWithPage:selectedIndex - 1 forceAddToSuperview:NO animated:YES];
[self loadScrollViewWithPage:selectedIndex forceAddToSuperview:NO animated:YES];
[self loadScrollViewWithPage:selectedIndex + 1 forceAddToSuperview:NO animated:YES];
}
In loadScrollViewWithPage I create the page view controller which loads the data from the server in the background. I don't add the view to the scrollview until it loads the data from the server.
- (void)loadScrollViewWithPage:(int)page forceAddToSuperview:(BOOL)value animated:(BOOL)animated
{
DetailController *controller = page >= viewControllers.count ? [NSNull null] :[viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null])
{
controller = [[DetailController alloc] initWithNibName:#"DetailController" bundle:nil];
controller.delegate = self;
controller.view.hidden = NO; //this will call viewDidLoad.
if (page >= viewControllers.count) {
[viewControllers addObject:controller];
}
else {
[viewControllers replaceObjectAtIndex:page withObject:controller];
}
[controller release];
}
// add the controller's view to the scroll view
if (controller.view && controller.view.superview == nil && (controller.isLoadedOrFailed || value)) {
[self setNumberOfVisiblePages:visiblePagesCount+1];
if (page < selectedIndex) {
// We are adding the page to the left of the current page,
// so we need to adjust the content offset.
CGFloat offset = (int)scrollView.contentOffset.x % (int)scrollView.frame.size.width;
offset = scrollView.frame.size.width * (selectedIndex - minLoadedPageIndex) + offset;
[scrollView setContentOffset:CGPointMake(offset, 0.0) animated:animated];
}
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * (page - minLoadedPageIndex);
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
[controller viewWillAppear:NO];
}
}
Also I have a detailControllerDidFinishDownload method which is called when data for the page view controller has been loaded.
- (void)detailControllerDidFinishDownload:(DetailController *)controller
{
... //here I calculate new minLoadedPageIndex value
// reset pages frames
if (minLoadedPageIndex < oldMinPage) {
for(int i=oldMinPage;i < [viewControllers count]; i++) {
DetailController *detailsController = [viewControllers objectAtIndex:i];
if ((NSNull *)detailsController != [NSNull null]) {
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * (i - minLoadedPageIndex);
frame.origin.y = 0;
[detailsController.view setFrame:frame];
}
}
}
// load the page now or delay load until the scrolling will be finished
if (!isScrolling) {
[self loadScrollViewWithPage:[photoItems indexOfObject:controller.photoItem] forceAddToSuperview:NO animated:NO];
}
else {
needDisplay = [photoItems indexOfObject:controller.photoItem];
//NSLog(#"pageControlUsed is used!!! %d", [photoItems indexOfObject:controller.photoItem]);
}
}
The problem I have now is that sometimes the scroll stucks on the middle (or somewhere near to middle) of the pages and it won't go to the nearest page bounce until I slightly more it. My tests showed that this situation happens if I scroll out of content view frame (the scroll view have bounces on) and wait for the new page to load. In 1 of the 10 times the scroll stucks.
Thanks, Mikhail!
There are a lot of things you are requiring at the same time. I suggest you have a look at this example.
http://ykyuen.wordpress.com/2010/05/22/iphone-uiscrollview-with-paging-example/
It's a very good starting point.

iphone: error and pdf pages question

I am building a pdf viewer app. my skills are at medium LOL voodoo level now... I've got real far on my own with this app but I'm pretty stuck ether on my theory or on my code. My app has a paging scroll view that makes its self the length of the entire pdf document, then it shows the current page in another scrollview (ImageScrollView) which is its own class. ImageScrollView makes a UIView which does the CATiledLayer usual stuff and it all work fine! :)
My ImageScrollView shows the on-screen page (when you scroll to the next page ImageScrollView loads again CATiledLayer on-screen and you can see the tiles). I've been researching about how to get the pages left and right of the current page to preload (as I don't think having a pdf load as tiles on-screen is good for the user experience) but Im not to sure if I'm thinking about it correctly.
Maybe I should be making a left and right UIView that sit next to the onscreen UIView in ImageScrollView?
or maybe it has to do with recycling no-longer-visible pages as seen below (but I think I would still need views to the left/right and even still wont I need to recycle the views also??)
- (void)tilePages {
// Calculate which pages are visible
CGRect visibleBounds = pagingScrollView.bounds;//CGRect visibleBounds = CGRectMake(0.0f, 0.0f, 320.0f * [self pdfPageCount], 435.0f);
int firstNeededPageIndex = floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds));
int lastNeededPageIndex = floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));
firstNeededPageIndex = MAX(firstNeededPageIndex, 0);
lastNeededPageIndex = MIN(lastNeededPageIndex, [self pdfPageCount] - 1);
// Recycle no-longer-visible pages
for (ImageScrollView *page in visiblePages) {
if (page.index < firstNeededPageIndex || page.index > lastNeededPageIndex) {
[recycledPages addObject:page];
[page removeFromSuperview];}}
[visiblePages minusSet:recycledPages];
// add missing pages
for (int index = firstNeededPageIndex; index <= lastNeededPageIndex; index++) {
if (![self isDisplayingPageForIndex:index]) {
ImageScrollView *page = [self dequeueRecycledPage];
if (page == nil) {
page = [[[ImageScrollView alloc] init] autorelease];}
[self configurePage:page forIndex:index];
[pagingScrollView addSubview:page];
[visiblePages addObject:page];}}}
I changed the code to see what happened below (not sure) also I get error: initialization makes pointer from integer without a cast
- (void)tilePages:(NSUInteger) index {
// Calculate which pages are visible
CGRect visibleBounds = pagingScrollView.bounds;
int firstNeededPageIndex = floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds));
int lastNeededPageIndex = floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));
firstNeededPageIndex = MAX(firstNeededPageIndex, 0);
lastNeededPageIndex = MIN(lastNeededPageIndex, [self pdfPageCount] - 1);
// Recycle no-longer-visible pages
for (ImageScrollView *page in visiblePages) {
if (page.index < firstNeededPageIndex || page.index > lastNeededPageIndex) {
// visible N/Ppages *start*
if (index == 1) {
ImageScrollView *Npage = Npage.index +1;
Npage = [[[ImageScrollView alloc] init] autorelease];
[self configurePage:Npage forIndex:index +1];
[pagingScrollView addSubview:Npage];
[visiblePages addObject:Npage];}
if (index < 2 || index > [self pdfPageCount] -2) {
ImageScrollView *Ppage = Ppage.index -1;
ImageScrollView *Npage = Npage.index +1;
Ppage = [[[ImageScrollView alloc] init] autorelease];
[self configurePage:Ppage forIndex:index -1];
[pagingScrollView addSubview:Ppage];
[visiblePages addObject:Ppage];
Npage = [[[ImageScrollView alloc] init] autorelease];
[self configurePage:Npage forIndex:index +1];
[pagingScrollView addSubview:Npage];
[visiblePages addObject:Npage];}
if (index == [self pdfPageCount] -1) {
ImageScrollView *Ppage = Ppage.index -1;
Ppage = [[[ImageScrollView alloc] init] autorelease];
[self configurePage:Ppage forIndex:index -1];
[pagingScrollView addSubview:Ppage];
[visiblePages addObject:Ppage];}
// visible N/Ppages *end*
[recycledPages addObject:page];
[page removeFromSuperview];}}
[visiblePages minusSet:recycledPages];
// add missing pages
for (int index = firstNeededPageIndex; index <= lastNeededPageIndex; index++) {
// recycled N/Ppages *start*
if (index == firstNeededPageIndex +1) {
ImageScrollView *Npage = Npage.index +1;
[recycledPages addObject:Npage];
[Npage removeFromSuperview];}
if (index < firstNeededPageIndex +2 || index > lastNeededPageIndex -2) {
ImageScrollView *Ppage = Ppage.index -1;
ImageScrollView *Npage = Npage.index +1;
[recycledPages addObject:Ppage];
[Ppage removeFromSuperview];
[recycledPages addObject:Npage];
[Npage removeFromSuperview];}
if (index == lastNeededPageIndex -1) {
ImageScrollView *Ppage = Ppage.index -1;
[recycledPages addObject:Ppage];
[Ppage removeFromSuperview];}
// recycled N/Ppages *end*
if (![self isDisplayingPageForIndex:index]) {
ImageScrollView *page = [self dequeueRecycledPage];
if (page == nil) {
page = [[[ImageScrollView alloc] init] autorelease];}
[self configurePage:page forIndex:index];
[pagingScrollView addSubview:page];
[visiblePages addObject:page];}}}
Could I store 3 pdf pages in an NSIndex? eg: previousPageToRecycle, previousPage, currentPage, nextPage, nextPageToRecycle
I'm unsure of how to do this.
ImageScrollView *Ppage = Ppage.index -1;
Ppage.index -1 is an integer. You're assigning it to a variable of type ImageScrollView*, which is supposed to magically turn it into a pointer. This is probably a mistake.
The first bit of code is much more readable, and looks like it's vaguely on the right track.
The second bit of code is completely unreadable due to lack of sane indenting. There are also some problems:
There are far too many special cases. Simplify your logic; it makess your code easier to get right and easier to maintain.
There is a lot of duplicated code. This is related to the special-casing.
I can't for the life of me figure out why firstNeededPageIndex+1 and lastNeededPageIndex-1 are special.
I would not use an NSSet for so few pages. I don't think being able to use minusSet: is a significant advantage over just using removeObject: (and if you're worried about performance, just use removeObjectIdenticalTo:)
I'm not sure what you mean by "storing pages in an NSIndex"; do you mean CFIndex or NSInteger?
And now, an overall answer:
In general, you're on the right track, but you need to load up to five pages:
The current page, i.
Pages i-1 and i+1. The user can start scrolling at any time; you don't want the display to lag while you render some tiles.
Pages i-2 and i+2. Bouncy-scrolling means if the user scrolls from i to i+1, it will show a few pixels of i+2. You don't want to load pages during the scroll animation, or it'll appear to lag (unless you can make it load in a background thread somehow). Note that if the user scrolls from i to i+1 to i+2, it'll "bounce" at i+3. You can load more, but I consider this uncommon enough that a little lag is justified.
I implemented this using a ring-buffer-like array of size 8; page i is stored in ring[i%8] if it is loaded. Since I only need to have 5 pages at a time, 8 is plenty; admittedly there's a largeish pile of ring-buffer-management code that you need to get right (mostly it just takes a while to code).
I also just used a "current page", loading i±1 during a scroll (hopefully they're already loaded) and i±2 at viewDidAppear: and at the end of a scroll (scrollViewDidEndDragging:willDecelerate: with decelerate=NO, and scrollViewDidEndDecelerating:).
You might also want to hold on to views a little longer (preload i±2 but keep i±3 if they happen to be loaded), otherwise views will be needlessly reloaded if the user moves back and forth during a scroll (laaaaaag).
If you show a page number, you might also want to add some hysteresis. I only changed pages when the user scrolled 2/3 of the way into the next page (that means to toggle between two pages, the user has to move back and forth by 1/3 page). If you implement hysteresis, the previous paragraph isn't such a big deal.