Developing Titanium module for iOS

в 15:59, , рубрики: javascript, mobile development, module, objective-c, titanium, метки: , , ,

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.

Автор: peinguin

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js