Programování pro iOS - 8. Dokončení aplikace - 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ů



Software

Programování pro iOS - 8. Dokončení aplikace

22. září 2010, 00.00 | Aplikace pro měření vzdálenosti bouřky nám hezky vyrostla pod rukama. Zbývá dokončit pár posledních detailů a verzi 1.0 prohlásit za hotovou.

Nejprve ale jedno slíbené vysvětlení.

Mezi řadou dalších námětů jak aplikaci vylepšit jsme si minule uvedli také variantu: "Mohli bychom také měnit grafické pozadí rámce podle toho, zda se bouře blíží nebo vzdaluje". Na rozdíl od všech ostatních, které jsme si vyjmenovali, tato není úplně triviální; proto si dnes ukážeme, jak na to.

Nejprve si připravíme vhodné obrázky – nejspíše budou tři, jeden pro běžné pozadí (můžeme jej nazvat třeba "storm.png"), druhý nějaký velmi romantický pro blížící se bouři (ten se může jmenovat například "stormComing.png") a třetí klidný pro bouřku na odchodu ("stormGoing.png"). Uložíme je do projektu pomocí služby "Add / Existing files..."; dáme si přitom pozor na to, aby byl ve druhém dialogovém lístku v tabulce "Add to Targets" označený cíl. To zajistí, že obrázky budou při sestavování aplikace zkopírovány do její složky, a při jejím běhu je budeme mít k dispozici.

Pak se postaráme o to, aby náš hlavní rámec dokázal statický obrázek na pozadí zobrazovat.

Možností je několik; asi nejjednodušší ale je nahradit obyčejný rámec třídy UIView jeho podtřídou UIImageView; ta funguje stejně dobře jako hlavní rámec, a navíc je právě speciálně vytvořena pro podporu obrázků (hodně speciálně: dokonce používá zvlášť optimalizované nestandardní kreslení obsahu, takže pokud bychom si vytvořili její podtřídu, nebude nám v ní fungovat ani standardní metoda drawRect:!).

Otevřeme tedy objektovou síť "MainView.xib", v hlavním okně Interface Builderu označíme objekt "View", a v inspektoru identity hned v prvním textovém poli "Class" změníme text "UIView" na "UIImageView" – pro větší pohodlí můžeme použít rozevírací nabídku.

Pak se vrátíme do zdrojového kódu a doplníme jej např. takto:

// MainViewController.h
...
@interface MainViewController:UIViewController
  <FlipsideViewControllerDelegate> {
    IBOutlet UILabel *range,*speed;
}
@property (retain) NSMutableArray *history;
@property
  (nonatomic, retain, getter=view, setter=setView)
    UIImageView *imageView;
...

Tím definujeme nový atribut imageView; má stejné parametry jako atribut view, který jsme zdědili od třídy UIViewController (a v němž máme odkaz na rámec); má stejné přístupové metody view a setView – tedy má fakticky touž hodnotu; má ale odlišný typ: namísto UIView* je jeho typem UIImageView*. To nám umožní nový atribut přímo používat v konstrukcích, kde je zapotřebí odpovídající typ, aniž bychom se museli obracet k nehezkým konstrukcím s přetypováním (jako třeba "[(UIImageView*)self.view setImage:...]" nebo dokonce "((UIImageView*)self.view).image=...").

Je mimochodem velká škoda, že Objective C nezůstalo u jediného objektového typu id – konkrétní typy odpovídající jednotlivým třídám přinášejí daleko víc problémů než užitku; objektový systém má ideálně být zcela beztypový. Ale to odbočujeme.

Implementace by pak mohla vypadat kupříkladu takto:

// MainViewController.m
...
-(IBAction)rangeButtonTapped {
  ...
  if (pdist>dist) {
    speed.text=@"Bouře se blíží!";
    self.imageView.image=
      [UIImage imageNamed:@"stormComing.png"];
  } else {
    speed.text=@"Bouře se vzdaluje...";
    self.imageView.image=
      [UIImage imageNamed:@"stormGoing.png"];
  }
  ...
}
-(void)viewDidLoad {
[super viewDidLoad];
    self.history=[OCSAutosavedMutableArray array];
    self.imageView.image=
      [UIImage imageNamed:@"storm.png"];
}
...

Standardní třída UIImage nabízí řadu služeb pro práci s obrázky; prozatím nám stačí to, že na základě zprávy imageNamed: dokáže vyhledat obrázek uložený v aplikační složce podle jména – od verze iOS 4 již není zapotřebí zadávat příponu; služba si stejně dobře poradí s obrázkem .png jako třeba s GIFem nebo s čímkoli jiným, co iOS podporuje; my ji však použili, aby náš kód pracoval stejně dobře i ve starších systémech – a načíst jej z disku.

Služba navíc obrázky automaticky cachuje, takže si nemusíme dělat starost s její efektivitou.

Často se můžeme setkat s varováním, že to je zároveň její velký problém, neboť cache nevyprázdní při nedostatku paměti; podle dostupných informací to sice byla pravda v iOS 2, ale od verze 3 nahoru by mělo chování cache třídy UIImage fungovat korektně.

Mimochodem – když už máme obrázek na pozadí hlavního rámce, bylo by hezké, aby se týž obrázek, ale zrcadlově převrácený, objevil také na pozadí rámce "FlipsideView", v němž máme předvolby. Je asi zřejmé, kterak toho můžeme docílit – všechny tři obrázky zrcadlově převrátíme (na to nepotřebujeme žádný Photoshop, to umí i Preview) a uložíme do aplikace. Z rámce v objektové síti "FlipsideView.xib" uděláme UIImageView přesně týmž postupem, jaký jsme použili pro hlavní rámec. A obrázky načteme podle potřeby.

Pokud ovšem pracujeme v iOS 4, máme k dispozici daleko elegantnější řešení – zde úplně stačí v metodě viewDidLoad řídicího objektu rámce použít tento hezký trik:

//  FlipsideViewController.m
...
-(void)viewDidLoad {
  [super viewDidLoad];
  self.imageView.image=
    [UIImage
      imageWithCGImage:delegate.imageView.image.CGImage
      scale:1 orientation:UIImageOrientationUpMirrored];
  units.selectedSegmentIndex=
    [[NSUserDefaults standardUserDefaults]
      boolForKey:@"ImperialUnits"]?1:0;
  int hc=[[NSUserDefaults standardUserDefaults]
    integerForKey:@"HistoryCapacity"];
  [capacity selectRow:hc/10 inComponent:0 animated:NO];
  [capacity selectRow:hc%10 inComponent:1 animated:NO];
}
...

"Autosizing"

Jednou z šikovných vlastností rámců Cocoa je to, že dokáží při změně vlastní velikosti automaticky upravit rozmístění a velikost podřízených rámců, aniž bychom kvůli tomu museli programovat; stačí jen správně nastavit jejich atributy v Interface Builderu; vysvětlili jsme si to v našem seriálu už před šesti lety v odstavci "Změna velikosti objektů". Konkrétní vzhled ovladače v inspektoru nového Interface Builderu se poněkud změnil (podle mého názoru k horšímu, současné čárky jsou méně intuitivní, než bývaly "pružiny a dráty" starého), ale zato je k dispozici dynamický náhled, který ukazuje, jak se při změnách velikosti bude rámec chovat, takže v praxi to nevadí.

Ale proč se tím vůbec tady zabýváme, vždyť iPhone má obrazovku pevné velikosti, žádná okna s možností změn se nepoužívají, takže nás to vůbec nezajímá a můžeme se na "autosizing" vykašlat, ne?

Ne.

iPhone sice má obrazovku pevné velikosti, ale oblast, již z ní má k dispozici aplikace, se mění: pokud je zrovna aktivní telefonní hovor, při horním okraji je vyhrazen širší stavový pruh, a všechny rámce v aplikaci se odpovídajícím způsobem zmenší. S tím je nutno počítat kdykoli; simulátor na to dokonce nabízí službu "Toggle in-call status bar", díky níž si to můžeme vyzkoušet.

Kromě toho bychom u většiny aplikací měli podporovat možnost změny orientace a práce po otočení o 90 º; náš "bouřkoměr" je zde zrovna trochu výjimka v tom směru, že pro něj to opravdu není zapotřebí (ale v některém z příštích dílů si ukážeme, jak na to). U mnoha rámců se může ukázat, že pro jejich presentaci "na výšku" i "na šířku" není třeba nic přeprogramovávat – stačí jen správně nastavit "autosizing" (samozřejmě, u mnoha jiných tomu tak není, a musíme programově "přeskládat" GUI tak, aby to, co máme při pohledu na výšku nad sebou, bylo po pootočení vedle sebe).

Vraťme se k "bouřkoměru": zde se musíme postarat především o to, aby tlačítka při dolním okraji obrazovky zůstala i při aktivním hovoru skutečně při dolním okraji (a nesklouzla pod něj), nějak takto:

U ostatních objektů je vhodné upravit automatické změny velikosti také, hlavně proto, aby se vzájemně nepřekrývaly: u větších vnořených rámců bychom tak nastavili zvětšování/zmenšování (aktivací "šipek" uvnitř čtverečku) apod.

A ještě pár obrázků

Už jsme s aplikací skoro hotovi, ale pořád zbývají dvě důležité drobnosti.

Prvou z nich je aplikační ikona: je asi zřejmé, proč se bez ní nemůžeme obejít.

Její instalace je extrémně jednoduchá: vytvoříme prostě v libovolném grafickém programu vhodný obrázek velikosti 57×57 pixelů, pojmenujeme jej "Icon.png" a uložíme jej do projektu ve formátu PNG. V Xcode jej vložíme do cíle, aby se při jeho sestavení automaticky zkopíroval do složky "Resources" – a to je vše.

Až se později budeme zabývat iPadem a podporou více různých zařízení, uvidíme, že existuje podpora pro více různých a libovolně pojmenovaných ikon; prozatím se tím ale nemusíme zabývat. Za zmínku ale stojí to, že není třeba, abychom se snažili o standardní vzhled ikony iPhone s jejím "nasvícením shora" – to k našemu obrázku systém doplní automaticky předtím, než jej uloží do hlavní obrazovky. Naopak ale pokud to uděláme, můžeme dodatečné zpracování vypnout tak, že do souboru Info.plist přidáme položku

<key>UIPrerenderedIcon</key>
<true/>

ve specializovaném editoru Xcode ji máme k dispozici jako "Icon already includes gloss effects".

Kromě toho je zapotřebí sestavit a vložit do aplikace ještě jeden obrázek; jeho standardní jméno je "Default.png" a musí mít rozměry obrazovky (stejně jako u ikony, existuje způsob, jak uložit do aplikace různě pojmenované varianty pro různá zařízení, ale zatím nás to nemusí zajímat). iPhone v okamžiku spuštění aplikace přepkryje obrazovku tímto obrázkem, takže uživatel má pocit, že se hned něco děje – a po skutečné inicializaci (jež nějaký čas zabere), je obrázek nahrazen skutečným UI aplikace. Jde tedy vlastně o "švindl", který v uživateli vzbuzuje dojem, že se aplikace aktivují rychleji, než tomu je ve skutečnosti.

V našem případě je volba zřejmá – použijeme samozřejmě týž obrázek, který ukládáme na pozadí hlavního rámce.

Tím máme prvou, jednoduchoučkou verzi aplikace skutečně hotovou, a je na čase ji vyzkoušet nejen v simulátoru, ale také na skutečném zařízení: o tom si budeme povídat 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  

Diskuse k článku

 

Vložit nový příspěvek   Sbalit příspěvky

 

Ikona

Autor: PH Muž

Založeno: 22.09.2010, 08:00
Odpovědí: 0

"prozatím se tím ale nemusíme zabývat"

Pro distribuci je nutné mít i ikonu o velikosti 114x114.

Odpovědět na příspěvek

RE: Ikona

Autor: OC Muž

Založeno: 22.09.2010, 14:28

Appstore chce velkou ikonu 512x512, ale ta nemusi byt soucasti projektu a uz vubec ne v Info.plist. Na Appstore ale mame zatim casu dost; priste se budeme zabyvat tim, jak aplikaci dostat do vlastniho iPhone, coz bohuzel zdaleka neni trivialni.

Odpovědět na příspěvek

RE: RE: Ikona

Autor: PH Muž

Založeno: 22.09.2010, 14:33

То jsem nemyslel. Chce to ikonu pro iPhone 4. Jinak to po uploadu (přes Application Uploader) v iTunes Connect místo "Upload received" ukazuje červenou tečku a hlášku "Invalid binary" (bez bližšího upřesnění). Krátce po uvedení iOS 4 to ještě ikony pro retinu nevyžadovalo, ale teď už to neprojde. Takže v Info.plist opravdu ikona o velikosti 114x114 být musí.

Odpovědět na příspěvek

RE: RE: RE: Ikona

Autor: OC Muž

Založeno: 22.09.2010, 18:06

(i) jak píši: "na Appstore ale mame zatim casu dost" :)
(ii) nicméně, naposledy jsme aplikaci uploadovali 13. t. m., a to bez speciální ikony pro iP4. A bez problémů. Takže je-li Vaše zkušenost novější, je vskutku možné, že tam Applovci mezitím něco poprasili; je-li však Vaše zkušenost starší, bude to něco ve Vašem nastavení.

Odpovědět na příspěvek

RE: RE: RE: RE: Ikona

Autor: PH Muž

Založeno: 22.09.2010, 18:17

Moje zkušenost je že včera.

Odpovědět na příspěvek

chyba v setter @property

Autor: Pajlo Muž

Založeno: 29.10.2010, 14:55
Odpovědí: 0

Sice mi to trvalo, ale na konci setter-u chyba dvojbodka. Takze funguje mi to az takto:


@property (nonatomic, retain, getter=view, setter=setView:) UIImageView *imageView;

Odpovědět na příspěvek

RE: chyba v setter @property

Autor: OC Muž

Založeno: 29.10.2010, 15:56

To je nějaká prasečina v LLVM; prozatím bych se tomu vyhýbal, podle mne je to beta, ač to tak Apple neinzeruje :)

V normálním překladači je to zcela bez problémů:


130 /tmp> #import
@interfa
ce Foo:NSObject
@property (setter=bezDvojteckyVPoho
de) int foo;
@end
@implementati
on Foo
@synthesize foo;
@end
int main() {
Foo *foo;
[foo bezDvojteckyVPohode:666];

NSLog(@"Nima problema: %u",foo.foo);
return 0;
}
131 /tmp> cc -Wall -framework Cocoa q.m && ./a.out
2010-10-29 15:54:42.986 a.out[33094:903] Nima problema: 0
132 /tmp> cc --version
i686-apple-dar
win10-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2333.4)
Copyright (C) 2007 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

133 /tmp>

Odpovědět na příspěvek

RE: RE: chyba v setter @property

Autor: OC Muž

Založeno: 29.10.2010, 16:00

Ježiš pardon, já omylem zkopíroval z Terminálu první versi s chybou. Mea culpa. Tady je ta finální bez chyby:

137 /tmp> #import
@interfa
ce Foo:NSObject
@property (setter=bezDvojteckyVPoho
de) int foo;
@end
@implementati
on Foo
@synthesize foo;
@end
int main() {
Foo *foo=[Foo new];
[foo bezDvojteckyVPohode:666];

NSLog(@"Nima problema: %u",foo.foo);
return 0;
}
138 /tmp> cc -Wall -framework Cocoa q.m && ./a.out
2010-10-29 15:55:12.162 a.out[33137:903] Nima problema: 666
139 /tmp>

Odpovědět na příspěvek

RE: RE: RE: chyba v setter @property

Autor: Pajlo Muž

Založeno: 30.10.2010, 18:01

No neviem, ja som v tomto len zaciatocnik a nemam s tym moc skusenosti. Ale ci uz prepnem v XCode do LLVM alebo GCC4.2, tu dvojbodku tam proste dat musim inac to hadze Error. (XCode 3.2.4)

A aby zmizli pri kompilovani WARNING-y (ja ich fakt nemam rad ;-) ) tak som musel pridat aj:

-(UIImageView*)vie
w {
return (UIImageView*)[super view];
}

-(void)setVi
ew:(UIImageView *)newView
{
[super setView:newView];
}

Odpovědět na příspěvek

RE: RE: RE: RE: chyba v setter @property

Autor: PH Muž

Založeno: 30.10.2010, 18:13

Nezmizly by ty warningy použitím @dynamic?

Odpovědět na příspěvek

RE: RE: RE: RE: RE: chyba v setter @property

Autor: OC Muž

Založeno: 30.10.2010, 18:31

Jak celkem explicitně dokazuje můj příklad nahoře, dvojtečka není v GCC povinná v aktuálním překladači, 4.2.1 :) Čtyřka ji vyžadovala, to je fakt.

@dynamic by sice vskutku odstranilo warning, avšak patrně nikoli problém -- nejsou-li accessory implementovány nějakým exotickým způsobem (přesměrování zpráv apod.), je spíše potřeba je implementovat. Buď ručně, nebo pomocí direktivy @synthesize, jako v mém příkladu...

Odpovědět na příspěvek

RE: RE: RE: RE: RE: RE: chyba v setter @property

Autor: OC Muž

Založeno: 30.10.2010, 18:34

Aha, pardon já už si nepamatoval, o čem jde řeč :)

Jo, tady by skutečně @dynamic mělo stačit... ale také by nemělo být ani potřeba, protože ty accessory _jsou_ implementované (zděděné), a překladač to ví.

Divné. Až budu mít volnou chvíli, ještě na to juknu, co se to tam děje.

Odpovědět na příspěvek

RE: RE: RE: RE: RE: RE: RE: chyba v setter @property

Autor: Pajlo Muž

Založeno: 30.10.2010, 20:57

Ano, aj @dynamic pomohlo.

Odpovědět na příspěvek

hezky trik

Autor: Ludvík Muž

Založeno: 10.01.2011, 20:06
Odpovědí: 0

Zdravim.
Vse je ok, az na implementaci hezkeho triku, kdy se prirazuje zrcadlove otoceny obrazek do flipsideview. Haze mi to error: request for member image in something not a structure or union.

Nelibi se mu delegate.imageView.image.
CGImage

Kdyz obrazek prirazuji primo pres imageNamed, tak to funguje. Jako by delegat neodkazoval na mainview.

Co asi delam spatne?

Odpovědět na příspěvek

RE: hezky trik

Autor: OC Muž

Založeno: 11.01.2011, 02:14

Podívejte se na deklarace -- toto by se např. stalo, kdyby byl delegate deklarován jako "id".

Můžete to zkusit také nahradit [[[delegate imageView] image] CGImage] -- to by mělo fungovat korektně při víceméně libovolné deklaraci*, pod podmínkou samozřejmě, že "delegate" obsahuje správnou hodnotu (což by mělo).
___
* Ponecháme-li stranou extrémy typu "delegate deklarovaný jako struct" apod :)

Odpovědět na příspěvek

RE: RE: hezky trik

Autor: Ludvík Muž

Založeno: 11.01.2011, 14:18

dekuji, je to tak, delegat je deklarovan jako:

id legate> delegate;

Pokud pouziji [[[delegate imageView] image] CGImage], tak chyba opravdu zmizi, ale zase se objevily 2 warnings:

1. -imageView not found in protocol
2. Passing argument 1 of setImage: from incompatible pointer type

Obsah (temer cely automaticky vytvoreny) FlipsideViewController.h je nasledujici:

#import

@proto
col FlipsideViewControllerDel
egate;

@interface FlipsideViewController : UIViewController
erViewDelegate, UIPickerViewDataSource> {
id legate> delegate;
IBOutlet UISegmentedControl *units;
IBOutlet UIPickerView *capacity;
}

@propert
y (nonatomic, assign) id legate> delegate;
@property (nonatomic,retain,getter=
view,setter=setView) UIImageView *imageView;
- (IBAction)done:(id)sender
;
- (IBAction)unitsChanged;

@end

@protocol FlipsideViewControllerDel
egate
- (void)flipsideViewControl
lerDidFinish:(FlipsideVie
wController *)controller;
@end

Odpovědět na příspěvek

RE: RE: RE: hezky trik

Autor: OC Muž

Založeno: 11.01.2011, 22:20

No, to je ono -- v protokolu FlipsideViewControllerDel
egate nikde není property "imageView", takže není divu, že o ní překladač neví.

Má-li být delegát deklarován jako "id", pak v tom protokolu musí být všechny metody a properties, jež se u toho delegáta fakticky používají (nebo je třeba jej v místě použití patřičně přetypovat).

> Passing argument 1 of setImage: from incompatible pointer type

Toto je podezřelé -- jak u Vás vypadá celý příkaz?

Odpovědět na příspěvek

RE: RE: RE: RE: hezky trik

Autor: Ludvík Muž

Založeno: 14.01.2011, 10:36

Zdravim,

tak jsem to konecne vyresil. I kdyz jsem to pretypovaval, tak mi to hlasilo ruzne warnings. Zahrnul jsem tedy do protokolu svou metodu getMainImage, (podobne jako tlacitko Done ve flipsideviewcontroller), ktera vola tuto metodu v mainview a ta mi vraci referenci na objekt imageview. Pak uz to funguje dobre.

Snad je muj postup spravny.

Jsem vycerpan, ale stasten. ;)

Dekuji za pomoc. Nez tuto serii dokoncim, urcite se tady jeste ozvu. :)

Odpovědět na příspěvek

RE: RE: RE: RE: RE: hezky trik

Autor: Zdeněk Muž

Založeno: 29.03.2011, 01:31

To Ludvík: Já jsem na to prostě nepřišel ... mohu požádat o nápovědu? Díky, Zdeněk

Odpovědět na příspěvek

 

 

Vložit nový příspěvek

Jméno:

Pohlaví:

,

E-mail:

Předmět:

Příspěvek:

 

Kontrola:

Do spodního pole opište z obrázku 5 znaků:

Kód pro ověření

 

 

 

 

 

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

Uživatelské jméno:

Heslo: