...

вторник, 12 ноября 2013 г.

Developing Titanium module for iOS

В этой статье я опишу создание нативного iOS модуля для Titanium. Целью статьи является показать базовые вещи при создании Titanium модуля, чтобы при необходимости вы не боялись расширить/ускорить базовый функционал Titanium.SDK. Основной задачей модуля будет сохранение pdf файла с наложением картинок поверх страниц.

Кому просто надо такой модуль — исходный код.


Titanium позволяет создавать мобильные приложение на JavaScript. Но в отличие от phonegap он не просто оборачивает html5 приложение в WebView, а запускает nodeJS сервер, на котором все это крутится. Все это хорошо, но с pdf JavaScript пока плохо дружит. Для рендеринга есть pdf.js в котором как раз на мобильном сафари не все гладко, для генерации есть jspdf где помимо плохой документации проблемы с памятью. Дело в том, что большой файл генерируется в ОЗУ и потом только сохраняется, но зачастую ОС убивает приложение до того как файл успеет сохранится.

Итак, под катом относительно простой способ ускорить приложение.



Предполагается использование Titanium SDK не ниже 3й версии. После установки titanium на ваш MacOS в консоле будет доступна команда «titanium», это не то, что нам нужно. А нужно нам «titanium.py» из "~/Library/Application\ Support/Titanium/mobilesdk/osx/[SDK Version]/". Додайте алиасы, если не сделали этого ранее.



$ cat ~/.bash_profile
alias ios_builder="/Users/peinguin/Library/Application\ Support/Titanium/mobilesdk/osx/3.1.3.GA/iphone/builder.py"
alias titanium.py="/Users/peinguin/Library/Application\ Support/Titanium/mobilesdk/osx/3.1.3.GA/titanium.py"




После этого можно создать скелет модуля.

$ titanium.py create --platform=iphone --type=module --dir=/Volumes/yanpix_projects --name=pdfsaver --id=ti.pdfsaver




Собрать модуль можно командой:

$ ./build.py




Но пока собирать нечего. Для начала лучше всего очертить как вы будете использовать модуль. В моем случае у меня был исходный pdf документ и canvas для каждой его страницы с пометками. Еще в некоторых случаях мне нужен был thumbnail первой страницы pdf документа.

После того, как вы определитесь с требуемым функционалом, опишите его в «example/app.js».



// TODO: write your module tests here
var pdfsaver = require('ti.pdfsaver');
Ti.API.info("module is => " + pdfsaver);

var old = Titanium.Filesystem.getFile(Titanium.Filesystem.getTempDirectory(),'test.pdf');
var newpdf = Titanium.Filesystem.getFile(Titanium.Filesystem.getTempDirectory(),'export.pdf');

pdfsaver.saveInExportFileWithDrawings(
old.resolve(),
newpdf.resolve(),
{
1: 'data:image/png;base64,[base64 image representation],
4: 'data:image/png;base64,[base64 image representation]'
},
1
);

var jpeg = Titanium.Filesystem.getFile(Titanium.Filesystem.getTempDirectory(),'export.jpeg');

pdfsaver.saveThumbnail(
newpdf.resolve(),
jpeg.resolve()
);


Если в приложении вы можете получить «Resource directory», то в даном случае лучше просто бросить файлы в «Temp directory» приложения (~/Library/Application Support/iPhone Simulator/5.1/Applications/[app ID]/tmp).


Если верить документации, то «TiPdfsaverModuleAssets.h» и «TiPdfsaverModuleAssets.m» изменять нет смысла — всеравно перетираются. Свой код следует писать в «TiPdfsaverModule.m» и, соответственно «TiPdfsaverModule.h». Вот код моих функций:



#pragma Public APIs

-(void)saveThumbnail:(id)args{
NSString *pdf = [args objectAtIndex:0];
NSString *jpeg = [args objectAtIndex:1];

CFURLRef url = CFURLCreateWithFileSystemPath (NULL, (CFStringRef)pdf, kCFURLPOSIXPathStyle, 0);
CGPDFDocumentRef templateDocument = CGPDFDocumentCreateWithURL(url);
CFRelease(url);

CGPDFPageRef templatePage = CGPDFDocumentGetPage(templateDocument, 1); // get the first page
CGRect templatePageBounds = CGPDFPageGetBoxRect(templatePage, kCGPDFCropBox);
UIGraphicsBeginImageContext(templatePageBounds.size);

CGContextRef contextRef = UIGraphicsGetCurrentContext();

CGContextTranslateCTM(contextRef, 0.0, templatePageBounds.size.height);
CGContextScaleCTM(contextRef, 1.0, -1.0);

CGContextDrawPDFPage(contextRef, templatePage);

UIImage *imageToReturn = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGPDFDocumentRelease(templateDocument);

[UIImageJPEGRepresentation(imageToReturn, 1.0) writeToFile:jpeg atomically:YES];
}

-(void)saveInExportFileWithDrawings:(id)args{

NSString *fresh = [args objectAtIndex:0];
NSString *exportpath = [args objectAtIndex:1];
NSDictionary *drawings = [args objectAtIndex:2];
NSNumber *all = [args objectAtIndex:3];
CFURLRef url = CFURLCreateWithFileSystemPath (NULL, (CFStringRef)fresh, kCFURLPOSIXPathStyle, 0);
CGPDFDocumentRef templateDocument = CGPDFDocumentCreateWithURL(url);
CFRelease(url);
size_t count = CGPDFDocumentGetNumberOfPages(templateDocument);

UIGraphicsBeginPDFContextToFile(exportpath, CGRectMake(0, 0, 612, 792), nil);
for (int pageNumber = 1; pageNumber <= count; pageNumber++) {
id image = [drawings objectForKey:[NSString stringWithFormat:@"%d",pageNumber ]];
if(image == nil && [all boolValue] == NO){
continue;
}

CGPDFPageRef templatePage = CGPDFDocumentGetPage(templateDocument, pageNumber);
CGRect templatePageBounds = CGPDFPageGetBoxRect(templatePage, kCGPDFCropBox);
UIGraphicsBeginPDFPageWithInfo(templatePageBounds, nil);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0.0, templatePageBounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);

CGContextDrawPDFPage(context, templatePage);

CGContextTranslateCTM(context, 0.0, templatePageBounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);

if(image != nil){
NSURL *url = [NSURL URLWithString:image];
NSData *imageData = [NSData dataWithContentsOfURL:url];
UIImage *ret = [UIImage imageWithData:imageData];

[ret drawInRect:CGRectMake(0, 0, templatePageBounds.size.width, templatePageBounds.size.height)];
}
}
CGPDFDocumentRelease(templateDocument);
UIGraphicsEndPDFContext();
}




Можно передавать сколько угодно аргументов в функции и получать значения через «objectAtIndex». Или проверять на наличие индекса. Все параметры передаются как объекты по ссылке.

Теперь чтобы проверить правильную работу модуля нужно выполнить:



$ titanium.py run




Когда вы убедитесь, что все работает правильно. Соберите через «build.py» и разархивируйте zip файл, что находится в корне модуля, в корень вашего проекта. А также додайте тег «module» в тег «modules» в файле «tiapp.xml» проекта.

<modules>
<module platform="iphone">ti.pdfsaver</module>
</modules>




В статье описаны только базовые вещи. Ни слова не сказано о View и Proxy. Но этого хватит чтобы не впадать в депресию, когда заказчик просит что-то, что будет сильно тормозить на JS, но переписывать приложение с нуля уже позно. На JS можно быстро разрабатывать само приложение, ловить exception`ы, но чтобы само приложение быстро работало стоит все-таки использовать нативный код. Я думаю со временем весь код моего приложения перепишется на objective-c что даст, в итоге, возможность полностью отказатся от Titanium.

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Комментариев нет:

Отправить комментарий