Programování pro iOS - 39. UIPopoverController - 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:

Seriály

Více seriálů



Software

Programování pro iOS - 39. UIPopoverController

27. dubna 2011, 00.00 | V minulém dílu našeho seriálu jsme dokončili popis základních služeb řídicího objektu pro "tab bar" a také jsme si sestavili jednoduchou testovací aplikaci. Dnes kód této aplikace rozšíříme o použití třídy UIPopoverController.

Dříve, než odbočíme od služeb API k vývojovému prostředí a než se podrobněji podíváme na nové Xcode 4, dokončíme současný blok, v němž se zabýváme řídicími objekty rámců. Po obecném popisu jejich služeb a možností se nyní zabýváme řídicími objekty, jež slouží jako "kontejnery" pro jiné řídicí objekty a automatizují jejich vzhled i vzájemnou interakci: seznámili jsme se s třídami UINavigationController a UITabBarController, a dnes se podíváme na použití třídy UIPopoverController.

Co to je a k čemu je to dobré?

Třída UIPopoverController definuje rozhraní speciálního řídicího objektu; ten – stejně jako ty předchozí – sám obsahuje jiný řídicí objekt, který reprezentuje "vlastní obsah". UIPopoverController se pak stará o jeho speciální presentaci na obrazovce.

Jak už jeho název napovídá, jde o prezentaci v "okénku", jež se zobrazí nad běžným GUI. Toto okénko obsahuje potřebné informace, obvykle se odkazuje na místo, z nějž jsme je vyvolali, "zobáčkem" při některém z okrajů, a můžeme je kdykoli zavřít; vypadá zhruba nějak takto:

Právě implementaci takovéhoto "popoveru", který zobrazí souřadnice bodu, na nějž klepneme prstem, si dnes ukážeme.

Základy, pomocný řídicí objekt a jeho rámec

Jelikož UIPopoverController je "kontejnerem", potřebujeme řídicí objekt rámce, který do něj uložíme – a ten samozřejmě potřebuje rámec, který skrze "popover" zobrazí. V praxi k tomu téměř vždy využijeme vlastní podtřídu UIViewController s rámcem buď načteným z vlastního NIBu nebo vytvořeným programově, tak, jak jsme se to učili v předchozích dílech našeho seriálu.

Zde si ale pro zjednodušení ukázkového kódu a proto, abychom se soustředili jen na to podstatné, dovolíme "zkratku":

• namísto vlastní podtřídy použijeme přímo UIViewController; jelikož po něm žádné specifické služby nebudeme chtít, vystačíme si s ním;

• rámec pak načteme z jiného NIBu a našemu řídicímu objektu jej vnutíme programově.

Použijeme k tomu třeba SecondViewController z aplikace, již jsme sestavili v minulém dílu: rámec, který budeme v "popoveru" zobrazovat, uložíme do jeho NIBu a veškerý řídicí kód umístíme do zdrojového souboru "SecondViewController.m". Zároveň si ukážeme jeden potenciální "podraz", na který mohou uživatelé NIBů v iOSu narazit; ačkoli s řídicími objekty nesouvisí přímo, je dobré o něm vědět.

Začneme tedy tím, že otevřeme rozhraní v souboru "SecondViewController.h" a přidáme do něj dva "outlety" pro dvě textová pole. Od minula zde již máme jeden outlet "label"; stačí je tedy přidat k němu. Pojmenovat je můžeme třeba "xx" a "yy":

@interface SecondViewController:UIViewController {
  IBOutlet UILabel *label,*xx,*yy;
}
@end

Pak otevřeme objektovou síť "SecondViewController.xib" a postupně

• do ní (do kořene, do jejího hlavního okna – nikoli do existujícího rámce!) přidáme z palety nový rámec;

• vhodně upravíme jeho velikost;

• uložíme do něj dvě textová pole;

• a propojíme je s "outlety" xx a yy.

Propojování outletů a prvků GUI jsme si ukázali na obrázku v minulém dílu; dnes proto ilustrujeme vkládání textového pole z palety:

Vhození vlastního rámce bylo ovšem stejné; jen namísto rámce bylo cílem přímo samotné okno "SecondViewController.xib".

Obsluha klepnutí a hlavně správa paměti s objekty z NIBu

Nyní můžeme vstoupit do zdrojového kódu řídicího objektu SecondViewController a doplnit jeho metodu viewDidLoad takto:

-(void)viewDidLoad {
  [super viewDidLoad];
  label.text=self.title;
  [self.view addGestureRecognizer:
    [[[UITapGestureRecognizer alloc]
      initWithTarget:self action:@selector(tapped:)]
      autorelease]];
  [xx.superview retain];
}

Prvý z přidaných řádků je zřejmý – k hlavnímu rámci přičleníme gesture recognizer, který nám pošle zprávu tapped: kdykoli uživatel do rámce klepne prstem; to už dávno známe a umíme.

Proč ale to retain na druhém z řádků?

Zde právě narážíme na obecný "podraz" při práci s NIBy: v iOSu – na rozdíl od Mac OS X – se všechny kořenové objekty, načtené z NIBu, automaticky "autoreleasují". Pokud bychom si je nějak nepřidrželi – zde tedy v případě našeho "popoverového" rámce zprávou retain – automaticky by byly uvolněny na konci event loopu.

"Moment," volají nyní asi středně zkušení programátoři, "jak to, že tento osud nepostihne i základní rámec?"

To je druhý "podraz" při práci s NIBy: při načtení NIBu se totiž vazba mezi "outlety" a do nich "nadrátovanými" objekty GUI vytvoří pomocí služeb KVC (Key-Value Coding). Ty, jak už dávno víme, ovšem fungují (v zásadě) takto:

• naleznou-li vhodnou přístupovou metodu, prostě ji použijí;

• jinak vyhledají vhodnou instanční proměnnou, objekt do ní uloží, a použijí na něj automaticky 'retain'

A to je ono: pokud – jak je běžné – připojujeme objekty v NIBu do "outletů" deklarovaných jako instanční proměnné, jsou tyto objekty automaticky "retainované" (a měli bychom si, mimochodem, dát tu práci je korektně uvolnit v metodě dealloc a případně také viewDidUnload – tím se pro dnešek zabývat nebudeme a smíříme se s drobnými a nepodstatnými "leaky").

V našem případě jsou díky tomuto mechanismu "retainovaná" textová pole xx a yy, ale pozor, nikoli jejich nadřízený rámec, protože ten jsme do žádné instanční proměnné neuložili. Musíme si jej proto v metodě viewDidLoad přidržet explicitně, jinak by v době, kdy jej budeme chtít vložit do "popoveru", již neexistoval.

V zásadě by to bylo snadné...

Základní použití třídy UIPopoverController je velmi jednoduché: vytvoříme její instanci – přitom jí předáme řídicí objekt, který určuje její obsah – a zobrazíme ji v požadovaném místě.

Mohli bychom tedy napsat např. tento kód:

-(void)tapped:(UITapGestureRecognizer*)tgr { // nefunguje!
  CGPoint pt=[tgr locationInView:self.view];
  xx.text=[NSString stringWithFormat:@"x=%g",pt.x];
  yy.text=[NSString stringWithFormat:@"y=%g",pt.y];

  UIViewController *vc=
    [[[UIViewController alloc] init] autorelease];
  vc.view=xx.superview;

  UIPopoverController *pc=[[[UIPopoverController alloc]
    initWithContentViewController:vc] autorelease];
  [pc presentPopoverFromRect:CGRectMake(pt.x,pt.y,0,0)
    inView:self.view
    permittedArrowDirections:UIPopoverArrowDirectionAny
    animated:YES];
}

Logika je jednoduchá – nejprve zjistíme bod, v němž uživatel do rámce klepl (pt) a jeho souřadnice zapíšeme do textových polí xx a yy. Pak vytvoříme pomocný řídicí objekt rámce, a vnutíme mu náš rámec s textovými poli (tedy xx.superview).

Pak vytvoříme "popover" nad naším pomocným řídicím objektem, a pomocí standardní metody presentPopoverFromRect:inView:permittedArrowDirections:animated: jej zobrazíme tak, aby se na místo klepnutí odkazoval "zobáčkem" v libovolném směru.

Jenže bohužel, programátoři firmy Apple si na nás vymysleli v kódu třídy UIPopoverController předlouhou řadu podrazů a triků.

... nebýt prapodivné správy paměti

U tříd, které reprezentují objekty, jež stojí samostatně v GUI, je logicky zvykem, že dokud jsou zobrazeny, samy sebe "retainují"; uvolněny jsou právě ve chvíli, kdy je uživatel z obrazovky odstraní – srovnejte např. chování třídy UIAlertView. Bohužel, z jakéhosi nepochopitelného důvodu třída UIPopoverController tuto konvenci nedodržuje!

Pokusíme-li se proto použít výše uvedený kód, aplikace jednoduše "sletí" na výjimce "-[UIPopoverController dealloc] reached while popover is still visible."

Oprava je samozřejmě triviální, ačkoli poněkud obtěžující – využijeme služeb delegáta, jejichž prostřednictvím "popover" informuje o tom, že je uzavřen; uvolnění pak namísto autorelease delegujeme na release v metodě popoverControllerDidDismissPopover:, např. takto:

-(void)tapped:(UITapGestureRecognizer*)tgr {
  CGPoint pt=[tgr locationInView:self.view];
  xx.text=[NSString stringWithFormat:@"x=%g",pt.x];
  yy.text=[NSString stringWithFormat:@"y=%g",pt.y];
    
  UIViewController *vc=
    [[[UIViewController alloc] init] autorelease];
  vc.view=xx.superview;
  UIPopoverController *pc=[[UIPopoverController alloc]
    initWithContentViewController:vc];
  pc.delegate=(id)self;
  [pc presentPopoverFromRect:CGRectMake(pt.x,pt.y,0,0)
    inView:self.view
    permittedArrowDirections:UIPopoverArrowDirectionAny
    animated:YES];
}
-(void)popoverControllerDidDismissPopover:
  (UIPopoverController*)poc {
    [poc release];
}

Takto již bude aplikace v zásadě dělat co má – vyzkoušejte si to! –, ale "popover" nebude vypadat úplně přesně tak, jak jsme chtěli. Proč tomu tak je a jak to léčit si ale ukážeme až příště.

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  

 

 

 

Nejčtenější články
Nejlépe hodnocené články
Apple kurzy

 

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

Uživatelské jméno:

Heslo: