Programování pro iOS - 41. Zbývající služby UIPopoverControlleru - 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 - 41. Zbývající služby UIPopoverControlleru

11. května 2011, 00.00 | V součastnosti se zabýváme službami třídy UIPopoverController, jež slouží na iPadu pro zobrazování "okének", plovoucích nad hlavním grafickým rámcem. Dnes toto téma dokončíme.

Minule jsme se zabývali takřka výhradně "triky a podrazy", jež si na nás programátoři Apple při implementaci třídy UIPopoverController vymysleli: naučili jsme se řídit velikost "popoveru", a také jsme si ukázali, proč mnohdy jsou jeho "zobáčky" na nesprávném místě a jak tento problém řešit.

Dnes se opět vrátíme ke standardnímu API a začneme tím, jak přimět "popover" k tomu, aby nám "nekradl" uživatelské akce.

Aktivní rámce pod popoverem

První vylepšení, o němž jsme se zmínili už minule, spočívá v tom, že po klepnutí někam do našeho rámce ve chvíli, kdy je "popover" zobrazen, jej nebudeme nejprve rušit – což je standardní chování –, ale rovnou jej přemístíme na nové souřadnice (a adekvátně změníme jeho obsah).

K tomu se ale nejprve musíme k tomuto klepnutí dostat: za normálních okolností je náš rámec (resp. jeho gesture recognizer) vůbec nedostane: jakékoli klepnutí mimo sebe interpretuje sám "popover" jako pokyn k uzavření.

Naštěstí zde je ale poměrně pohodlné API, jehož prostřednictvím můžeme instanci třídy UIPopoverController říci, které rámce mají zůstat aktivní i ve chvíli, kdy je "popover" zobrazen: jejich seznam prostě v poli uložíme do jeho atributu

@property (nonatomic,copy) NSArray *passthroughViews;

Kdykoli pak uživatel klepne do některého takového rámce, "popover" tuto akci ignoruje a zůstává na místě beze změny.

V našem případě tedy potřebujeme provést několik drobných úprav: především si musíme v řídicím objektu pamatovat aktuální "popover", abychom k němu měli přístup i nadále. Vzhledem k tomu, že – jak víme z předminulého dílu – se stejně musíme postarat o přidržení "popoveru" službou retain, můžeme k tomu s výhodou využít generovaných atributů. Změníme tedy rozhraní třídy v hlavičkovém souboru takto:

@interface SecondViewController:UIViewController {
  IBOutlet UILabel *label,*xx,*yy;
}
@property (retain,nonatomic) UIPopoverController *popover;
@end

a na začátek implementace přidáme vytvoření odpovídající instanční proměnné a obou přístupových metod:

@implementation SecondViewController
@synthesize popover;
...

Metodu tapped: pozměníme tak, aby

• vytvořila novou instanci třídy UIPopoverController pouze v případě, že dosud žádná neexistuje;

• uložila ji do našeho atributu (čímž se ovšem také automaticky "retainuje" – nezapomeneme proto při vytvoření objektu hezky hned použít autorelease, jak je dobrým zvykem);

• a do atributu passthroughViews mu uložila náš rámec – klepnutí na něj tedy "popover" již nadále nezavře;

• a změnila obsah obou textových polí se souřadnicemi – to samozřejmě budu fungovat beze změny.

V metodě delegáta popoverControllerDidDismissPopover: pak jen atribut popover vyprázdníme (což samozřejmě objekt uvolní).

Trochu na nejisté půdě jsme jen s přemístěním "popoveru" na nové souřadnice; k tomu firma Apple oficiální API nenabízí, ale v praxi se ukazuje, že můžeme již existujícímu objektu znovu poslat zprávu presentPopoverFromRect:inView:permittedArrowDirections:animated: a bude to fungovat stejně dobře (resp. stejně špatně, vizte minulý díl :)), jako poprvé.

Celý kód by tedy mohl vypadat zhruba nějak 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];
    
  if (!self.popover) {
    UIViewController *vc=
      [[[UIViewController alloc] init] autorelease];
    vc.view=xx.superview;
    self.popover =[[[UIPopoverController alloc]
      initWithContentViewController:vc] autorelease] ;
    self.popover .delegate=(id)self;
    self.popover .popoverContentSize=vc.view.frame.size;
    self.popover.passthroughViews=
      [NSArray arrayWithObject:self.view];
  }
  pt=[self.view convertPoint:pt toView:self.tabBarController.view];
  [self.popover  presentPopoverFromRect:CGRectMake(pt.x,pt.y,0,0)
    inView:self.tabBarController.view
    permittedArrowDirections:UIPopoverArrowDirectionAny
    animated:YES];
}
-(void)popoverControllerDidDismissPopover:
  (UIPopoverController*)poc {
    self.popover=nil;
}

Nyní funguje vše, jak má – klepneme-li na jiné místo, "popover" na ně hned přeskočí. Zavřít jej můžeme pouze tak, že klepneme někam mimo náš rámec (např. do oblasti ikon v "tab baru" při dolním okraji obrazovky).

Programové uzavření a další podraz...

Moment ale – to, že "popover" nejde zavřít jinak, než klepnutím "kamsi mimo", to přece není dobře! Daleko lepší by bylo, kdyby jej uživatel mohl zavřít nějak přímo.

Samo o sobě to není těžké – klasickým postupem, který již jistě dávno všichni máte v malíčku, přidáme do rozhraní řídicího objektu akci closePopover:

@interface SecondViewController : UIViewController {
    IBOutlet UILabel *label,*xx,*yy;
}
@property (retain,nonatomic) UIPopoverController *popover;
-(IBAction)closePopover;
@end

v Interface Builderu pak do rámce, jenž tvoří obsah "popoveru", uložíme tlačítko a "prodrátujeme" je s touto akcí:

V její implementaci pak můžeme použít standardní službu dismissPopoverAnimated:, asi takto:

-(IBAction)closePopover { // pozor, není to přesně ono
    [self.popover dismissPopoverAnimated:YES];
}

Ono to v zásadě bude fungovat dobře; jak už ale bylo uvedeno v diskusi k předminulému dílu, pouze potud, pokud neděláme v metodě popoverControllerDidDismissPopover: nic zvlášť důležitého – tak, jak máme aplikaci napsanou nyní, by nám mohly přibývat "leaky". To proto, že UIPopoverController se prostě zprávu popoverControllerDidDismissPopover: neobtěžuje poslat, pokud byl uzavřen programově!

Poslední úpravou, která je zapotřebí, tedy je explicitní odeslání této zprávy:

-(IBAction)closePopover { // toto je lepší
    [self.popover dismissPopoverAnimated:YES];
    [self popoverControllerDidDismissPopover:self.popover];
}

Na konec jen znímka o posledním triku...

V praxi poměrně často "popovery" otevíráme na základě stisknutí některého tlačítka v příkazové liště. V takovém případě se nemusíme obtěžovat s hledáním správného místa, na němž by se měl "zobáček" objevit; namísto zprávy presentPopoverFromRect:inView:permittedArrowDirections:animated: totiž máme k dispozici zprávu

-(void)presentPopoverFromBarButtonItem:(UIBarButtonItem*)item
  permittedArrowDirections:(UIPopoverArrowDirection)ads
  animated:(BOOL)animated;

jež to udělá za nás (a kupodivu správně).

Nicméně, jeden malý trik se skrývá i zde: tentokrát nejde o chybu ale naopak o úmyslné a korektně zdokumentované chování, přesto však dokáže nezkušené programátory překvapit – takto presentovaný "popover" totiž automaticky uloží "toolbar", v němž je tlačítko item, do atributu passthroughViews!

Bylo by asi zbytečné uvažovat o tom, zda je to žádoucí nebo nesmyslné chování; každopádně tomu tak je, a pokud tedy např. dalším klepnutím na totéž tlačítko nechceme otevřít další "popover" přes ten minulý, musíme si na to dávat pozor :)

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: