问题
I am trying to make a basic photo grid, that is a "grid" of small "thumbnail" images, that when clicked go to a big version of the image. I am using a UITableView to accomplish this. My problem is that when I try adding rotation support I cant get the table to redraw the grid with an extra row of images.
Here's some of my code, PLEASE feel free to ask any question as I'm at my wits end on this one >:(
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell==nil){
cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
if(self.interfaceOrientation==UIInterfaceOrientationPortrait || self.interfaceOrientation==UIInterfaceOrientationPortraitUpsideDown){
NSLog(@"portrait");
for (int i=0; i < self.numberOfColumns; i++) {
if (self.photoIndex < [self.photosArray count] || self.shouldReload==TRUE) {
UIButton *imageButton = [[UIButton alloc]init];
UIActivityIndicatorView *loadingActivityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[imageButton addTarget:self action:@selector(imageClicked:) forControlEvents:UIControlEventTouchDown];
switch (self.buttonNumber) {
case 1:
self.xCord=35.0f;
self.buttonNumber++;
break;
case 2:
self.xCord=213.25f;
self.buttonNumber++;
break;
case 3:
self.xCord=391.5f;
self.buttonNumber++;
break;
case 4:
self.xCord=569.75f;
self.buttonNumber=1;
break;
}
imageButton.frame = CGRectMake(self.xCord, 35.0f, 163.25f, 163.25f);
imageButton.tag = self.photoIndex;
imageButton.enabled=FALSE;
imageButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
if(self.buttonsArray.count < self.photosArray.count)[self.buttonsArray addObject:imageButton];
if([self.isInternetAvailableClass isInternetAvailable]==YES)[self downloadImages:self.photoIndex];
cell.selectionStyle=UITableViewCellSelectionStyleNone;
[cell addSubview:imageButton];
if(self.photoIndex < self.buttonsArray.count){
if(![[self.buttonsArray objectAtIndex:self.photoIndex] currentImage]){
loadingActivityIndicator.center = imageButton.center;
loadingActivityIndicator.hidesWhenStopped=TRUE;
}
}
if(self.activityIndicatorArray.count < self.photosArray.count){
[self.activityIndicatorArray addObject:loadingActivityIndicator];
if([self.isInternetAvailableClass isInternetAvailable]==YES){
[loadingActivityIndicator startAnimating];
}
}else{
if(self.photoIndex < self.buttonsArray.count){
[self.activityIndicatorArray replaceObjectAtIndex:self.photoIndex withObject:loadingActivityIndicator];
}
}
[cell addSubview:loadingActivityIndicator];
self.photoIndex++;
}
}
return cell;
}else{
NSLog(@"landscape called!");
for (int i=0; i < self.numberOfColumns; i++) {
if (self.photoIndex < [self.photosArray count] || self.shouldReload==TRUE ){
NSLog(@"inside landscape called!");
UIButton *imageButton = [[UIButton alloc]init];
UIActivityIndicatorView *loadingActivityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[imageButton addTarget:self action:@selector(imageClicked:) forControlEvents:UIControlEventTouchDown];
switch (self.buttonNumber) {
case 1:
self.xCord=37;
self.buttonNumber++;
break;
case 2:
self.xCord=230;
self.buttonNumber++;
break;
case 3:
self.xCord=423;
self.buttonNumber++;
break;
case 4:
self.xCord=616;
self.buttonNumber++;
break;
case 5:
self.xCord=809;
self.buttonNumber=1;
break;
}
imageButton.frame = CGRectMake(self.xCord, 35.0f, 163.25f, 163.25f);
imageButton.tag = self.photoIndex;
imageButton.enabled=FALSE;
imageButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
if(self.buttonsArray.count < self.photosArray.count){
[self.buttonsArray addObject:imageButton];
}
if([self.isInternetAvailableClass isInternetAvailable]==YES){
[self downloadImages:self.photoIndex];
}
cell.selectionStyle=UITableViewCellSelectionStyleNone;
[cell addSubview:imageButton];
if(self.photoIndex < self.buttonsArray.count){
if(![[self.buttonsArray objectAtIndex:self.photoIndex] currentImage]){
loadingActivityIndicator.center = imageButton.center;
loadingActivityIndicator.hidesWhenStopped=TRUE;
}
}
if(self.activityIndicatorArray.count < self.photosArray.count){
[self.activityIndicatorArray addObject:loadingActivityIndicator];
if([self.isInternetAvailableClass isInternetAvailable]==YES){
[loadingActivityIndicator startAnimating];
}
}else{
if(self.photoIndex < self.activityIndicatorArray.count){
[self.activityIndicatorArray replaceObjectAtIndex:self.photoIndex withObject:loadingActivityIndicator];
}
}
[cell addSubview:loadingActivityIndicator];
self.photoIndex++;
}
}
return cell;
}
if (self.shouldReload==TRUE)self.shouldReload=FALSE;
}
#pragma mark - Table view delegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 205.0f;
}
#pragma mark - View Lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
self.photosTableView = [[UITableView alloc]init];
self.photosTableView.delegate=self;
self.photosTableView.dataSource=self;
self.photosTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.photosTableView.backgroundColor = [UIColor clearColor];
[self.view addSubview:self.photosTableView];
self.isInternetAvailableClass = [[IsInternetAvailable alloc]init];
self.buttonNumber=1;
self.xCord=0.0f;
self.downloadedImageNumber=0;
self.buttonsArray = [[NSMutableArray alloc]init];
self.dataDictionary = [[NSMutableDictionary alloc]init];
self.downloadedImagesArray = [[NSMutableArray alloc]init];
self.activityIndicatorArray = [[NSMutableArray alloc]init];
self.photosArray = [[NSMutableArray alloc]init];
self.largePhotosArray = [[NSMutableArray alloc]init];
self.imageOrderDictionary = [[NSMutableDictionary alloc]init];
self.refToDownloadedImage = [[UIImage alloc]init];
self.unformattedPhotosArray = [[NSMutableArray alloc]init];
self.appDelegate = (StereophotoAppDelegate *)[[UIApplication sharedApplication]delegate];
self.shouldReload=FALSE;
if(self.interfaceOrientation==UIInterfaceOrientationPortrait || self.interfaceOrientation==UIInterfaceOrientationPortraitUpsideDown){
self.numberOfColumns=4;
}else{
self.numberOfColumns=5;
}
self.photoIndex=0;
self.errorImageArray = [[NSMutableArray alloc]init];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewWillAppear:) name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(deviceDidRotate) name:UIDeviceOrientationDidChangeNotification object:nil];
[self loadImages];
}
-(void)viewWillAppear:(BOOL)animated{
self.appDelegate.isSlideshowRunning=NO;
self.appDelegate.PhotosPopoverSlideshowText = @"Start Slideshow";
if([self.isInternetAvailableClass isInternetAvailable]==YES){
if(self.isCommingFromNoNetworkConnectivity==YES){
[self viewDidLoad];
}
if(self.photosTableView.isHidden==YES)[self.photosTableView setHidden:NO];
self.isCommingFromNoNetworkConnectivity=NO;
}else{
if(self.photosTableView.isHidden==NO){
[self.photosTableView setHidden:YES];
self.isCommingFromNoNetworkConnectivity=YES;
}
UIAlertView *errorAlert = [[UIAlertView alloc]initWithTitle:@"Oops!" message:@"No Internet connection was detected! Please connect to the Internet and try again!" delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil];
[errorAlert show];
}
}
-(void)viewWillDisappear:(BOOL)animated{
for(NSURLConnection *connection in self.connectionsArray){
[connection cancel];
}
}
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation{
return TRUE;
}
-(void)deviceDidRotate{
self.photosTableView.frame=CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height);
if(self.interfaceOrientation==UIInterfaceOrientationPortrait || self.interfaceOrientation==UIInterfaceOrientationPortraitUpsideDown){
self.numberOfColumns=4;
}else{
self.numberOfColumns=5;
}
self.shouldReload=TRUE;
[self.photosTableView reloadData];
}
-(void)viewDidAppear:(BOOL)animated{
self.photosTableView.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height);
}
thanks for ANY help!
Shredder
回答1:
I think a table view is the wrong tool for a gallery. You're always going to go through some silliness to put multiple images in a single table view cell, you presumably aren't going to be using the didSelectRowAtIndexPath
logic, etc. I'd suggest a UIScrollView
instead (see example below).
But, if you were determined to use a table view, then [self.tableView reloadData]
is the right solution. But your cellForRowAtIndexPath
has quite a few problems:
You should only create
UIButtons
if you're creating your cell. You'll have to take care, though, given your landscape v portrait orientation, of handling that properly. Not hard, but be careful.You should decouple the caching of images with the loading of cells. They really are two different things, even though it might feel like they're awfully similar. Regardless, you definitely shouldn't be changing the basics populating of a cell in
cellForRowAtIndexPath
based upon whether the image was cached or not; it just changes where you get the image.Unrelated to the problem at hand, but I would't hard code screen coordinates. I wouldn't be surprised to see new iOS devices with different resolutions over the coming months/years. Personally, I calculate how many images I can fit on the screen based upon how many thumbnails I can fit
self.view.frame.width
(you might usecell.contentView.frame.width
).Also probably unrelated to the problem here, I would never recommend calling
viewDidLoad
fromviewWillAppear
. If you want to have some common initialization routine that they both use, then maybe you can do that (but even that is a little sloppy), but given thatviewDidLoad
is calling[super viewDidLoad]
, you'll end up calling thatsuper
method twice, and you don't know if iOS is cool with that. Probably not disasterous, but I can't imagine that it's a good idea.Finally, also unrelated to your problem, but you don't need to use the notification center for
UIDeviceOrientationDidChangeNotification
because the standardwillAnimateRotationToInterfaceOrientation
orviewWillLayoutSubviews
will do that for us.
If you wanted to do a UIScrollView
version of a gallery with lazy loading of images from a remote server, leveraging a cache for the thumbnails, detecting a tap on the images, etc. it might look like the following. I draw your attention to an absolute absence of any portrait/landscape logic (because it just looks at the dimensions of the scrollview), but it handles orientation changes perfectly well. I have not included, though, my code for populating NSArray *imageUrlStrings
because that's obviously completely unique to a particular app, but you probably get the idea nonetheless. And there are all sorts of optimizations you could do (e.g. use Reachability
), but this is a shell of a gallery that might do the job for you.
// GalleryViewController.m
#import "GalleryViewController.h"
#import "ThumbnailCache.h"
#define IMAGE_WIDTH 76.0
#define IMAGE_HEIGHT 76.0
@interface MyImage : NSObject
@property (nonatomic, strong) NSString *urlString;
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIActivityIndicatorView *activityIndicator;
@property (nonatomic, strong) UIView *view;
@property BOOL loading;
@property BOOL loaded;
@end
@implementation MyImage
// I find that I generally can get away with loading images in main queue using Documents
// cache, too, but if your images are not optimized (e.g. are large), or if you're supporting
// older, slower devices, you might not want to use the Documents cache in the main queue if
// you want a smooth UI. If this is the case, change kUseDocumentsCacheInMainQueue to NO and
// then use the Documents cache only in the background thread.
#define kUseDocumentsCacheInMainQueue YES
- (id)init
{
self = [super init];
if (self)
{
_view = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, IMAGE_WIDTH, IMAGE_HEIGHT)];
_imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, IMAGE_WIDTH, IMAGE_HEIGHT)];
_imageView.contentMode = UIViewContentModeScaleAspectFill;
_imageView.clipsToBounds = YES;
[_view addSubview:_imageView];
_loading = NO;
_loaded = NO;
}
return self;
}
- (void)loadImage:(dispatch_queue_t)queue
{
if (self.loading)
return;
self.loading = YES;
ThumbnailCache *cache = [ThumbnailCache sharedManager];
if (self.imageView.image == nil)
{
UIImage *imageFromCache = [cache objectForKey:self.urlString useDocumentsCache:kUseDocumentsCacheInMainQueue];
if (imageFromCache)
{
if (self.activityIndicator)
{
[self.activityIndicator stopAnimating];
self.activityIndicator = nil;
}
self.imageView.image = imageFromCache;
self.loading = NO;
self.loaded = YES;
return;
}
if (self.activityIndicator == nil)
{
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
self.activityIndicator.center = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0);
[self.view addSubview:self.activityIndicator];
}
[self.activityIndicator startAnimating];
dispatch_async(queue, ^{
if (self.loading)
{
UIImage *image = nil;
// only requery cache for Documents cache if we didn't do so in the main queue
if (!kUseDocumentsCacheInMainQueue)
image = [cache objectForKey:self.urlString useDocumentsCache:YES];
// if we haven't gotten the image yet, retrieve it from the remote server
if (!image)
{
NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:self.urlString]];
if (data)
{
image = [UIImage imageWithData:data];
[cache setObject:image forKey:self.urlString data:data];
}
}
// now update the UI in the main queue
dispatch_async(dispatch_get_main_queue(), ^{
if (self.loading)
{
[self.activityIndicator stopAnimating];
self.activityIndicator = nil;
self.imageView.image = image;
self.loading = NO;
self.loaded = YES;
}
});
}
});
}
}
- (void)unloadImage
{
NSLog(@"%s %@", __FUNCTION__, self.urlString);
// remove from imageview, but not cache
self.imageView.image = nil;
self.loaded = NO;
self.loading = NO;
}
@end
@interface GalleryViewController ()
{
NSMutableArray *_myImages;
dispatch_queue_t _queue;
}
@end
@implementation GalleryViewController
- (void)dealloc
{
if (_queue)
dispatch_release(_queue);
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidBecomeActiveNotification
object:nil];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// this is not strictly necessary because `NSCache` will automatically
// evict objects in low memory situations; it will not, though
// remove items when you try to simulate a low memory situation
// in the simulator, but it really will empty the cache when
// memory really runs low
//
// ThumbnailCache *cache = [ThumbnailCache sharedManager];
// [cache removeAllObjects];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.scrollView.delegate = self;
_queue = dispatch_queue_create("com.robertmryan.imageloader", NULL);
[self loadImages];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapHandler:)];
[self.scrollView addGestureRecognizer:tap];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil];
}
- (void)viewWillLayoutSubviews
{
NSUInteger imagesPerRow = (self.view.frame.size.width / IMAGE_WIDTH);
CGFloat imageMargin = (self.view.frame.size.width - (IMAGE_WIDTH * imagesPerRow)) / (imagesPerRow + 1.0);
NSUInteger row = 0;
NSUInteger col = -1;
for (NSUInteger i = 0; i < [_myImages count]; i++)
{
col++;
if (col >= imagesPerRow)
{
col = 0;
row++;
}
MyImage *myImage = [_myImages objectAtIndex:i];
CGRect frame = myImage.view.frame;
frame.origin.x = imageMargin + (imageMargin + IMAGE_WIDTH) * col;
frame.origin.y = imageMargin + (imageMargin + IMAGE_HEIGHT) * row;
myImage.view.frame = frame;
}
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width, (row + 1) * (IMAGE_HEIGHT + imageMargin) + imageMargin);
[self displayVisibleImages:NO];
}
- (void)viewDidUnload
{
[self setScrollView:nil];
_myImages = nil;
[super viewDidUnload];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self displayVisibleImages:NO];
}
- (void)appDidBecomeActive
{
[self displayVisibleImages:YES];
}
- (void)displayVisibleImages:(BOOL)forceLoad
{
CGPoint contentOffset = self.scrollView.contentOffset;
CGRect contentFrame = self.scrollView.bounds;
contentFrame.origin = contentOffset;
for (MyImage *myImage in _myImages)
{
if (CGRectIntersectsRect(contentFrame, myImage.view.frame))
{
// if the image is visible, then make sure it's loaded
if (!myImage.loaded || forceLoad)
[myImage loadImage:_queue];
}
else
{
// if not, go ahead and unload it to conserve memory
if (myImage.loaded || myImage.loading)
[myImage unloadImage];
}
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self displayVisibleImages:NO];
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
CGFloat version = [[[UIDevice currentDevice] systemVersion] floatValue];
if (version < 5.0)
[self viewWillLayoutSubviews];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return TRUE;
}
- (void)tapHandler:(UITapGestureRecognizer *)sender
{
CGPoint location = [sender locationInView:self.scrollView];
for (MyImage *myImage in _myImages)
{
if (CGRectContainsPoint(myImage.view.frame, location))
NSLog(@"Tapped in image %1d", myImage.view.tag);
}
}
- (void)loadImages
{
NSArray *imageUrlStrings = [self loadImageUrls];
_myImages = [[NSMutableArray alloc] init];
NSUInteger imagesPerRow = (self.scrollView.frame.size.width / IMAGE_WIDTH);
CGFloat imageMargin = (self.scrollView.frame.size.width - (IMAGE_WIDTH * imagesPerRow)) / (imagesPerRow + 1.0);
NSInteger row = 0;
NSInteger col = -1;
for (NSUInteger i = 0; i < [imageUrlStrings count]; i++)
{
col++;
if (col >= imagesPerRow)
{
col = 0;
row++;
}
MyImage *myImage = [[MyImage alloc] init];
myImage.urlString = [imageUrlStrings objectAtIndex:i];
CGRect frame = myImage.view.frame;
frame.origin.x = imageMargin + (imageMargin + IMAGE_WIDTH) * col;
frame.origin.y = imageMargin + (imageMargin + IMAGE_HEIGHT) * row;
myImage.view.frame = frame;
[self.scrollView addSubview:myImage.view];
myImage.view.tag = i;
[_myImages addObject:myImage];
}
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width, (row + 1) * (IMAGE_HEIGHT + imageMargin) + imageMargin);
}
@end
My ThumbnailCache
is just a simple NSCache
singleton object. (Whether you implement this as a singleton, is up to you; you don't have to but it's convenient given the nature of my apps.) I've updated this to encapsulate a secondary Documents
cache (thus you're retrieving images from a remote server, cached in both the NSCache
object for optimal performance, and secondarily cached in the Documents
folder if you want to save subsequent invocations of the program from needing to re-retrieve the images from the server, giving you a persistent cache across sessions). This theoretically could (should?) be optimized to purge the Documents
folder of images that haven't been used for a long time, but I'll leave that for others to implement.
//
// ThumbnailCache.h
//
// Created by Robert Ryan on 7/17/12.
// Modified 8/9/12 to support secondary Documents-based cache.
//
#import <UIKit/UIKit.h>
@interface ThumbnailCache : NSCache
+ (id)sharedManager;
- (id)objectForKey:(id)key useDocumentsCache:(BOOL)useDocumentsCache;
- (void)setObject:(UIImage *)image forKey:(id)key data:(NSData *)data;
@end
and
//
// ThumbnailCache.m
//
// Created by Robert Ryan on 7/17/12.
// Modified 8/9/12 to support secondary Documents-based cache.
//
#import "ThumbnailCache.h"
@implementation ThumbnailCache
- (id)init
{
self = [super init];
if (self)
{
self.name = @"Thumbnail cache";
}
return self;
}
+ (id)sharedManager
{
static ThumbnailCache *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
- (id)objectForKey:(id)key
{
return [self objectForKey:key useDocumentsCache:YES];
}
- (id)objectForKey:(id)key useDocumentsCache:(BOOL)useDocumentsCache
{
// This is a variation of the standard objectForKey method which takes an additional
// parameter, useDocumentsCache, which will tell this to also check the Documents
// cache or not. This is a subtle refinement in case you don't want your main queue
// to use the Documents cache. Generally, if you're dealing with optimized images,
// this is not necessary, but if you're dealing with large images, this permutation
// can be useful.
// first, just try to get the image from the NSCache
id result = [super objectForKey:key];
// if successful (or if we don't want to look in the Documents cache), then just return.
if (result || !useDocumentsCache)
return result;
// otherwise, if we didn't find it in the NSCache and we want to look in our Documents
// cache, then let's do so
NSString *path = [self pathInDocumentCache:key];
if (!result && path)
{
// get the data from the remote server
NSData *data = [NSData dataWithContentsOfFile:path];
if (data)
{
// and if we found it, save it in our NSCache
UIImage *image = [UIImage imageWithData:data];
if (image)
[self setObject:image forKey:key];
return image;
}
}
return result;
}
- (void)setObject:(UIImage *)image forKey:(id)key data:(NSData *)data
{
// This is a variation of the standard setObject:forKey which takes an additional NSData.
// The notion is that our cache stores UIImage objects (to save the theoretical overhead,
// if any, of loading image data into a NSData and then into a UIImage ... I wouldn't be
// surprised if Apple did some cool optimization of UIImage objects, though I don't know),
// so if we want to write our image file, but don't want to convert the UIImage back to
// a NSData, then let's just take the original NSData as a parameter.
// save the object in the NSCache
[super setObject:image forKey:key];
// now let's also save the NSData in our Documents-based cache
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *path = [self pathInDocumentCache:key];
if (path && ![fileManager fileExistsAtPath:path])
{
// let's create the folder if we need to
NSString *folder = [path stringByDeletingLastPathComponent];
if (![fileManager fileExistsAtPath:folder])
[fileManager createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:nil error:nil];
// and let's write the NSData to that folder
[data writeToFile:path atomically:YES];
}
}
- (NSString *)pathInDocumentCache:(NSString *)key
{
if (![key isKindOfClass:[NSString class]])
return nil;
NSString *docsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *imagePath = [docsPath stringByAppendingPathComponent:@"image_cache"];
NSURL *url = [NSURL URLWithString:key];
return [imagePath stringByAppendingPathComponent:[url relativePath]];
}
@end
I'd be surprised if there weren't some great publicly available gallery classes, too, but if you wanted to roll your own, the above is a possibility.
来源:https://stackoverflow.com/questions/11697240/existing-uitableview-photo-grid-not-rotating-correctly