Monday, December 23, 2013
Thursday, December 12, 2013
Monday, December 2, 2013
SDWebImage Retry Failed Image Download https://github.com/rs/SDWebImage/issues/465
SDWebImage Retry Failed Image Download
What is the best way to retry a failed image download? I have a collection view that can show about 20 80x80 thumbnail images. I put the SDWebImage code in the cellForItemAtIndexPath method, but I've found that every so often an image fails to download. I know I can use a completion block and test if the image is nil, but it doesn't seem to be reloading the image correctly (the image still never shows up). Is this how I should be handling retrying the image download? Here's an example of my code.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { NSURL *thumbnailURL = [NSURL URLWithString:url]; INCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath]; [cell.imageView setImageWithURL:thumbnailURL completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) { if (!image) { [self.collectionView reloadItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]]; } }]; return cell; }
You need to use one of the methods with an "options" parameter and specify SDWebImageRetryFailed like so...
[cell.imageView setImageWithURL:thumbnailURL
placeholderImage:[UIImage imageNamed:@"placeholder.png"]
options:SDWebImageRetryFailed
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {...}];
-M.
Great, thank you
Saturday, November 30, 2013
Сетевое кэширование в iOS. Введение
Сетевое кэширование в iOS. Введение
К сожалению, доступ к сети не всегда возможен и поэтому разработчику важно правильно реализовать сетевой кэш в приложении.
В связи с этим, я решил написать серию статей о том, какие существуют способы имплементации кэша и когда их применять.
Итак, введение.
Стратегии для кэширования
Есть два подхода к кэшированию: кэширование по требованию и предварительное кэширование.
Кэширование по требованию позволяет в offline режиме просматривать контент, который был просмотрен ранее. Данные, полученные от сервера, хранятся на устройстве и для каждого запроса происходит проверка их актуальности. Если данные актуальны, то они берутся с диска, если нет то идет запрос на сервер.
Предварительное кэширование подразумевает, что все данные, которые могут потребоваться пользователю, будут получены и сохранены на диск сразу же.
Чтобы определить какую стратегию кэширования использовать, нужно понять, может ли потребоваться пост обработка данных после их загрузки. Пост обработка подразумевает какую-либо модификацию загруженных данных. Например, изменение ссылок в HTML странице так, чтобы они указывали на картинки кэшированные локально и т.д.
Где хранить кэш
Приложения могут хранить информацию только в своей песочнице. Так как кэшируемые данные не генерируются пользователем, то они должны быть сохранены в NSCachesDirectory, а не в NSDocumentsDirectory. Хорошей практикой является создание отдельной директории, для всех кэшируемых данных.
В этом примере в папке Library/Caches создается директория MyAppCache:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSString *cachesDirectory = [paths objectAtIndex:0]; cachesDirectory = [cachesDirectory stringByAppendingPathComponent:@"MyAppCache"];
Причиной хранения кэша в папке Library/Caches является то, что iCloud (и iTunes) исключает эту директорию из бэкапа. И, следовательно, и так ограниченное в iCloud пространство (в настоящее время для бесплатного аккаунта, это около 5 GB) не тратится на хранение ненужных данных.
В случае если в приложении происходит интенсивное кэширование, рекомендуется вместо диска использовать память и выгружать данные на диск при закрытии приложения. Это связано с тем, что flash память iPhone имеет ограниченное число циклов записи/чтения и нежелательно нагружать ее лишний раз.
Как хранить кэш
На iOS существует множество различных способов хранения пользовательских данных. Для кэширования лучше всего подходят: NSKeyedArchiver, Core Data, SQLite, NSURLCache.
NSKeyedArchiver
Кэширование модели данных реализуется с использованием класса NSKeyedArchiver. Для того, чтобы объекты модели могли быть архивированы, классы модели должны реализовать протокол NSCoding. А именно методы
- (void)encodeWithCoder:(NSCoder *)aCoder; - (id)initWithCoder:(NSCoder *)aDecoder;
Если класс реализует NSCoding, для архивации достаточно вызвать один из следующих методов:
[NSKeyedArchiver archiveRootObject:objectForArchiving toFile:archiveFilePath];
[NSKeyedArchiver archivedDataWithRootObject:objectForArchiving];
Первый метод создаст файл с архивом по пути archiveFilePath. Второй метод вернет объект NSData. NSData обычно быстрее, так как отсутствуют дополнительные затраты на доступ к файлу, но при этом данные будут храниться в памяти приложения.
Для разархивирования модели из файла (или указателя на NSData) используется класс NSKeyedUnarchiver. Разархивировать данные можно одним из следующих методов:
[NSKeyedUnarchiver unarchiveObjectWithData:data];
[NSKeyedUnarchiver unarchiveObjectWithFile:archiveFilePath];
Использование NSKeyedArchiver/NSKeyedUnarchiver требует чтобы модели удовлетворяли протоколу NSCoding. Реализация NSCoding очень проста, но если файлов много, то она может занять много времени. Поэтому для автоматизации этого процесса лучше использовать какой-либо инструмент. Например среду разработки AppCode.
Core Data
Чтобы хранить данные в Core Data, необходимо создать файл модели, который содержит описание сущностей (Entities), а также связей между ними (Relationships), и написать методы для сохранения и получения данных. Используя Core Data можно получить настоящий offline режим работы приложения, как это сделано в стандартных приложениях Mail и Calendar.
При реализации предварительного кэширования нужно периодически удалять данные, которые не нужны. Иначе размер кэша начнет заметно расти, что вызовет потерю производительности. Синхронизация локальных изменений выполняется путем отслеживания набора изменений и отправкой их обратно на сервер. Существует много алгоритмов для отслеживания наборов изменений, но лучше использовать один из тех, что работает в Git.
Несмотря на то, что Core Data можно использовать для кэширования по требованию, лучше этого не делать. Главное преимущество Core Data заключается в предоставление доступа к свойствам модели без необходимости разархивировать все данные. Однако сложность реализации Core Data в приложении, перекрывает это преимущество.
Raw SQLite
Для работы с SQLite надо слинковать приложение с библиотекой libsqlite3, но такой подход имеет значительные недостатки.
Все sqlite3 библиотеки и механизм Object Relational Mapping (ORM) работают медленнее чем Core Data. Кроме того реализация sqlite3 в iOS не потоко-безопасна. Так что если вы не используете отдельно собранную sqlite3 библиотеку (скомпилированную с флагом thread-safe), то вы сами отвечаете за то, чтобы гарантировать потоко-безопасный доступ на чтение/запись к базе данных sqlite3.
Так как Core Data может предложить гораздо больше возможностей (миграция данных, встроенная потоко-безопасность, ...) рекомендуется избегать использование нативной SQLite на iOS.
NSURLCache
Идеальный вариант для кэширования по запросу. Позволяет кэшировать данные возвращаемые NSURLRequest практически в автоматическом режиме и требует минимум кода.
Всего лишь пара строк и ваше приложение получит дисковый кэш для запросов.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024 diskCapacity:20 * 1024 * 1024 diskPath:nil]; [NSURLCache setSharedURLCache:URLCache]; }
К сожаление пригоден только для REST сервисов и есть проблемы при работе с некоторыми HTTP заголовками.
Заключение
Для реализации кэширования по запросу лучше использовать NSURLCache или NSKeyedArchiver. Реализация полноценного offline режима требует работы c CoreData.
В следующей части я планирую детально рассмотреть работу с NSURLCache/NSKeyedArchiver и описать случаи, для которых они подходят.
Thursday, November 28, 2013
http://stackoverflow.com/questions/14902835/sdwebimage-download-image-and-store-to-cache-for-key
Hello I am using the SDWebImage framework in a project and I was wanting to download and cache some images, but I think my code is storing the image in the cache twice? Is there a way to store a UIImage in the cache by a key one time? Here is my code. Possibly there is just something I missed? Looks like doing this in my allocations instrument is pilling up a lot of memory. | ||
add comment |
I'm surprised nobody answered this question, but I've had a similar question and came across this, so I'll answer it for people viewing this going forward (assuming you've sorted this out yourself by now). To directly answer your question, yes, you are caching the image twice. Download calls to SDWebImageManager automatically cache images with keys based on the absoluteString of the image's url. If you want your own key, you can use the download call on SDWebImageDownloader which as far as I can tell does NOT cache by default. From there you can call the sharedImageCache as you're already doing and cache with whatever key you want. That aside, it is strange you're seeing allocations piling up in any case as SDWebImage likes to cache to disk and not memory generally. Maybe something else is going on at the same time? Hope this helps, -Brandon | |||
Работа с изображениями IOS http://forum.i-ekb.ru/index.php?showtopic=1716
Работа с изображениями
Как скачать, создать и показать изображение через URL
URL на удаленное изображение
NSURL *url = [NSURL URLWithString: @"http://darknessproduction.ru/ifullstat.png"];Создание UIImage из NSData
UIImage *image = [UIImage imageWithData: [NSData dataWithContentsOfURL:url]];
Поместим на наш UIView
NSURL *url = [NSURL URLWithString:
@"http://darknessproduction.ru/ifullstat.png"];
UIImage *image = [UIImage imageWithData: [NSData dataWithContentsOfURL:url]];
[self.view addSubview:[[[UIImageView alloc] initWithImage:image] autorelease]];
Закругляем изображения
Добавляем
#include <QuartzCore/QuartzCore.h>в viewDidLoad нашего контроллера прописываем:
CALayer * ourLayer = [imageView layer]; // Будем округлять UIImageViewНу и собственно всё. Очевидно, что если хотим получить овальный View, указываем cornerRadius равный половине длины короткой стороны нашего View.
ourLayer.cornerRadius = 8.0f; // Задаем радиус для округления.
ourLayer.masksToBounds = YES; // Чтобы за овальной границей в углах ничего не рисовалось
ourLayer.borderWidth = 0.0f; // Границу рисовать не будем. Если нужна - указываем толщину
Как сделать анимацию из картинок
GIF грузить не получится стандартными средствами SDK, так что если речь идет не о анимации в OpenGL, то делайте так:
1. создаете UIImageView
2. грузите серию изображений вашей анимации как массив UIImage объектов
3. проставляете массив изображений для свойства animationImages в UIImageView
4. устанавливаете продолжительность анимации в свойстве animationDuration
5. startAnimating для UIImageView
NSArrray *theImages = [NSArray arrayWithObjects:
[UIImage imageNamed:@"image_1.png"],
[UIImage imageNamed:@"image_2.png"],
[UIImage imageNamed:@"image_3.png"],
[UIImage imageNamed:@"image_4.png"],
[UIImage imageNamed:@"image_5.png"], nil];
UIImageView *anImageView = ... //мембер класса, или создаете по месту, не важно
anImageView.animationImages = theImages;
anImageView.animationDuration = 1.0; // 1 секунда
[anImageView startAnimating];
Как отправить емейл с вложениями, используя камеру и фотоальбом
Запуск камеры iPhone
- (void)buttonPressed:(UIButton *)buttonКонвертируем изображение с камеры в NSData
{
// Создание image picker controller
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
// Устанавливаем сорс для камеры
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
// Текущий Delegate
imagePicker.delegate = self;
// Вырубаем редактирование изображений
imagePicker.allowsImageEditing = NO;
// Показываем image picker
[self presentModalViewController:imagePicker animated:YES];
}
- (void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)infoОтправка изображения с камеры
{
// Берем изображение из информации словаря
UIImage *image = [info objectForKey:@"UIImagePickerControllerOriginalImage"];
// Закрываем окно камеры
[self dismissModalViewControllerAnimated:YES];
// Запускаем функцию отправки изображения по почте
[self performSelector:@selector(emailImage:) withObject:image afterDelay:1.0];
// Релизим picker
[picker release];
}
- (void)emailImage:(UIImage *)image
{
MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];
picker.mailComposeDelegate = self;
// Устанавливаем тему письма
[picker setSubject:@"Picture from my iPhone!"];
// Добавляем адреса почты, куда будем отсылать
[picker setToRecipients:[NSArray arrayWithObjects:@"(Гости не видят ссылок. )", @"(Гости не видят ссылок. )", nil]];
[picker setCcRecipients:[NSArray arrayWithObject:@"(Гости не видят ссылок. )"]];
[picker setBccRecipients:[NSArray arrayWithObject:@"(Гости не видят ссылок. )"]];
// Устанавливаем тело сообщения
NSString *emailBody = @"I just took this picture, check it out.";
// Вырубаем XTML формат
[picker setMessageBody:emailBody isHTML:NO];
// Создаем NSData из PNG изображения с камеры
NSData *data = UIImagePNGRepresentation(image);
// Добавляем изображение к письму как вложение
[picker addAttachmentData:data mimeType:@"image/png" fileName:@"CameraImage"];
// Показываем окно отправки письма
[self presentModalViewController:picker animated:YES];
// Релизим picker
[picker release];
}
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
// Закрываем окно отправки письма
[self dismissModalViewControllerAnimated:YES];
}
Как анимированно вращать изображение
Радианы и градусы
Переводим из градусов в радианы
// This is defined in Math.hВращаем изображение
#define M_PI 3.14159265358979323846264338327950288 /* pi */
#define DEGREES_TO_RADIANS(angle) ((angle / 180.0) * M_PI)
- (void)rotateImage:(UIImageView *)image duration:(NSTimeInterval)durationВызов вращения
curve:(int)curve degrees:(CGFloat)degrees
{
// Устанавливаем анимацию
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:duration];
[UIView setAnimationCurve:curve];
[UIView setAnimationBeginsFromCurrentState:YES];
CGAffineTransform transform =
CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(degrees));
image.transform = transform;
[UIView commitAnimations];
}
- (void)startApp
{
UIImageView *imageToMove =
[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"ArrowUp.png"]];
imageToMove.frame = CGRectMake(10, 10, 20, 100);
[self.view addSubview:imageToMove];
[self rotateImage:imageToMove duration:3.0
curve:UIViewAnimationCurveEaseIn degrees:180];
}
Как анимированно передвинуть изображение
Передвижение изображения
- (void)moveImage:(UIImageView *)image duration:(NSTimeInterval)durationВызов передвижения
curve:(int)curve x:(CGFloat)x y:(CGFloat)y
{
// Устанавливаем анимацию
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:duration];
[UIView setAnimationCurve:curve];
[UIView setAnimationBeginsFromCurrentState:YES];
// The transform matrix
CGAffineTransform transform = CGAffineTransformMakeTranslation(x, y);
image.transform = transform;
[UIView commitAnimations];
}
- (void)startApp
{
UIImageView *imageToMove =
[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Square.png"]];
imageToMove.frame = CGRectMake(10, 10, 20, 100);
[self.view addSubview:imageToMove];
[self moveImage:imageToMove duration:3.0
curve:UIViewAnimationCurveLinear x:50.0 y:50.0];
}
Как сохранить UIImage в PNG или JPG
//Создадим пути для выходных форматов
NSString *pngPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/Test.png"];
NSString *jpgPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/Test.jpg"];
// Запишем UIImage в JPEG с минимальным сжатием (наилучшее качество)
[UIImageJPEGRepresentation(image, 1.0) writeToFile:jpgPath atomically:YES];
// Запишем UIImage в PNG
[UIImagePNGRepresentation(image) writeToFile:pngPath atomically:YES];
// Проверим как хорошо у нас записалось:
// Создаем файл-менеджер
NSError *error;
NSFileManager *fileMgr = [NSFileManager defaultManager];
// Устанавливаем директорию Документов
NSString *documentsDirectory = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
// Проверяем список файлов, который содержит директория Документов
NSLog(@"Директория Документов: %@", [fileMgr contentsOfDirectoryAtPath:documentsDirectory error:&error]);
Как сделать снимок с камеры и сохранить в фотоальбом
Запускаем камеру
// Создаем image picker controllerСохранение изображения в Фотоальбом
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
// Устанавливаем сорс камеры
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
// Текущий Delegate
imagePicker.delegate = self;
// Выключаем редактирование изображения
imagePicker.allowsImageEditing = NO;
// Покажем image picker
[self presentModalViewController:imagePicker animated:YES];
- (void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
UIImage *image = [info objectForKey:@"UIImagePickerControllerOriginalImage"];
// Сохраняем изображение
UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
[picker release];
}
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
UIAlertView *alert;
// Проверяем доступность сохранения изображения
if (error)
alert = [[UIAlertView alloc] initWithTitle:@"Ошибка"
message:@"Невозможно сохранить изображения."
delegate:self cancelButtonTitle:@"Ok"
otherButtonTitles:nil];
else // все хорошо
alert = [[UIAlertView alloc] initWithTitle:@"Успех"
message:@"Изображение сохранено."
delegate:self cancelButtonTitle:@"Ok"
otherButtonTitles:nil];
[alert show];
[alert release];
}
Как изменить размер изображения
UIImage *UImageFromPathScaledToSize(NSString* path, CGSize toSize)
{
UIImage *scaledImg = nil;
UIImage *img = [[UIImage alloc] initWithContentsOfFile:path]; // get the image
if( img )
{
float scale = GetScaleForProportionalResize( img.size, toSize, false, false );
CGImageRef cgImage = CreateCGImageFromUIImageScaled( img, scale );
[img release];
if( cgImage )
{
scaledImg = [UIImage imageWithCGImage:cgImage]; // autoreleased
CGImageRelease( cgImage );
}
}
return scaledImg;
}
Как разбить большое изображение на куски равного размера
Иногда бывает удобно изображение одинакового размера затолкать в одно большое изображение, чтобы не разводить кучу файлов, для разбивки используем код:
@interface UIImage (Utilities)Использование:
- (NSMutableArray*) getTilesWithSize:(CGSize)tileSize;
@end
//------------------------------------------------------------------------
@implementation UIImage (Utilities)
- (NSMutableArray*) getTilesWithSize:(CGSize)tileSize
{
NSUInteger countW = self.size.width / tileSize.width;
NSUInteger countH = self.size.height / tileSize.height;
NSMutableArray* array = [NSMutableArray arrayWithCapacity:(countW * countH)];
CGRect rect = CGRectMake(0, 0, tileSize.width, tileSize.height);
NSUInteger w;
NSUInteger h;
for (h = 0, rect.origin.y = 0; h < countH; h++, rect.origin.y += tileSize.height)
{
for (w = 0, rect.origin.x = 0; w < countW; w++, rect.origin.x += tileSize.width)
{
CGImageRef tileImageRef = CGImageCreateWithImageInRect(self.CGImage, rect);
[array addObject:[UIImage imageWithCGImage:tileImageRef]];
CGImageRelease(tileImageRef);
}
}
return array;
}
@end
UIImage* image;
NSMutableArray* normalImages;
NSMutableArray* highlightedImages;
image = [UIImage imageNamed:@"ImagesSet_Buttons_Normal.png"];
normalImages = [image getTilesWithSize:CGSizeMake(66, 66)];
image = [UIImage imageNamed:@"ImagesSet_Buttons_Highlighted.png"];
normalImages = [image getTilesWithSize:CGSizeMake(66, 66)];
Как обрезать картинку
// Создаем UIImage из существующего PNG-файла
UIImage *image = [UIImage imageNamed:@"prgBinary.jpg"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
// Берем размер текущего изображения
CGSize size = [image size];
// Делаем imageView по размеру изображения и добавляем в наше представление
[imageView setFrame:CGRectMake(0, 0, size.width, size.height)];
[[self view] addSubview:imageView];
[imageView release];
// Создаем прямоугольную область, которую надо обрезать
CGRect rect = CGRectMake(size.width / 4, size.height / 4 ,
(size.width / 2), (size.height / 2));
// Создаем bitmap изображения
CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], rect);
UIImage *img = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
// Создаем и показываем изображение из bitmap
imageView = [[UIImageView alloc] initWithImage:img];
[imageView setFrame:CGRectMake(0, 200, (size.width / 2), (size.height / 2))];
[[self view] addSubview:imageView];
[imageView release];
Как сделать изображение черно-белым
- (UIImage *)convertImageToGrayScale:(UIImage *)image
{
CGRect imageRect = CGRectMake(0, 0, image.size.width, image.size.height);
// Выбираем Черно-белую палитру
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
// Создаем bitmap и контекст
CGContextRef context = CGBitmapContextCreate(nil, image.size.width, image.size.height, 8, 0, colorSpace, kCGImageAlphaNone);
CGContextDrawImage(context, imageRect, [image CGImage]);
CGImageRef imageRef = CGBitmapContextCreateImage(context);
// Создаем UIImage
UIImage *newImage = [UIImage imageWithCGImage:imageRef];
// Релизим палитру, битмап и контекст
CGColorSpaceRelease(colorSpace);
CGContextRelease(context);
CFRelease(imageRef);
// Возвращаем уже черно-белое изображение
return newImage;
}
Как сделать снимок экрана
Настраиваем пользовательский интерфейс
- (id)initВызов UIGetScreenImage()
{
if (self = [super init])
{
self.view = [[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease];
self.view.backgroundColor = [UIColor grayColor];
// Кнопка активации камеры
button = [[UIButton alloc] initWithFrame:CGRectMake(80, 55, 162, 53)];
[button setBackgroundImage:[UIImage imageNamed:@"Camera.png"] forState:UIControlStateNormal];
[button addTarget:self action:@selector(buttonPressed:) forControlEvents: UIControlEventTouchUpInside];
[self.view addSubview:button];
[button release];
}
return self;
}
CGImageRef UIGetScreenImage(void);Сохранение скрина экрана в фотоальбом
- (void)buttonPressed:(UIButton *)button
{
// Создаем скрин экрана и добавляем его в UIImage
CGImageRef screen = UIGetScreenImage();
UIImage* image = [UIImage imageWithCGImage:screen];
CGImageRelease(screen);
// Сохраняем UIImage в фотоальбом
UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
}
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
UIAlertView *alert;
// Проверяем возможность сохранения
if (error)
alert = [[UIAlertView alloc] initWithTitle:@"Ошибка"
message:@"Невозможно сохранить."
delegate:self cancelButtonTitle:@"Ok"
otherButtonTitles:nil];
else // Все хорошо
alert = [[UIAlertView alloc] initWithTitle:@"Успех"
message:@"Изображение сохранено."
delegate:self cancelButtonTitle:@"Ok"
otherButtonTitles:nil];
[alert show];
[alert release];
}