Pull to refresh

Простой Image Viewer для iPhone

Reading time6 min
Views4.1K
К сожалению, iPhone SDK не предоставляет функциональность, аналогичную той, что доступна в приложении Photos. Я имею ввиду не Image Picker, а сам Viewer, где можно двигать изображение пальцем и изменять масштаб. Здесь, я постараюсь объяснить, каким образом, можно добиться такой функциональности и куда копать для ее расширения…


Первое, на что падает глаз, это класс UIImageView, который позволяет отрисовать изображение из файла. Однако, в нем отсутствует функциональность связанная с обработкой пользовательского ввода, а самое главное в нем есть ограничение на количество пикселей, которое он может отобразить. В соответствии с документацией это только 1024x1024 пикселя:

«It is a programmer error to create a UIImage object with an image that is greater than 1024 x 1024 pixels in size. Besides the practical considerations of such an image consuming a large amount of memory, the graphics hardware does not support images greater than that size.»


Печально… Однако, мы можем создавать UIImage из CGImage, на котором этого ограничения нет. В свою очередь, CGImage может быть создан из части изображения. Итак, создаем простое Cocoa Touch приложение, которое будет включать класс AppDelegate. Мастер должен сгенерировать необходимый код для создания окна приложения. Нам нужно модифицировать его для отображения UIImageView. Сам UIImageView создается методом:

+ (UIImage *)imageWithContentsOfFile:(NSString *)path


path содержит путь к файлу, который нам надо отрисовать. Я не буду вдаваться в подробности файловой структуры приложений на iPhone, проще обратиться к секции Application Sandbox в iPhone Programming Guide. Для упрощения повествования, предположим, что нужный файл находится в папке Documents. Ниже, приведен способ получения пути к файлу, который мы добавляем в метод AppDidFinishLaunching класса AppDelegate:

//constructing path to the image
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fileName = @«photo.jpg»;
NSString *path = [documentsDirectory stringByAppendingPathComponent:fileName];


Далее, мы создаем UIImageView, c использованием созданого пути и добавляем его как subview к нашему окну, не забывая удалить ссылку на объект:

UIImageView *anImageView = [[windowImageView alloc] initWithImage:path];
[window addSubview:anImageView];
[anImageView release];


Запускаем. Смотрим. Изображение на экране, однако оно не реагирует на пользовательский ввод. Кроме того, как мы знаем из документации, мы не можем отобразить изображение в котором больше чем 1024x1024 пикселя.

Выход, создать свой класс, на основе UIImageView, который будет обрабатывать пользовательский ввод и позволять создавать изображение из CGImage. Добавляем новый класс к проекту. Это два файла MyImageView.h и MyImageView.m. Первый, выглядит следующим образом:

#import <UIKit/UIKit.h>

interface windowImageView: UIImageView {

}

end


Второй:

#import «windowImageView.h»

@implementation windowImageView

— (void)dealloc {
[super dealloc];
}

end


Из названия класса CGImage, который мы собираемся использовать видно, что нам потребуется framework под названием CoreGraphics. В Xcode, в списке файлов выбираем Add Framework… и находим CoreGraphics.framework.

Поехали… Прежде всего, надо создать новый метод, который будет инициализировать MyImageView с ипользованием объекта CGimage. В блоке interface файла MyImageView.h перед end пишем:

— (id)initWithCGImage:(CGImageRef)cg_image


Напомню, что в фигурных скобках мы объявляем instance variables, а после фигурных скобок — методы. Объявив эту функцию, нам осталось ее реализовать. Это делается в блоке @implementation файла MyImageView.m:

— (id)initWithCGImage:(CGImageRef)cg_image{
cg_raw_image = CGImageCreateCopy(cg_image);

//position view in the center of an image
current_x_position = CGImageGetWidth(cg_image)/2 — 320/2;
current_y_position = CGImageGetHeight(cg_image)/2 — 480/2;
//getting image data for the area being displayed
CGRect subImageRect = CGRectMake(current_x_position, current_y_position, 320, 480);
CGImageRef cg_subimage = CGImageCreateWithImageInRect(cg_image, subImageRect);
UIImage *imageToDisplay = [UIImage imageWithCGImage:cg_subimage];
CGImageRelease(cg_subimage);
if(imageToDisplay != NULL)
{
if(self = [super initWithImage:imageToDisplay])
{
//initialization code here
}
}
return self;
}


cg_raw_image, current_x_position, current_y_position это instance variables, котрые мы будем использовать позднее для обработки пользовательского ввода. Суть же этой функции в том, что мы создаем UIImage из CGImage, который в свою очередь, создан как часть исходного изображения и соответсвует размеру экрана телефона или размеру вашего view. Конечно же, использование цифр в этом коде должно быть заменено на вызов функций, которые позволяют получить размеры view в котором изображение будет отображено. Стоит отметить обязательный вызов функции CGImageRelease, который уменьшает счетчик ссылок на объект, что приводит к освобождению памяти, занимаемой этим объектом. Эта функция, должна быть вызвана еще раз для cg_raw_image. Это должно быть сделано в методе dealloc класса MyImageView.

Нам нужно модифицировать AppDelegate для вызова нового метода. ApplicationDidFinishLaunching, теперь будет выглядеть так:

— (void)applicationDidFinishLaunching:(UIApplication *)application {
//no status bar in main window
application.statusBarHidden = YES;
//creating window
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
//constructing path to the image
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fileName = @«photo.jpg»;
NSString *path = [documentsDirectory stringByAppendingPathComponent:fileName];
NSURL *url = [NSURL fileURLWithPath:path];
//creating CG image
CGDataProviderRef provider = CGDataProviderCreateWithURL((CFURLRef)url);
CGImageRef cg_image = CGImageCreateWithJPEGDataProvider (provider, NULL, true, kCGRenderingIntentDefault);
if(cg_image != NULL)
{
MyImageView *anImageView = [[MyImageView alloc] initWithCGImage:cg_image];
anImageView.userInteractionEnabled = TRUE;
[window addSubview:anImageView];
[anImageView release];
}
CGDataProviderRelease (provider);
CGImageRelease (cg_image);
// Override point for customization after app launch
[window makeKeyAndVisible];
}


Здесь, вместо UIImage, c использованием JPEGDataProvider, мы создаем CGImage, который используем для инициализации MyImageView. Сейчас, после запуска программы, мы должны иметь результат, аналогичный тому, что мы имели с использованием UIImage. Однако мы избавились от ограничения на количество пикселей в файле. Стоит обратить внимание на строку:

anImageView.userInteractionEnabled = TRUE;


Это позволяет нашему новому классу реагировать на прикосновения. Вернемся к нашему классу и добавим функции для обработки пользовательского ввода. В iPhone SDK это осуществляется с использованием трех функций:

— (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
— (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
— (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;


В данный момент мы заинтересованы только в touchesMoved, т.к. мы хотим двигать наше изображение пальцем. Добавим функцию touchesMoved в MyImageView:

— (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{

UITouch *touch = [[event allTouches] anyObject];
int x_dist = [touch previousLocationInView:self].x — [touch locationInView:self].x;
int y_dist = [touch previousLocationInView:self].y — [touch locationInView:self].y;
current_x_position = current_x_position + x_dist;
current_y_position = current_y_position + y_dist;
CGRect subImageRect = CGRectMake(current_x_position, current_y_position, 320, 480);

CGImageRef cg_subimage = CGImageCreateWithImageInRect(cg_raw_image, subImageRect);
UIImage *imageToDisplay = [UIImage imageWithCGImage:cg_subimage];
CGImageRelease(cg_subimage);
self.image = imageToDisplay;
}

Здесь, мы получаем объект touch, который содержит информацию о прикосновении, делаемое пользователем. Это позволяет нам отследить движение пользователя и вычислить расстояние в пикселях на которое нам следует переместить изображение. По мере того, как происходит событие, мы создаем новое изображение, как часть основного изображения и отображаем его, изменяя значение image, класса MyImageView. Отрисовка нового изображения, происходит автоматически.

Все, что описано выше, показывает принципы, которые позволят достич функциональности схожей с той, что сущесвует в приложени Photo. Здесь нужно добавить массу вещей, включая проверку выхода за пределы изображения, и увеличение c использованием двух пальцев, и много чего еще…

Кроме того, схожая функциональность может быть реализована с ипользованием UIView, от которого унаследован класс UIImageView. Это увеличит гибкость приложения, но при этим придется реализовывать отрисовку изображения в методе DrawRect.
Tags:
Hubs:
Total votes 18: ↑17 and ↓1+16
Comments11

Articles