Show PDF on a UIScrollView - iphone

I found an example code for showing PDF file on a UIScrollView with horizontal scrolling. It works fine, but problem is it shows only 2 pages of PDF. I tried in my best to figure out the issue, but I couldn't figure it. Can you please give me help?

I looked at that sample project you pointed to in this question and like you, I can see it only displays two pages of whatever PDF file it's given to display.
The problem with the sample code comes in the PDFViewController.m file. For these lines:
PDFScrollView *page = [self dequeueRecycledPage];
if (page == nil) {
page = [[[PDFScrollView alloc] initWithPage:index + 1 frame:[self frameForPageAtIndex:index]] autorelease];
}
I added
else {
[page setPage: index inFrame:[self frameForPageAtIndex: index]];
}
And also these new lines into PDFScrollView.h
- (void) setPage: (NSInteger) onPage inFrame:(CGRect)frame;
And PDFScrollView.m
- (void) setPage: (NSInteger) onPage inFrame:(CGRect) frame
{
if(pdfView)
{
[pdfView removeFromSuperview];
[pdfView release];
}
self.frame = frame;
self.index = onPage;
pdfView = [[PDFViewTiled alloc] initWithPage:onPage frame:self.frame];
[self addSubview:pdfView];
}
This isn't a perfect fix. You'll see the drawing isn't proper, especially when backing up pages. I'll leave that to you as an exercise to take care of, but hopefully this is a nice start.
And I hope it helps you out.

Related

UIAlertView Rendering Error

I have been working on an app for a couple of months now, but have finally run into an issue that I can't solve myself, and can't find anything on the internet to help.
I am using several normal UIAlertViews, in my app. Some have 2 buttons, some have 3 buttons, and a couple have 2 buttons and a text field. However all have the same issue. When you call [someAlertView show]; the alert view appears as normal, but then suddenly its graphics context seems to get corrupted as you can see from the screenshot.
This happens on both iPhone and iPad simulators (both 5.0 and 5.1), and happens on an iPad and iPhone4S device as well.
The image showing through is whatever happens to be behind the alertView.
The Alert still works, I can click the buttons, type in the text field, then when it dismisses the delegate methods are called correctly and everything goes back to normal. When the alertView appears again, the same thing happens.
The view behind the alert is a custom UIScrollView subclass with a content size of approximately 4000 pixels by 1000 with a UIImage as the background. The png file is mostly transparent, so is only about 80kB in memory size, and the phone is having no issues rendering it - the scroll view is still fully responsive and not slow.
It also has a CADisplayLink timer attached to it as part of the subclass. I have tried disabling this just before the alertView is shown, but it makes no difference so I am doubtful that is the issue.
This app is a partial rewrite of one I made for a university project, and that one could display UIAlertViews over the top of a scrollView of the same size and subclass without issue. The difference between this app and that one is that in my old app, I had subclassed UIAlertView to add extra things such as a pickerView, however I decided that I didn't like the way it looked so moved everything out of the alert and am just sticking with a standard UIAlertView.
This is how the alertView in the screenshot is called:
- (IBAction)loadSimulation:(id)sender {
importAlert = [[UIAlertView alloc] initWithTitle:#"Load Simulation" message:#"Enter Filename:" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Load", nil];
[importAlert setAlertViewStyle:UIAlertViewStylePlainTextInput];
[importAlert showPausingSimulation:self.simulationView]; //Calling [importAlert show]; makes no difference.
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
[self hideOrganiser]; //Not an issue as the problem occurs on iPad as well.
}
}
With this being the categorised AlertView to add the ability to stop the scrollViews CADisplay link.
#interface UIAlertView(pauseDisplayLink)
- (void)showPausingSimulation:(UILogicSimulatorView*)simulationView;
#end
#implementation UIAlertView(pauseDisplayLink)
- (void)showPausingSimulation:(UILogicSimulatorView *)simulationView {
[simulationView stopRunning];
[simulationView removeDisplayLink]; //displayLink needs to be removed from the run loop, otherwise it will keep going in the background and get corrupted.
[self show];
}
I get no memory warnings when this happens, so I am doubtful it is due to lack of resources.
Has anyone come across an issue like this before? If you need further information I can try to provide it, but I am limited in what code I can post. Any help would be appreciated, I've been trying to solve this for two weeks and can't figure it out.
Edit:
It appears that it is not the AlertView at all (or rather it is not just the alertView), as the problem goes away when I remove the scroll view behind it, so there must be some issue between the two. This is the code for my UIScrollView subclass:
.h file:
#import
#import
#class ECSimulatorController;
#interface UILogicSimulatorView : UIScrollView {
CADisplayLink *displayLink;
NSInteger _updateRate;
ECSimulatorController* _hostName;
}
#property (nonatomic) NSInteger updateRate;
#property (nonatomic, strong) ECSimulatorController* hostName;
- (void) removeDisplayLink;
- (void) reAddDisplayLink;
- (void) displayUpdated:(CADisplayLink*)timer;
- (void) startRunning;
- (void) stopRunning;
- (void) refreshRate:(NSInteger)rate;
- (void) setHost:(id)host;
- (void)setMinimumNumberOfTouches:(NSInteger)touches;
- (void)setMaximumNumberOfTouches:(NSInteger)touches;
#end
.m file:
#import "UILogicSimulatorView.h"
#import "ECSimulatorController.h"
#import <QuartzCore/QuartzCore.h>
#implementation UILogicSimulatorView
#synthesize updateRate = _updateRate;
#synthesize hostName = _hostName;
- (void)reAddDisplayLink {
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; //allows the display link to be re-added to the run loop after having been removed.
}
- (void)removeDisplayLink {
[displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; //allows the display link to be removed from the Run loop without deleting it. Removing it is essential to prevent corruption between the games and the simulator as both use CADisplay link, and only one can be in the run loop at a given moment.
}
- (void)startRunning {
[self refreshRate:self.updateRate];
[displayLink setPaused:NO];
}
- (void)refreshRate:(NSInteger)rate {
if (rate > 59) {
rate = 59; //prevent the rate from being set too an undefined value.
}
NSInteger frameInterval = 60 - rate; //rate is the number of frames to skip. There are 60FPS, so this converts to frame interval.
[displayLink setFrameInterval:frameInterval];
}
- (void)stopRunning {
[displayLink setPaused:YES];
}
- (void)displayUpdated:(CADisplayLink*)timer {
//call the function that the snakeController host needs to update
[self.hostName updateStates];
}
- (void)setHost:(ECSimulatorController*)host;
{
self.hostName = host; //Host allows the CADisplay link to call a selector in the object which created this one.
}
- (id)initWithFrame:(CGRect)frame
{
//Locates the UIScrollView's gesture recogniser
if(self = [super initWithFrame:frame])
{
[self setMinimumNumberOfTouches:2];
displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(displayUpdated:)]; //CADisplayLink will update the logic gate states.
self.updateRate = 1;
[displayLink setPaused:YES];
}
return self;
}
- (void)setMinimumNumberOfTouches:(NSInteger)touches{
for (UIGestureRecognizer *gestureRecognizer in [self gestureRecognizers])
{
if([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]])
{
//Changes the minimum number of touches to 'touches'. This allows the UIPanGestureRecogniser in the object which created this one to work with one finger.
[(UIPanGestureRecognizer*)gestureRecognizer setMinimumNumberOfTouches:touches];
}
}
}
- (void)setMaximumNumberOfTouches:(NSInteger)touches{
for (UIGestureRecognizer *gestureRecognizer in [self gestureRecognizers])
{
if([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]])
{
//Changes the maximum number of touches to 'touches'. This allows the UIPanGestureRecogniser in the object which created this one to work with one finger.
[(UIPanGestureRecognizer*)gestureRecognizer setMaximumNumberOfTouches:touches];
}
}
}
#end
Well, I have managed to come up a solution to this. Really it is probably just masking the issue rather than finding the route cause, but at this point I will take it.
First some code:
#interface UIView (ViewCapture)
- (UIImage*)captureView;
- (UIImage*)captureViewInRect:(CGRect)rect;
#end
#implementation UIView (ViewCapture)
- (UIImage*)captureView {
return [self captureViewInRect:self.frame];
}
- (UIImage*)captureViewInRect:(CGRect)rect
{
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.layer renderInContext:context];
UIImage *screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return screenShot;
}
#end
- (void)showPausingSimulation:(UILogicSimulatorView *)simulationView {
[simulationView stopRunning];
UIView* superView = simulationView.superview;
CGPoint oldOffset = simulationView.contentOffset;
for (UIView* subview in simulationView.subviews) {
//offset subviews so they appear when content offset is (0,0)
CGRect frame = subview.frame;
frame.origin.x -= oldOffset.x;
frame.origin.y -= oldOffset.y;
subview.frame = frame;
}
simulationView.contentOffset = CGPointZero; //set the offset to (0,0)
UIImage* image = [simulationView captureView]; //Capture the frame of the scrollview
simulationView.contentOffset = oldOffset; //restore the old offset
for (UIView* subview in simulationView.subviews) {
//Restore the original positions of the subviews
CGRect frame = subview.frame;
frame.origin.x += oldOffset.x;
frame.origin.y += oldOffset.y;
subview.frame = frame;
}
[simulationView setHidden:YES];
UIImageView* imageView = [[UIImageView alloc] initWithFrame:simulationView.frame];
[imageView setImage:image];
[imageView setTag:999];
[superView addSubview:imageView];
[imageView setHidden:NO];
superView = nil;
imageView = nil;
image = nil;
[self show];
}
- (void)dismissUnpausingSimulation:(UILogicSimulatorView *)simulationView {
UIView* superView = simulationView.superview;
UIImageView* imageView = (UIImageView*)[superView viewWithTag:999];
[imageView removeFromSuperview];
imageView = nil;
superView = nil;
[simulationView setHidden:NO];
[simulationView startRunning];
}
Then modifying the dismiss delegate method in my class to have this line:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
[alertView dismissUnpausingSimulation:self.simulationView];
...
When the alert view is called, but before it is shown, I need to hide the simulator to prevent it corrupting the alert. However just hiding it is ugly as then all is visible behind is a empty view.
To fix this, I first make a UIImage from the simulator views graphics context. I then create a UIImageView with the same frame as the simulator and set the UIImage as its image.
I then hide the simulator view (curing the alert issue), and add my new UIImageView to the simulators superview. I also set the tag of the image view so I can find it later.
When the alert dismisses, the image view is then recovered based on its tag, and removed from its superview. The simulator is then unhidden.
The result is that the rendering issue is gone.
I know its too late for an answer to this question. Lately I had experianced this very same issue.
My Case:
Added couple of custom UIViews with background images and some controlls to the scroll view with shadow effect. I had also set the shadowOffset.
The Solution:
After some step by step analysis, I found out that setting the setShadowOpacity caused The rendering problem for me. When i commented that line of code, it cured the UIAlertView back to normal appearance.
More:
To make sure, I created a new project mimicing the original ui with shadowOpacity. But it didnt caused the rendering problem as i expected. So I am not sure about the root cause. For me it was setShadowOpacity.

subview not showing on already displayed view

I'm new on iPhone development and I'm stuck with a subview not showing after the controller is notified to refresh these items.
So, it's my main page showing, showing the 3-last items of a newsfeed at the bottom.
The view controler handles the loading of the newsfeed (in a NSArray).
When loading the page, the newsfeed is correctly displayed as a subview of the main view. However, when the view already exists (for example just after login for the first time), the newsfeed is not displayed, or refresehed. The avatar images that are fetched from internet asynchronously are displayed correctly. But not the labels.
The debug shows that the code is correctly executed...but nothing happens...
Here is the code handling the notification and refresh.
I'm sure I miss something stupid but cannot put my finger on it.
Does an UILabel or UIImage of the subview can avoid the element to be refreshed?
Thanks for the help!
The controler code
-(void) handleRefreshNewsfeed:(NSNotification*)notification{
if (self.newsfeedMostRecentDate == nil){
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyyMMdd HH:mm"];
self.newsfeedMostRecentDate = [dateFormatter dateFromString:#"20100101 00:00"];
[dateFormatter release];
}
NSArray * newsfeed = [cdw getNewsfeedForUser:self.currentUser andLimit:self.newsfeedLimit];
if ([newsfeed count] > 0){
self.newsfeedMostRecentDate = [(Newsfeed *)[newsfeed objectAtIndex:0] posted];
if ([self.view isKindOfClass:[MenuView class]]){
[(MenuView *)self.view refreshNewsfeed:newsfeed];
}
}
}
The main view declaration:
#interface MenuView : UIView {
MenuNewsfeed * newsFeedView;
}
#property(nonatomic, retain) IBOutlet MenuNewsfeed *newsFeedView;
-(void)displayNewsfeed:(NSArray *)newsfeed;
-(void)refreshNewsfeed:(NSArray *)newsfeed;
The main view code :
-(void)displayNewsfeed:(NSArray *)newsfeed{
[self.newsFeedView setCurrentUser:currentUser];
[self.newsFeedView displayNewsfeed:newsfeed];
[UIView animateWithDuration:0.75f
delay: 0.0
options: UIViewAnimationOptionCurveEaseIn
animations:^{
self.newsFeedView.alpha = 1.0;
}
completion:^(BOOL finished){
//nothing
}
];
}
-(void)refreshNewsfeed:(NSArray *)newsfeed{
if ([self.newsFeedView.newsfeed count] > 0){
[UIView animateWithDuration:0.50f
delay: 0.0
options: UIViewAnimationOptionCurveEaseIn
animations:^{
self.newsFeedView.alpha = 0.0;
}
completion:^(BOOL finished){
// [self displayNewsfeed:newsfeed];
}
];
}else {
[self displayNewsfeed:newsfeed];
}
}
Michael
This may not answer your problem.
However, there is a small glitch that I can see. The CGRectMake (50, 100, 55, 12) will create a rect of 12px wight but 55px height.
Well, at least "te" or "tes" of your text should be visible as I do not see any insects. With some insect value (10 or so) it could happen that noting of your text string is visible.
Or no. When a text is clipped, depending on the default line break mode, it could be truncated to a certain extend and some "..." added at the end.
Could it be that you only see some points instead of "test test test"?
Otherwise check wether the tmp label is hidden by some other view, although it should be most on top of the view's stack.
But give it some realistic rect of maybe 20, 100, just to be save, before you do any further testing.
I've found out what the problem was : the code handling the notification (and modifying the UI) was not processed on the main thread.
[self performSelectorOnMainThread:#selector(refreshNewsfeed) withObject:notification.object waitUntilDone:NO];
Calling the refresh method via performSelectorOnMainThread solved the issue.

cover flow effect for the list of pdf

I wanted to know how can we implement the cover flow effect for the list of pdfs and on double tap ,it should open respective pdf. I m really struck between ,please help me out
thanks in advance
In openFlowView.m file search this method
- (void)awakeFromNib {
[self setUpInitialState];
}
Now when you find this replace this method by below or add lines of tapGestureRecognizer.
- (void)awakeFromNib {
[self setUpInitialState];
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(screenTapped:)];
[tapRecognizer setNumberOfTapsRequired:2];
[self addGestureRecognizer:tapRecognizer];
[tapRecognizer release];
}
screenTapped is my method that gets called when I tap twice on cover flow.
- (void)screenTapped:(UITapGestureRecognizer *)tap {
NSLog(#"Screen tapped");
//put your points of your coverflow coordinates
if(coverPointimage.x > 0 && coverPointimage.x <= 265 && coverPointimage.y >0 && coverPointimage.y <= 205){
[self imageClicked];//either write code or made seperate method that should gets called when you do double tap.
}
}
Hope this code will save your couple of hours.Happy Coding.
You should try out the Tapku Library for coverflow effect. Its really easy and customizable. You can find it here.

CALayer NAN deleting section of UITableView with custom UItableviewCell

Hi i dont know where to look further im totally stuck:
Scenario: When i leave edit mode of my view i delete rows and sections of my table.
The table consits of different custom cells which have animation when entering or leaving
editmode.
Problem: Sometimes and i dont know when, i cant reproduce it, the app crashes with a "
CALayerInvalidGeometry', reason: 'CALayer position contains NaN: [nan 2.03571e-10]" error message.
I looked for hours now but i cant find the error therefor i provide some more code maybe anyone knows where it happens:
I call tis method to leave edit mode of the table in it setupDetailEditMode gets called.
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
if(editing)
{..}
else{
tableShowEditMode = NO;
[self setupDetailEditMode];
[self createPersonTableHeader];
[self clearAllEmptyFieldsFromArray];
[self saveUserProfile];
[super setEditing:editing animated:animated];
[profileTable setEditing:editing animated:YES];
......}
This code is where i delete sections.... some of the sections still have rows which is no problem cause they will be deleted by deleting the section in the following function it crashes last line:
part of function **setupDetailEditMode**:
//Phone Section
if(removedRowsPhone)
{
if(app.isitUserProfile.isitPhone.count == 0)
[sectionOperationIndexSet addIndex:sectionWorked];
sectionWorked++;
}
//Email Section
if(removedRowsEmail)
{
if(app.isitUserProfile.isitEmail.count == 0)
[sectionOperationIndexSet addIndex:sectionWorked];
sectionWorked++;
}
//update table data structur
[self buildCurrentTableStructure];
//delete sections <<<<<**here will crash the app sometimes**
[profileTable deleteSections:sectionOperationIndexSet withRowAnimation:UITableViewRowAnimationFade];
I add a random custom cell i use maybe there is something wrong that causes the crash in layoutsubviews:
- (void) layoutSubviews {
[super layoutSubviews];
if (self.editing)
{
primaryField.enabled = YES;
// (self.editing && !self.showingDeleteConfirmation)
star1Img.frame = CGRectMake(87.0,30.0,20.0, 20.0);
star2Img.frame = CGRectMake(111.0,30.0,20.0, 20.0);
star3Img.frame = CGRectMake(135.0,30.0,20.0, 20.0);
star4Img.frame = CGRectMake(159.0,30.0,20.0, 20.0);
star5Img.frame = CGRectMake(183.0,30.0,20.0, 20.0);
//primaryLabel.frame = CGRectMake(primaryLabel.frame.origin.x, 8.0,180.0 , 17.0);
primaryField.frame = CGRectMake(primaryField.frame.origin.x, 8.0,180.0 , 17.0);
secondaryLabel.frame = CGRectMake(secondaryLabel.frame.origin.x,18.0, 65.0 , 21.0);
}
else
{
primaryField.enabled = NO;
star1Img.frame = CGRectMake(29.0,27.0,10.0, 10.0);
star2Img.frame = CGRectMake(39.0,27.0,10.0, 10.0);
star3Img.frame = CGRectMake(49.0,27.0,10.0, 10.0);
star4Img.frame = CGRectMake(59.0,27.0,10.0, 10.0);
star5Img.frame = CGRectMake(69.0,27.0,10.0, 10.0);
primaryField.frame = CGRectMake(primaryField.frame.origin.x, 14.0,213.0 , 17.0);
secondaryLabel.frame = CGRectMake(14.0,6.0, 65.0 , 21.0);
}
} // layoutSubviews*/
- (void) setStarRating:(int) rating
{
if(rating>=1)
[star1Img setImage:[UIImage imageNamed:#"goldstar20.png"]];
......}
I am very frustrated atm because i cant find this bug it would be very niice if anyone has a hint or find the bug. Anything will help. Thanks
-Bevo
I haven't looked through your code very much, but I had something similar once. There was a view that got released but did want to be painted again - basically a retain thing. Hard to find - or understand - even via profiler. You should check with the profiler if there is a leak anywhere.

Optimized Image Loading in a UIScrollView

I have a UIScrollView that has a set of images loaded side-by-side inside it. You can see an example of my app here: http://www.42restaurants.com. My problem comes in with memory usage. I want to lazy load the images as they are about to appear on the screen and unload images that aren't on screen. As you can see in the code I work out at a minimum which image I need to load and then assign the loading portion to an NSOperation and place it on an NSOperationQueue. Everything works great apart from a jerky scrolling experience.
I don't know if anyone has any ideas as to how I can make this even more optimized, so that the loading time of each image is minimized or so that the scrolling is less jerky.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
[self manageThumbs];
}
- (void) manageThumbs{
int centerIndex = [self centerThumbIndex];
if(lastCenterIndex == centerIndex){
return;
}
if(centerIndex >= totalThumbs){
return;
}
NSRange unloadRange;
NSRange loadRange;
int totalChange = lastCenterIndex - centerIndex;
if(totalChange > 0){ //scrolling backwards
loadRange.length = fabsf(totalChange);
loadRange.location = centerIndex - 5;
unloadRange.length = fabsf(totalChange);
unloadRange.location = centerIndex + 6;
}else if(totalChange < 0){ //scrolling forwards
unloadRange.length = fabsf(totalChange);
unloadRange.location = centerIndex - 6;
loadRange.length = fabsf(totalChange);
loadRange.location = centerIndex + 5;
}
[self unloadImages:unloadRange];
[self loadImages:loadRange];
lastCenterIndex = centerIndex;
return;
}
- (void) unloadImages:(NSRange)range{
UIScrollView *scrollView = (UIScrollView *)[[self.view subviews] objectAtIndex:0];
for(int i = 0; i < range.length && range.location + i < [scrollView.subviews count]; i++){
UIView *subview = [scrollView.subviews objectAtIndex:(range.location + i)];
if(subview != nil && [subview isKindOfClass:[ThumbnailView class]]){
ThumbnailView *thumbView = (ThumbnailView *)subview;
if(thumbView.loaded){
UnloadImageOperation *unloadOperation = [[UnloadImageOperation alloc] initWithOperableImage:thumbView];
[queue addOperation:unloadOperation];
[unloadOperation release];
}
}
}
}
- (void) loadImages:(NSRange)range{
UIScrollView *scrollView = (UIScrollView *)[[self.view subviews] objectAtIndex:0];
for(int i = 0; i < range.length && range.location + i < [scrollView.subviews count]; i++){
UIView *subview = [scrollView.subviews objectAtIndex:(range.location + i)];
if(subview != nil && [subview isKindOfClass:[ThumbnailView class]]){
ThumbnailView *thumbView = (ThumbnailView *)subview;
if(!thumbView.loaded){
LoadImageOperation *loadOperation = [[LoadImageOperation alloc] initWithOperableImage:thumbView];
[queue addOperation:loadOperation];
[loadOperation release];
}
}
}
}
EDIT:
Thanks for the really great responses. Here is my NSOperation code and ThumbnailView code. I tried a couple of things over the weekend but I only managed to improve performance by suspending the operation queue during scrolling and resuming it when scrolling is finished.
Here are my code snippets:
//In the init method
queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:4];
//In the thumbnail view the loadImage and unloadImage methods
- (void) loadImage{
if(!loaded){
NSString *filename = [NSString stringWithFormat:#"%03d-cover-front", recipe.identifier, recipe.identifier];
NSString *directory = [NSString stringWithFormat:#"RestaurantContent/%03d", recipe.identifier];
NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:#"png" inDirectory:directory];
UIImage *image = [UIImage imageWithContentsOfFile:path];
imageView = [[ImageView alloc] initWithImage:image andFrame:CGRectMake(0.0f, 0.0f, 176.0f, 262.0f)];
[self addSubview:imageView];
[self sendSubviewToBack:imageView];
[imageView release];
loaded = YES;
}
}
- (void) unloadImage{
if(loaded){
[imageView removeFromSuperview];
imageView = nil;
loaded = NO;
}
}
Then my load and unload operations:
- (id) initWithOperableImage:(id<OperableImage>) anOperableImage{
self = [super init];
if (self != nil) {
self.image = anOperableImage;
}
return self;
}
//This is the main method in the load image operation
- (void)main {
[image loadImage];
}
//This is the main method in the unload image operation
- (void)main {
[image unloadImage];
}
I'm a little puzzled by the "jerky" scrolling. Since NSOperationQueue runs operations on separate thread(s) I'd have expected at worst you might see empty UIImageViews showing up on the screen.
First and foremost I'd be looking for things that are impacting the processor significantly as NSOperation alone should not interfere with the main thread. Secondly I'd be looking for details surrounding the NSOperation setup and execution that might be causing locking and syncing issues which could interrupt the main thread and therefore impact scrolling.
A few items to consider:
Try loading your ThumbnailView's with a single image at the start and disabling the NSOperation queuing (just skip everything following the "if loaded" check. This will give you an immediate idea whether the NSOperation code is impacting performance.
Keep in mind that -scrollViewDidScroll: can occur many times during the course of a single scroll action. Depending on how for the scroll moves and how your -centerThumbIndex is implemented you might be attempting to queue the same actions multiple times. If you've accounted for this in your -initWithOperableImage or -loaded then its possible you code here is causing sync/lock issues (see 3 below). You should track whether an NSOperation has been initiated using an "atomic" property on the ThumbnailView instance. Prevent queuing another operation if that property is set and only unset that property (along with loaded) at the end of the NSOperation processes.
Since NSOperationQueue operates in its own thread(s) make sure that none of your code executing within the NSOperation is syncing or locking to the main thread. This would eliminate all of the advantages of using the NSOperationQueue.
Make sure your "unload" operation has a lower priority than your "load" operation, since the priority is the user experience first, memory conservation second.
Make sure you keep enough thumbnails for at least a page or two forward and back so that if NSOperationQueue falls behind, you have a high margin of error before blank thumbnails become visible.
Make sure your load operation is only loading a "pre-scaled" thumbnail and not loading a full size image and rescaling or processing. This would be a lot of extra overhead in the middle of a scrolling action. Go even further and make sure you've converted them to PNG16 without an alpha channel. This will give at least a (4:1) reduction in size with hopefully no detectable change in the visual image. Also consider using PVRTC format images which will take the size down even further (8:1 reduction). This will greatly reduced the time it takes to read the images from "disk".
I apologize if any of this doesn't make sense. I don't see any issues with the code you've posted and problems are more likely to be occurring in your NSOperation or ThumbnailView class implementations. Without reviewing that code, I may not be describing the conditions effectively.
I would recommend posting your NSOperation code for loading and unloading and at least enough of the ThumbnailView to understand how it interacts with the NSOperation instances.
Hope this helped to some degree,
Barney
One option, although less visually pleasing, is to only load images when the scrolling stops.
Set a flag to disable image loading in:
-scrollViewWillBeginDragging:
Re-enable loading images when the scrolling stops using the:
-scrollViewDidEndDragging:willDecelerate:
UIScrollViewDelegate method. When the willDecelerate: parameter is NO, the movement has stopped.
the problem is here:
UIImage *image = [UIImage imageWithContentsOfFile:path];
It seems that threaded or not when you load a file from disk (which maybe that happens on the main thread regardless, I'm not totally sure) everything stalls. You normally don't see this in other situations because you don't have such a large area moving if any at all.
While researching this problem, I found two more resources that may be of interest:
Check out the iPhone sample project "PageControl": http://developer.apple.com/iphone/library/samplecode/PageControl/index.html
It lazy loads view controllers in a UIScrollView.
and -
Check out the cocoa touch lib: http://github.com/facebook/three20 which has a 'TTPhotoViewController' class that lazy loads photos/thumbnails from web/disk.
Set shouldRasterize = YES for the sub content view adde to the scrollview. It is seen to remove the jerky behavior of custom created scroll view like a charm. :)
Also do some profiling using the instruments in the Xcode. Go through the tutorials created for profiling by Ray Wenderlich it helped me a lot.