Programování pro iOS - 4. Varovná hlášení - MujMAC.cz - Apple, Mac OS X, Apple iPod

Odběr fotomagazínu

Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!

 

Zadejte Vaši e-mailovou adresu:

Kamarád fotí rád?

Přihlas ho k odběru fotomagazínu!

 

Zadejte e-mailovou adresu kamaráda:

Soutěž

Sponzorem soutěže je:

IDIF

 

Jaký fotograf/ka získal/a cenu za nejpopulárnější příspěvek v Nikon Photo Contest?

V dnešní soutěži hrajeme o:

Seriály

Více seriálů



Informace

Programování pro iOS - 4. Varovná hlášení

26. srpna 2010, 00.00 | Začneme aplikaci trochu vylepšovat. Přitom si ukážeme řadu standardních technik, jež se používají při programování aplikací v iOS. Dnes to bude práce s varovnými hlášeními - alerty.

V minulém dílu našeho seriálu jsme sestavili sice jednoduchoučkou, ale přece jen plně funkční aplikaci. Dnes ji začneme trochu vylepšovat a přitom si ukážeme řadu dalších standardních technik, jež se používají při programování aplikací v iOS; dnes si ukážeme kromě jiného práci s varovnými hlášeními ("alerty"), a hned v příštím dílu si řekneme více o řídicích objektech rámců, jež jsou v iOS velmi důležité (mnohem důležitější, než v Mac OS X).

Memoria minuitur, nisi eam exerceas

Prvé zdokonalení, jež do aplikace implementujeme, bude paměť minulých měření. Takový záznam se může hodit, a navíc ji budeme později také moci využít pro vyhodnocení toho, jestli se bouřka přibližuje nebo vzdaluje a jak rychle.

Základní implementace paměti je velmi jednoduchá: využijeme standardních tříd knihovny Foundation; každé měření uložíme do dynamického pole NSMutableArray jako slovník, obsahující dva údaje – časovou značku a naměřenou vzdálenost.

Nejprve si připravíme pole pro ukládání objektů. Nejjednodušší – a v našem případě vcelku i postačující – by ovšem bylo přidat další proměnnou objektu "NSMutableArray *history"; my ale využijeme podporu explicitních objektových atributů, již Objective C nabízí od verze 2. První důvod je didaktický – neuškodí, pokud si práci s atributy procvičíme; kromě toho ale také existuje i důvod praktický, ačkoli konkrétně v tomto případě je velmi slabý: výhodou atributů oproti jednoduchým objektovým proměnným je to, že se nemusíme starat o správu paměti (ta, jak víme, je v iOS ne zcela zanedbatelná, protože zde není k dispozici automatický garbage collector).

Jelikož iOS podporuje všechny výhody moderní architektury Objective C 2 (na rozdíl od dvaatřicetibitových prostředí Mac OS X), stačí pro kompletní podporu nového atributu pouhé dva řádky zdrojového textu; jeden v rozhraní:

@interface MainViewController:UIViewController
  <FlipsideViewControllerDelegate> {
    ...
}
@property (retain) NSMutableArray *history;
...
@end

a jeden v implementaci:

@implementation MainViewController
@synthesize history;
...

Nyní již můžeme implementovat vlastní ukládání záznamů – mohlo by to vypadat kupříkladu takto – zároveň jsme přidali omezení na nejvýše 50 uložených měření, aby nám pole nerostlo nade všechny meze (ačkoli v současné verzi aplikace, kdy o obsah pole přijdeme při jejím ukončení, to samozřejmě není příliš podstatné; za chvilku ale budeme pole ukládat):

@implementation MainViewController
...
-(IBAction)rangeButtonTapped {
  ...
  } else { // thunder
    NSTimeInterval diff=
      [NSDate timeIntervalSinceReferenceDate]-light;
    light=0;
    double dist=diff*330.;
    range.text=[NSString stringWithFormat:
      @"%.1 f km",dist/1000.];
    if (!self.history)
      self.history=[NSMutableArray array];
    [self.history addObject:
      [NSDictionary dictionaryWithObjectsAndKeys:
        [NSNumber numberWithDouble:dist],@"dist",
        [NSDate date],@"timestamp",
        nil]];
    while (self.history.count>50)
      [self.history removeObjectAtIndex:0];
  }
}
...

Povšimněme si několika drobností: předně, pole vytváříme "on demand", teprve ve chvíli, kdy je to skutečně zapotřebí (a ne hned při inicializaci objektu v jeho metodě init). V naprosté většině případů je tento přístup optimální: nejenže šetří paměť a snižuje čas, potřebný ke spuštění aplikace (či k přepnutí do jiného režimu apod.); navíc je spolehlivější a bezpečnější – potenciálními riziky alokace zdrojů v metodě init jsme se zabývali před časem podrobněji.

Dále pak použití cyklu while pro odstranění nadbytečných prvků v poli vypadá na první pohled jako neefektivní – nebylo by lepší odstranit všechny nadbytečné prvky najednou pomocí standardní služby removeObjectsInRange:? Inu, v obecném případě zcela jistě ano; zde je to ale zbytečné, protože přidáváme do pole vždy jen jeden záznam; odstraňovat tedy budeme zase jen jediný.

Inu dobrá, řeknete asi – pak ale proč while a ne pouhý if? To proto, že programujeme defensivně: kdybychom snad přece jen někde náhodou udělali chybu a omylem do pole přidali více prvků, zaručí nám while, že zde bude chyba napravena – a pokud je vše v pořádku, není mezi if a while v tomto případě zhola žádný rozdíl.

Konečně pak, protože nemáme k dispozici automatický garbage collector, musíme při zrušení řídicího objektu paměť záznamů uvolnit (v tomto případě bychom si to mohli "ponechat od cesty", protože řídicí objekt hlavního rámce je vytvořen jednou a nikdy se neuvolňuje; z tréninkových důvodů ale uvolnění implementujeme, ač se v praxi nevyužije):

-(void)dealloc {
    self.history=nil;
    [super dealloc];
}

Pozn.: mezi programátory v Cocoa existuje dlouhý a prastarý spor o to, zda v metodách init... a dealloc užívat přístupových metod /"accessors"/ nebo ne. Oficiální doporučení firmy Apple je spíše negativní; naopak autor tohoto článku to považuje z mnoha důvodů za správné. Ať tak či onak, v případech, jako je tento, vůbec neexistuje žádný způsob, jak bychom se použití přístupové metody mohli vyhnout! Je tedy zřejmé, že jde o postup správný – prostě proto, že je jediný možný.

Zobrazení historie

Máme-li tedy hotové ukládání historie, pojďme implementovat její zobrazení.

Nejprve do rámce přidáme vhodné tlačítko, do řídicího objektu MainViewController metodu showHistory, a tuto metodu propojíme pomocí mechanizmu akce/cíl s událostí "Touch Up Inside" nového tlačítka; to už všechno umíme, dělali jsme to tak s tlačítkem pro vlastní měření.

Mimochodem, proč má akce showInfo: připravená na základě projektového vzoru argument sender, zatímco naše akce jej nemají? To je tak: v iOS akce může tento argument mít a nemusí; pokud jej použijeme, bude v argumentu předán prvek uživatelského rozhraní, který akci poslal – zde tedy odpovídající tlačítko. Akce by pak mohla rozlišit způsob, jimž byla vyvolána, a podle toho nabízet mírně odlišnou funkčnost. Programátoři Apple argument do projektového vzoru dali proto, že by jej třeba někdy někdo mohl potřebovat; my jej u našich akcí nepoužíváme, protože víme, že jej potřebovat nebudeme.

Prázdná historie

Nejprve si ukážeme možnou implementaci akce showHistory v případě, kdy je historie prázdná; naučíme se přitom zobrazovat varovná hlášení ("alerty"):

-(IBAction)showHistory {
  if (self.history.count==0)
    [[[[UIAlertView alloc]
      initWithTitle:@"No history"
      message:@"The history list is empty."
      delegate:nil
      cancelButtonTitle:@"OK"
      otherButtonTitles:nil]
        autorelease] show];
}

Vytvoříme instanci standardní knihovní třídy UIAlertView, nastavíme vhodným způsobem její atributy, postaráme se o její automatické uvolnění pomocí zprávy autorelease, a zobrazíme ji zprávou show. iOS zobrazí varovné hlášení a po klepnutí na jeho tlačítko je automaticky zavře; o to se starat nemusíme.

U práce s varovnými hlášeními se chvilku zdržíme, protože se od toho, nač jsme zvyklí z Mac OS X, poněkud liší. Jde o případ, kdy má hlášení více různých tlačítek (ty můžeme přidat tak, že jejich titulky – ukončené hodnotou nil – zapíšeme jako argumenty za otherButtonTitles:), a kdy v kódu je zapotřebí reagovat na to, které z tlačítek uživatel vybral.

V tomto případě musíme udělat následující:

  • v rozhraní explicitně deklarovat, že náš řídicí objekt odpovídá protokolu UIAlertViewDelegate (pokud bychom to neudělali, vše by fungovalo bez nejmenších problémů, ale při překladu by se ohlásilo varování);
  • namísto delegate:nil předat alertu odkaz na řídicí objekt pomocí delegate:self;
  • implementovat metodu alertView:clickedButtonAtIndex: a v ní stisknutí každého tlačítka ošetřit vhodným způsobem.

Jestliže náš řídicí objekt spravuje více varovných hlášení, je to trochu složitější: bohužel, na rozdíl od Mac OS X nelze při sestavování alertu určit zprávu, kterou po stisknutí tlačítka pošle – ta je a zůstává vždy alertView:clickedButtonAtIndex:. Proto musíme buď pro různé alerty použít různých delegátů, nebo – což je obvykle pohodlnější – při sestavování alertu nastavit jeho atributy (ideální je atribut tag, zděděný od nadtřídy UIView) a podle nich jej identifikovat:

-(void)alertView:(UIAlertView*)av
  clickedButtonAtIndex:(NSInteger)bi {
  switch (av.tag) {
    case 1:
      ...
  }
}
-(void)fooBar {
  if (...) {
    UIAlertView *av=[[UIAlertView alloc]
      initWith... ...] autorelease];
    av.tag=1;
    [av show];
  }
}

Pokud píšeme aplikace pouze pro iOS 4 a vyšší, můžeme si samozřejmě snadno doplnit nesrovnatelně pohodlnější podporu práce s bloky zhruba na stejném principu, na jakém jsme si již dříve ukázali její doplnění do Mac OS X.

Naopak zase v iOS 4 musíme při používání varovných hlášení zvážit, co se má stát při "uzavření" aplikace ve chvíli, kdy uživatel stiskne tlačítko "Home". Až do verze 3 včetně stisknutí tohoto tlačítka aplikaci vždy ukončilo; tím samozřejmě také zmizelo varovné hlášení, a po příští aktivaci téže aplikace se nezobrazilo (leda bychom se o to explicitně postarali v kódu). iOS 4 naproti tomu aplikaci ponechá běžet, jen ji přemístí do pozadí a pozastaví její běh; po příští aktivaci tedy varovné hlášení zůstává viditelné. Chceme-li tedy v iOS 4 zachovat stejnou funkčnost jaká byla předtím, musíme si při otvírání alerty zapamatovat, a ve chvíli, kdy je aplikace "uzavřena" (tedy když její delegát dostane systémovou zprávu applicationDidEnterBackground:), všechny aktivní alerty uzavřít zprávou dismissWithClickedButtonIndex:0 animated:NO.

Vypnutí "multitáskingu"

U velmi jednoduchých aplikací, které se načtou a spustí opravdu rychle (a v některých speciálních případech také u složitějších, ale to prozatím ponechme stranou) se může vyplatit vyřešit (nejen) tento problém tím nejjednodušším možným způsobem – pro aplikaci zakázat "multitásking". Ta se pak bude ve všech verzích iOS chovat stejně, jako tomu bylo v iOS 3 a starších: po stisknutí tlačítka "Home" prostě skončí. Stačí do souboru Info.plist přidat položku

<key>UIApplicationExitsOnSuspend</key>
<true/>

Inspektor atributů cíle pro to specialisovaný ovladač nemá; můžeme to ale celkem pohodlně udělat přímo v okně Xcode – otevřeme-li v něm soubor (Jméno projektu)-Info.plist, Xcode zobrazí editor, v němž nejenže máme k dispozici seznam standardních klíčů ze kterého můžeme vybrat; navíc jsou klíče i hodnoty zobrazeny uživatelsky přívětivým způsobem (konkrétně jako text "Application does not run in background" a jako přepínač).

Obsah seriálu (více o seriálu):

Tématické zařazení:

 » Rubriky  » Informace  

 » Rubriky  » Agregator  

 » Rubriky  » Tipy a Triky  

 » Rubriky  » Začínáme s  

 » Rubriky  » Software  

 

 

 

 

Přihlášení k mému účtu

Uživatelské jméno:

Heslo: