Ukládání UIView do obrázku jpg, png, pdf

Dnes se podíváme na to, jak lze uložit UIView objekt do obrázku nebo do PDF souboru. Původně jsem si myslel, že to bude obtížný proces, ale po chviličce googlení jsem zjistil, že to je vlastně zatraceně jednoduché. Díky nezávislosti UIView na kontextu, ve kterém se vykresluje, stačí jen vytvořit si PDF kontext nebo Image kontext a nechat do něj existující pohled znovu vykreslit pomocí metody [CALayer renderInContext:].

Další částí skládanky jsou funkce, které z instance třídy UIImage dokáží vytvořit .jpg nebo .png soubor. Jsou to funkce UIImagePNGRepresentation a UIImageJPEGRepresentation.

Nebudu ale zdržovat a rovnou přikládám kategorii na UIView, kterou stačí jen zkopírovat a začít využívat. Díky této kategorii můžete exportovat UIView objekt do .jpg, .png, .pdf nebo jako UIImage. Ať vám dobře slouží!

Hlavičkový soubor: UIView+UIViewExport.h

#import <Foundation/Foundation.h>


@interface UIView (UIViewExport)

/** Returns representation of UIView as UIImage object */
- (UIImage *)imageRepresentation;

/** Exports view as PDf file stored at given path */
- (void)exportInPDFFileAtPath:(NSString*)path;

/** Exports view as PNG file stored at given path */
- (void)exportInPNGFileAtPath:(NSString*)path;

/** Exports view as JPEG file with compression quality stored at given path*/
- (void)exportInJPEGFileAtPath:(NSString*)path withQuality:(CGFloat)compressionQuality;

@end

Implementace: UIView+UIViewExport.m

#import "UIView+IMTUIViewExport.h"
#import <QuartzCore/QuartzCore.h>


@implementation UIView (IMTUIViewExport)

- (UIImage *)imageRepresentation {

    UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, 0.0);
    [self.layer renderInContext:UIGraphicsGetCurrentContext()];

    UIImage* image = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return image;
}

- (void)exportInPDFFileAtPath:(NSString*)path {

    CGRect mediaBox = self.bounds;
    CGContextRef ctx = CGPDFContextCreateWithURL((__bridge CFURLRef)[NSURL fileURLWithPath:path], &mediaBox, NULL);

    CGPDFContextBeginPage(ctx, NULL);
    CGContextScaleCTM(ctx, 1, -1);
    CGContextTranslateCTM(ctx, 0, -mediaBox.size.height);

    [self.layer renderInContext:ctx];

    CGPDFContextEndPage(ctx);
    CFRelease(ctx);
}

- (void)exportInPNGFileAtPath:(NSString*)path {

    NSData *imageData = UIImagePNGRepresentation([self imageRepresentation]);

    [imageData writeToFile:path atomically:YES];
}

- (void)exportInJPEGFileAtPath:(NSString*)path withQuality:(CGFloat)compressionQuality {

NSData *imageData = UIImageJPEGRepresentation([self imageRepresentation], compressionQuality);

    [imageData writeToFile:path atomically:YES];
}

@end

Ukázka kategorie v akci! Pozn.: ukázka bude fungovat pouze v simulátoru. Exportované obrázky se objeví na ploše.

#import "UIView+UIViewExport.h"

// ...

- (void)exportToImage:(id)sender {

    NSString *path = @"/Users/name/Desktop/view.png";
    [self.ohlcChartView exportInPNGFileAtPath:path];

    path = @"/Users/name/Desktop/view.pdf";
    [self.ohlcChartView exportInPDFFileAtPath:path];

    path = @"/Users/name/Desktop/view.jpg";
    [self.ohlcChartView exportInJPEGFileAtPath:path withQuality:0.8];
}

- (void)viewDidLoad {
    [super viewDidLoad];

    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction
                                                                                       target:self
                                                                                       action:@selector(exportToImage:)];
}
Filed under  //   Tips   iOSdev  

Sledujte velikost souborů a volného místa na zařízení

Představte si aplikaci, která často pracuje se soubory – například vytváří a ukládá fotky či videa. Pro takovouto aplikaci je takřka nezbytné sledovat, kolik jí zbývá volného místa na zařízení, a jak velké soubory vlastně ukládá.

Naštěstí, zjistit velikost dostupného místa, je záležitostí pár řádků kódu:

- (unsigned long long)spaceLeftOnDevice {

    NSError *error = nil;
    NSDictionary *systemInfo = [[NSFileManager defaultManager] attributesOfFileSystemForPath:[[NSBundle mainBundle] executablePath] error:&error];

    if (error != nil) {
        NSLog(@"%s : %@", __PRETTY_FUNCTION__, error);
        return NSNotFound;
    }
    return [[systemInfo objectForKey:NSFileSystemFreeSize] unsignedLongLongValue];
}

Využíváme toho, že třída NSFileManager je nám schopna podat informace o jakékoliv složce či souboru. Mezi těmito informacemi je, mimo jiné, i velikost volného místa.

Lehkou modifikací předchozí funkce můžeme také zjišťovat velikost libovolného souboru:

- (unsigned long long)sizeOfFileAtPath:(NSString *)filePath {

    NSError *error = nil;
    NSDictionary *fileInfo = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error];

    if (error != nil) {
        NSLog(@"%s : %@", __PRETTY_FUNCTION__, error);
        return NSNotFound;
    }
    return [fileInfo fileSize];
}

Weby jsou nuda, odlište se a vydělávejte na iPhone aplikacích

Již zhruba dva roky se věnuji programování aplikací na iPhone a iPad a situace v tomto oboru je tak kritická, že mě to přinutilo k napsání tohoto článku!

Vykašlete se na programování webů a pojďte dělat iPhone aplikace! Je to jednoduché a zábavné. A věřte mi, že za to jsou mnohem lepší peníze, než za weby, které dnes dělá každý. Ale ty peníze vám budou k ničemu, protože pro samou práci je nebudete mít čas utratit.

Poptávka v tomto oboru několikanásobně převyšuje nabídku, takže se nemusíte bát konkurence, už teď jste úspěšní! Lidé vás zasypou hromadou pracovních nabídek, jakmile se o vás dozví. Například mně neustále chodí nabídky z nejrůznějších kanálů:

  • emaily (a to jen díky mému webu urbasek.cz)
  • zprávy na linkedIn
  • nabídky přes Twitter
  • nabídky přes Facebook (štěstí, že ještě nemám G+)
  • nabídky od kamarádů, kteří chtějí iPhone appku do svých projektů
  • na několika konferencích jsem se zmínil, že dělám iPhone aplikace, a hned mi přistálo několik nabídek i s vizitkami
  • i mí spolužáci a učitelé(!) chtějí, ať s nimi spolupracuji

Za poslední dva roky jsem se naučil jednu velmi dobrou věc – říkat lidem NE. Opravdu rád bych uspokojil přání všech, ale den má bohužel (nebo bohudík) jen 24 hodin.

Takže návod je jednoduchý: pořídit si Mac a ZAČÍT hned TEĎ!

Pokud jen trošku chcete, tak o tom nepřemýšlejte a prostě to zkuste. Najde se spousta lidí, kteří vás zaměstnají i když ještě nic neumíte, stačí říci: „Ano, chci se to naučit!“. Já odmítám desítky nabídek a nikdy jsem si nemusel zakázky hledat nebo o ně dokonce bojovat. Stalo se vám toto někdy při tvorbě webových stránek?

Tímto se loučím a doufám v novou generaci českých iOS developerů. iPhonu zdar!

Vytváření triggerů v CoreData

Pomocí CoreData frameworku je velmi jednoduché implementovat trigger funkce. Trigger funkce je jakási funkce, která se má vykonat vždy, když dojde ke změně dat v databázi. Je to relativně známý pojem z relačních databází, např. z MySQL.

Pokud náhodou nevíte, co je to CoreData framework, tak je to velká ostuda a musíte to rychle napravit! Podívejte se třeba na wikipedii nebo na přehled tříd z CoreData frameworku.

Pro vytváření triggerů využíváme toho, že CoreData managed objekty přistupují k datům pomocí klasických properties, takže stačí pouze definovat setter ve kterém vykonáme požadované chování triggeru. Celé to můžeme ilustrovat na jednoduchém příkladu.

Dejme tomu, že máme CoreData managed objekt ShoppingList, který vlastní pole managed objektů Item (z pohledu relační databáze by se dalo říct, že tabulka ShoppingList je pomocí cizího klíče propojena s tabulkou Item v relaci 1:N).

A my chceme, aby se při změně property (atributu) ShoppingListu změnily i property všech Itemů, které ShoppingListu náleží. Například property isSelected:

// generated ManagedObject class by XCode
@implementation ShoppingList

@dynamic isSelected;

// ... other CoreData stuff

// overridden selector
- (void)setIsSelected:(NSNumber *)isSelected {

    [self willChangeValueForKey:@"isSelected"];
    [self setPrimitiveValue:isUsed forKey:@"isSelected"];

    for (Item *item in self.items) {
        item.isSelected = isSelected;
    }

    [self didChangeValueForKey:@"isSelected"];
}

@end

řádky

[self willChangeValueForKey:@"isSelected"];
[self setPrimitiveValue:isUsed forKey:@"isSelected"];
[self didChangeValueForKey:@"isSelected"];

zajistí propagaci změny property dovnitř CoreData frameworku a update databáze,

řádky

for (Item *item in self.items) {
    item.isSelected = isSelected;
}

jsou náš trigger!

S CoreData se dají dělat pěkné kousky, co říkáte…

Filed under  //   iOSdev  

Ukládání obrázku do Camera Roll

Někdy se stane, že potřebujete ve vaší aplikaci uložit obrázek tak, aby ho uživatel mohl stáhnout do počítače nebo použít v jiné aplikaci. Jedna z možností, jak toho docílit je uložit jej do Camera Roll, tj. tam, kde se ukládají obrázky vyfocené aplikací Fotoaparát.

Bohužel, zatím (v iOS5 a starších) není možné ukládat obrázky do existujícího alba ani vytvořit album nové.

Obrázek se ukládá pomocí funkce UIImageWriteToSavedPhotosAlbum(), která očekává parametr typu UIImage. Její použití je velmi jednoduché.

Metoda, která stáhne obrázek z webu a uloží ho do Camera Roll může vypadat následovně:

- (void)saveImageFromURLToCameraRoll:(NSString *)imageUrl {

    NSURL *url = [NSURL URLWithString:[imageUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
    UIImage *image = [UIImage imageWithData: [NSData dataWithContentsOfURL:url]]; 

    UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
}

Je nezbytné, aby metoda byla volána v jiném vlákně, než hlavním (Main Thread), protože volání [NSData dataWithContentsOfURL:] neskončí, dokud se nestáhne kompletní obsah z webu. Což v případě velkého obrázku a pomalého připojení může trvat dost dlouho a na hlavním vlákně by operace blokovala uživatelské rozhraní.

Zavolání metody v jiném vlákně se dá velmi pohodlně realizovat pomocí GCD:

NSString *imageUrl = ...

dispatch_async( dispatch_get_global_queue(0,0), ^{
    [self saveImageFromURLToCameraRoll:imageUrl];
});

Pro lepší uživatelskou přívětivost je také dobré zachytit případné chybové stavy. K tomuto účelu poskytuje funkce UIImageWriteToSavedPhotosAlbum() poslední 3 parametry, které říkají, jaká metoda se má při dokončení zavolat na který objekt. Podle mechanismu target – action.

Ošetření chyby ukládání obrázku je tedy velmi jednoduché:

// implementace selektoru predaneho jako 3. parametr UIImageWriteToSavedPhotosAlbum()

- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
    if (error != nil) {
        [self showError:error];
    }
}
Filed under  //   iOSdev  

Sdílení souborů z iOS aplikace do iTunes

To jsem tak jednou chtěl do aplikace přidat možnost stahování souborů přes iTunes. Zákazník na to prohodil, že pokud implementace zabere maximálně jednu minutu, tak to tam můžu dát, načež jsme se všichni zasmáli a řekli si, že tak rychle to opravdu udělat nepůjde.

To jsem ale ještě netušil, že na celou implementaci i s jejím vygooglením, mi 60 sekund bude bohatě stačit:

Itunes_file_sharing
Ano, je to takto jednoduché. Stačí pouze v souboru Info.plist specifikovat novou proměnnou Application supports iTunes file sharing s hodnotou YES a všechny soubory, které v aplikaci uložíte do složky /Documents, se automaticky budou zobrazovat a budou stažitelné v iTunes v sekci File Sharing.

Nutno dodat, že aplikace ještě musí nějaké soubory do složky /Documents ukládat. Realizace ukládání souboru do /Documents může vypadat následovně:

- (NSString *)applicationDocumentsDirectory {
    return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}

- (NSError *)saveFile:(NSURL *)fileUrl toDocumentsWithName:(NSString *)fileName {
         
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSError *error = nil;
    NSString *destFilePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:fileName];
    NSURL *destURL = [NSURL fileURLWithPath:destFilePath];

    if (![fileManager fileExistsAtPath:destFilePath]) {
        [fileManager copyItemAtURL:fileUrl toURL:destURL error:&error];
    }
    return error;
}
Filed under  //   iOSdev  

Aplikace, která nikdy nespí

Jak iPhonu přikázat, aby neuspával displej, a tedy celou aplikaci, i když je aplikace neaktivní? Je to snadnější, než by se dalo čekat.

[UIApplication sharedApplication].idleTimerDisabled = YES;

Doporučuji přečíst si dokumentaci k této property, dozvíte se tam například, že než se aplikace ukončí musí hodnotu nastavit zpět na NO.

Typické řešení může být pomocí App delegáta – nastavení hodnoty YES v metodě applicationDidBecomeActive: a hodnoty NO v metodě applicationWillResignActive:.

Proč by se aplikace vlastně neměla uspat? Pro některé aplikace je výhodné a někdy i nezbytné, aby displej nebyl automaticky uspán po několika minutách neaktivity. Mezi takové aplikace může patřit přehrávač videjí, nějaká hra, která se ovládá akcelerometrem, ale ne dotyky prstů atp.

Pro více informací se podívejte přímo do dokumentace property idleTimerDisabled.

Vlastní pozadí navigačního baru

Pokud jste někdy chtěli nastavit vlastní pozadí k titulnímu baru navigačního kontroleru (třída UINavigaitionController) , asi jste zjistili, že to není zcela jednoduchá záležitost. Titulní bar (třída UINavigaitionBar) nabízí jisté možnosti úpravy vzhledu, jako třeba změnu barvy pozadí, ale není to věru žádná sláva.

Nezbývá, než si pomoci nějak jinak.

Velmi jednoduchý způsob, jak tento problém vyřešit, je vytvořit si kategorii k UINavigationBar a předefinovat metodu drawRect:, která se stará o vykreslování navigačního baru.

Kategorie se může jmenovat třeba UINavigationBar+CustomBackground a její hlavičkový soubor vypadá zcela jednoduše

@interface UINavigationBar (CustomBackground)

@end

hlavní dění probíhá v UINavigationBar+CustomBackground.m

@implementation UINavigationBar (CustomBackground)

- (void)drawRect:(CGRect)rect {

    UIImage *image = [UIImage imageNamed:@"navBar.png"];
    [image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}

@end

Toto řešení je velmi jednoduché, ale má drobnou nevýhodu. Navigační bar bude mít upravené pozadí na všech obrazovkách (view controllerech), což není vždy to, co bychom si přáli. Například u mail composeru (MFMailComposeViewController) může být pestré pozadí navigační lišty docela na obtíž.

Jak to tedy vyřešit, abychom si mohli ručně zvolit, která obrazovka bude mít vlastní navigační bar a která ten základní? Máte nějaké typy, podělte se v komentářích?

Filed under  //   Tips   iOSdev  

Vlastní šablony tříd v XCode

Jste zvyklí v XCode používat jednotnou strukturu zdrojových kódů? Nevyhovují vám defaultní šablony tříd s hromadou zakomentovaných metod, které stejně hned smažete? Chcete přidat do všech zdrojových kódů společný text – například licenční text?

Tak to se vám určitě vyplatí věnovat trochu času a udělat si vlastní šablony pro zdrojové kódy v XCode.

Lokace šablon

Začněte tak, že zkopírujete složku /Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/ File Templates/Cocoa Touch Classes/. do ~/Library/Application Support/Developer/Shared/Xcode/File Templates/<Your Custom Template Name>, kde <Your Custom Template Name> bude název souboru šablon. Tento název XCode použije jako titulek šablon.

O zbytek se už XCode postará za vás a zobrazí novou šablonu v sekci User Templates. Například já používám dva soubory šablon – vlastní a firemní, takže mám definované dvě nové složky iOS Classes - Inmite a iOS Classes - Personal.

 

XCode také automaticky řeší hierarchii složek, takže není problém udělat si souborovou strukturu třeba

Personal/
    iOS Classes/
    Cocoa Classes/
    Resources/

a máte to hezky organizované. Můžete si s tím pohrát.

Jak si upravit vlastní šablony

Samotná úprava šablon je velmi jednoduchá, důležité je zachovat správné uspořádání složek. Šablona pro každou třídu je vždy samostatná složka, která obsahuje soubory TemplateChooser.plist a <něco>.pbfiletemplate. Text šablony upravíte právě ve složce <něco>.pbfiletemplate, jejíž strukturu už zajisté pochopíte.

Celé řešení je jednoduché a umožňuje, kromě úpravy existujících, definovat si i zcela nové šablony. Používáte často kategorie a chtěli byste pro ně mít vlastní šablonu? Stačí vedle složky Objective-C class/ vytvořit sožku Objective-C Category/ s požadovanou vnitřní strukturou a máte vyhráno.

Jak jednoduché a intuitivní.

Filed under  //   XCode   iOSdev  

About

IT student, iOS developer
Follow me on @jiriurbasek