Platform lehetőségek kutatása: iOS A Mobil multimédiás kliens fejlesztői eszközkészlet létrehozása című kutatás-fejlesztési projekthez
Tartalomjegyzék 1 Bevezetés ................................................................................................................................ 3 2 Objective-C ............................................................................................................................ 3 2.1 ARC ................................................................................................................................ 4 2.2 Konstruktorok ................................................................................................................. 4 2.3 Metódusok, üzenetek ...................................................................................................... 5 2.4 Interfészek és implementációk ....................................................................................... 5 2.4.1 Interfészek ............................................................................................................... 5 2.4.2 Implementációk ...................................................................................................... 6 2.5 Példányosítás .................................................................................................................. 6 2.6 Kategóriák és kiterjesztések (categories, extensions) .................................................... 7 2.6.1 Kategóriák ............................................................................................................... 7 2.6.2 Extensions .............................................................................................................. 8 2.7 Protokollok .................................................................................................................... 9 2.8 SEL és @selector ............................................................................................................ 9 2.9 Delegált fejlesztési minta (delegate design pattern) ..................................................... 10 3 iOS ........................................................................................................................................ 11 3.1 Gyári alkalmazások ...................................................................................................... 12 3.2 Liszensz politika ........................................................................................................... 12 3.3 Apple elvárások AppStore-ba töltéshez ....................................................................... 12 4 Bevezetés az iOS platform programozásába ........................................................................ 13 4.1 XCode tippek ................................................................................................................ 13 4.1.1 #pragma mark ....................................................................................................... 13 4.1.2 Analyze ................................................................................................................. 14 4.1.3 Hivatkozások (included by) .................................................................................. 14 4.1.4 Outlet-ek, action-ök ............................................................................................. 15 4.2 iPhone emulátor ............................................................................................................ 15 4.3 Compile, link és beállításaik ......................................................................................... 16 4.4 Felhasználói felület ....................................................................................................... 16 4.5 Webszervizek................................................................................................................ 17 4.6 Alkalmazás logika ........................................................................................................ 17 4.7 Képernyők, hogyanok és miértek ................................................................................. 18 4.7.1 Navigation Controller ........................................................................................... 18 4.7.2 Induló képernyő .................................................................................................... 18 4.7.3 Az UIApplicationDelegate.................................................................................... 18 4.7.4 Storyboard ............................................................................................................. 20 4.7.5 Splash screen ......................................................................................................... 21 4.7.6 Táblázatok, ScrollView elem ................................................................................ 22 4.7.7 Touch Event kezelése ........................................................................................... 28 5 Források ................................................................................................................................ 30
1 Bevezetés A dokumentum célja az iOS alkalmazások fejlesztési módszereinek, lehetőségeinek kiderítése, felkutatása, vizsgálat. iOS rendszerbe Objective-C nyelven lehet alkalmazásokat fejleszteni.
2 Objective-C Az Objective-C egy teljesen objektum-orientált kibővített változata a C programozási nyelvnek. A tervezők a C nyelvhez társították a Smalltalk stílusú üzenetközvetítést az objektumok között. Az 1980-as években a strukturált programozás volt a legelterjedtebb programozási modell. Előnye az volt, hogy a programokat kisebb modulokra lehetett bontani, így olvashatóbbak voltak. Nagy hátránya az volt, hogy a kód újrafelhasználása szinte nem létezett, valamint a nagy programok megint csak olvashatatlanok voltak. Hogy ezt kiküszöböljék, a XEROX Palo Alto-i kutatóközpontjában kifejlesztették a Smalltalk nyelvet, amely objektum-orientált volt. A Smalltalk nagy hátránya, hogy a programot egy Virtuális gép (VM) futtatja, emiatt nagyon nagy a memóriafelhasználása és sokkal lassúbb is, mint egy natívan futó program. Az Objective-C nyelvet Brad Cox és Tom Love fejlesztette ki az 1980-as évek elején. Mikor Steve Jobs elhagyta az Apple-t és megalapította a NeXT céget, megvásárolta az Objective-C licencét, ami lehetővé tette, hogy a NeXTstep operációs rendszert és minden fejlesztői alkalmazást Objective-C-ben írjanak meg. A NeXTstep ezáltal a kor legelőrehaladottabb operációs rendszere volt. A GNU projekt 1992-ben megjelentette a NeXTstep ingyenes klónját, az OpenStep operációs rendszert, amely már tartalmazott egy Objective-C fordítót és könyvtárakat, az gnuc-obj-t. Mikor az Apple 1996-ban megvásárolta a NeXT-et, a Mac OS X alapjául az OpenStep-et vették. Az OS tartalmazta az Objective-C-t és a NeXT fejlesztői rendszerét, a Project Buildert (amelyből később az Xcode lett), valamint az Interface Builder-t. Ezek a programok a mai napig a Mac OS X legfontosabb fejlesztői eszközei. A Cocoa API az Objective-C osztályokra támaszkodik és a legelterjedtebb Objective-C környezet a mai napig. A nyelv egy nagyon vékony réteg a C nyelv fölött (csakis C és nem C++ kiterjesztés). Bármilyen C program lefordítható Objective-C fordítóval. Az Objective-C szintaxisa a Smalltalkból ered. Minden nem objektum-orientált művelet (változók, preprocesszálás, függvény deklarációk, függvényhívások) egyezik a C-ben használtakkal, míg az objektum-orientált része a Smalltalk üzenetküldő mechanizmusának egyik változata. Az Objective-C objektum-orientált modellje az objektumpéldányok közti üzenetküldésen alapul. Ez teljesen eltér a C++ stílusú programozási nyelvektől: itt nem az objektum egyik metódusát hívjuk meg, hanem egy üzenetet küldünk az objektumnak és itt van a lényeges
eltérés. A Simula stílusú nyelvekben a metódus neve a fordító által pontosan meg van határozva a kódszegmensben, de a Smalltalkban és Objective-C-ben az üzenet csak egy név és futásidő alatt lesz meghatározva a tárbeli címe: a fogadó objektum kell feldolgozza a kapott üzenetet. Ebből következik, hogy az üzenet küldésekor nincs típusellenőrzés, és nincs rá garancia, hogy az objektum válaszolni is fog az üzenetre. Ha nem tudja feldolgozni, akkor egyszerűen egy nil pointert ad vissza. C++ kód egy objektum metódusának meghívására: foo->bar (parameter);
Ugyanez Objective-C nyelvben: [foo bar:parameter];
Mindkét stílusnak megvan az előnye és hátránya. A Simula OOP-ben többszörös öröklődés és kisebb futásidő lehetséges, de alapértelmezetten nincs dinamikus kötés. A Smalltalk OOP lehetővé teszi, hogy az üzenetek ne legyenek implementálva. A Cocoa platform ezt kihasználja maximálisan: minden objektumnak a platform inicializáláskor elküldi az 'awakeFromNib:' üzenetet amint a program elindul, és erre az objektumok inicializálódhatnak. Ugyanakkor nem kötelező definiálni egy objektumot az üzenet küldésekor a kódban, dinamikus kötéssel a függőségeket a runtime oldja meg. Egy Objective-C üzenet feldolgozása legalább háromszor annyi időbe telik, mint egy virtuális C++ metódushívás. Objective-c kódok készíteni majd futtatni Mac OSX operációs rendszeren az XCode nevű fejlesztőeszközzel szoktak. Az Xcode egy iPhone/iPad szimulátor is tartalmaz, ami nagyjából mindent tud emulálni, tehát az iPhone alkalmazás fejlesztése a szimulátoron végrehajtható, de azért a teszteléshez szükséges egy iPhone. A következő kis fejezetekben azokat a tulajdonságokat vesszük sorra melyek egyediek, eltérnek más nyelvektől.
2.1 ARC A cím az automatic reference counting rövidítése. C nyelvben minden memória foglalás felszabadításáról a programozónak kell gondoskodnia. Java-ban a JVM garbage collector folyamata gondoskodik erről. Amikor egy új objective-c projektet hozunk létre XCode-ban nyilatkoznunk kell, hogy ARC-s vagy ARC nélküli a projekt. ARC nélküli esetben minden alloc-ot valahol követnie kell egy dealloc-nak. ARC-s projektben nem tudunk dealloc hívást írni mert az XCode hibának jelzi. Tehát az ARC-s Objective-c közel olyan kényelmes memória használat szempontjából, mint a java.
2.2 Konstruktorok A szokásos objektum orientált paradigmát ismerők első kérdései között szerepel, hogy miként lehet konstruktort készíteni. A válasz egyszerű, minden olyan metódus, mely az adott osztály egy példányára mutató mutatót adja vissza az konstruktor, a következő példában a WSLogin osztály konstruktora ki van emelve: @interface WSLogin : NSObject -(WSLogin*) initWithDelegate: (id<WSLoginDelegate>) _del;
-(void) login; @property (weak) id<WSLoginDelegate> delegate; @end
Az, hogy mutató, onnan látszik, hogy van utána egy csillag. Ha ezt lehagyjuk, az XCode jelzi is. Az iOS gyári osztályai általában id típust adnak vissza a konstruktorok. Az id az tulajdonképpen egy olyan mutató mely bármilyen más osztály mutatóját tartalmazhatja. Tehát az id után nem kell csillag, mert az eleve mutató.
2.3 Metódusok, üzenetek Az Objectiv-C egyik furcsasága például a java-val szemben, hogy míg java esetén már fordítási időben kiderülnek a metódus eltérések, hiányok, típus eltérések, addig Objective-C esetén még futásidőben sem. Mivel az Objective-C nem metódusokban gondolkodik, hanem üzenetekben, ezért ha egy objektumnak küldött üzenettel az objektum nem tud mit kezdeni (vagyis nincs olyan metódusa), egyszerűen egy szó nélkül nil-t ad vissza és a program futása tovább folytatódik.
2.4 Interfészek és implementációk Az Objective-C nyelvben egy osztály interfésze és implementációja különálló kód-tömbökben kell legyen. Megegyezés szerint az interfészt egy header fájlban (általában .h kiterjesztéssel) míg az implementációt egy kód fájlban helyezzük el (általában .m kiterjesztéssel). A "h" fájlban vannak azok az információk, hogy a külvilág mit lát az osztályból, ez az interfész definíció. Az "m" fájlban pedig az interfészt megvalósító kód van, és persze lehetnek a külvilág számára láthatatlan metódusok is.
2.4.1
Interfészek
Az interfész általában a header fájlban van leírva. Szokványos, hogy a fájl neve az osztály neve is egyúttal. Ha van egy Foo osztályunk, akkor ezt általában a Foo.h fájlba tesszük. Példa egy interfész leírásra: @interface osztálynév : ősosztálynév { // változók } -(típus)példányMetódus1:(típus)paraméter1_neve :(típus)paraméter2_neve; -(típus)példányMetódus2Paraméter1:(típus)paraméter1_neve andParaméter2:(típus)paraméter2_neve; +(típus)osztályMetódus1; +(típus)osztályMetódus2; +(típus)osztályMetódus3:(típus)paraméter_neve; @end
A '+' azt jelenti, hogy osztálymetódus, a '-' hogy példány metódusa. Az osztálymetódusok olyanok mint a static metódusok java-ban. Megfigyelhetjük, hogy a példányMetódus2Paraméter1 mutatja az Objective-C egyik különlegességét, hogy a paramétereket nevesíteni lehet a függvény nevében (ilyet még csak
Oracle PL/SQL-ben láttam). És szokványos az is, hogy a második paramétertől kezdve "and" szócskával kezdődnek a paraméterek nevei, mintha egy kis mondat lenne az egész metódus hívós sor. Tehát a második paraméter így néz ki: andParaméter2:(típus)paraméter2_neve
A metódus hívásakor ezt írjuk: andParaméter2:var2
Ahol var2 az ott helyben létező típus típusú változó. Tehát a metódust hívó az andParaméter2-vel hivatkozik, a metódus megvalósító kód pedig a paraméter2_neve változóban éri el a kapott paramétert. A visszatérési típusok lehetnek bármilyen standard C típusok, egy pointer egy Objective-C objektumra, vagy egy specifikus objektumra mutató pointer (NSArray *, NSImage *, NSString *). A metódusok paraméterei kettősponttal kezdődnek, utána következik a típus majd a neve. Sok esetben érdemes megnevezni a paramétereket: -(void) setRange:(int)start :(int)end; -(void) importDocumentWithName:(NSString *)name withSpecifiedPreferences:(Preferences *)prefs beforePage:(int)insertPage
2.4.2
Implementációk
Az interfész csak a metódusok és változók neveit deklarálja; maga a kód az implementációban létezik. Általában .m kiterjesztésű fájlokba tesszük. @implementation classname +(típus)osztályMetódus { // implementáció } -(típus)példányMetódus { // implementáció } @end
Itt egy példa a paraméterek nevesítésére és használatára: -(int)changeColorToRed:(float)red green:(float)green blue:(float)blue; [myColor changeColorToRed:5.0 green:2.0 blue:6.0];
2.5 Példányosítás Az Objective-C osztályokat két lépésben példányosítjuk: először lefoglaljuk a memóriát az új objektum számára majd inicializáljuk. Egy objektum addig nem működőképes, amíg mindkét lépést nem végeztük el. Ezeket általában egyetlen sorban meg lehet oldani: MyObject *o = [[MyObject alloc] init];
Az alloc hívás elegendő memóriát foglal le, felülírásra még nem láttam példát, nem is szokás, az init hívás felülírható, hogy a változókat inicializálhassuk:
-(id) init { self = [super init]; if (self) { ivar1 = value1; ivar2 = value2; } return self; }
Objective-C nyelvben a self az ugyanaz mint a java-ban a this. Az if (self) azt vizsgálja, hogy az objektum létrejött-e, vagyis hogy az self != nil. A nil a java null megfelelője. Figyeljük meg, hogy a metódus self-el tér vissza ezért azonnal hívható egy másik metódusa az osztálynak, például feltételezve, hogy van egy loginWithUser metódusa is a MyObject osztálynak: MyObject *o = [[[MyObject alloc] init] loginWithUser:user andPassword:password];
2.6 Kategóriák és kiterjesztések (categories, extensions) A kategória lehetővé teszi, hogy metódusokat adjunk hozzá egy meglévő osztályhoz, még akkor is ha az eredeti osztály forráskódja nem áll rendelkezésre. A kategóriák kitűnő lehetőséget biztosítanak a meglévő osztályok kibővítésére leszármazott osztály készítése nélkül. Az extensions hasonló, de megengedi más APIk deklarációját.
2.6.1
Kategóriák
Új metódust az interfész fájlban egy új kategória névvel adhatjuk meg és az implementációs fájlban is ezzel a kategória névvel kell hivatkozni. A kategóriából tudható hogy a metódus utólag lett az osztályhoz adva nem pedig leszármazott osztály. Viszont új példány változót nem lehet a kategóriával az osztályhoz adni. A kategóriában lévő metódusok az osztály részévé válnak. Például, ha metódust adunk az NSArray osztályhoz egy kategóriában, akkor a fordító feltételezi, hogy minden NSArray példányban benne lesz ez a metódus. Ha származtatott osztályt készítenénk a kategória helyett, akkor az eredeti NSArray példányokban nem lenne benne a leszármazottban lévő metódus, természetesen. A kategória metódusok mindent megtehetnek amit az osztály normál metódusai is. Futásidőben nincs különbség. A kategóriában definiált metódusok az kiterjesztett osztályból származtatott osztályban is használhatók lesznek. A kategória deklaráció nagyon hasonlít az osztály interfész definícióra kivéve, hogy a kategória név zárójelek között szerepel az osztály neve után és ősosztály nem szerepel. Ha ezek a metódusok használnak példány változókat, akkor szerepelnie kell a kiterjesztendő osztály interfész importjának: #import "ClassName.h"
@interface ClassName ( CategoryName ) // method declarations @end
A kategória nem deklarálhat újabb példány változókat csakis kizárólag metódusokat. A kiterjesztendő osztály minden példány változója ugyanúgy elérhető a kategóriában mint a kiterjesztendő osztályban még a @private részben definiáltak is. Nincs limitálva az egy osztályhoz létrehozható kategóriák száma, csak annyi a megkötés, hogy mindegyik nevének el kell térnie.
2.6.2
Extensions
Az extensions olyan kb. mint egy névtelen kategória, kivéve hogy a metódusok melyeket deklarál a fő @implementation blokkban kell megvalósítani. Felül lehet definiálni példány változók láthatóságát is akár. Az extensions többnyire az implementációs fájlban kerül megvalósításra. Az interfész fájlban (.h): @interface MyClass : NSObject @property (retain, readonly) float value; @end
Az implementációs fájlban (.m): @interface MyClass () @property (retain, readwrite) float value; @end
A kategóriákkal ellentétben itt nincs név a zárójelek között. Osztály extensions lehetővé teszi, hogy deklaráljunk kötelezően megvalósítandó metódusokat, az interfész fájlban (.h): @interface MyClass : NSObject - (float)value; @end
Az implementációs fájlban (.m): @interface MyClass () { float value; } - (void)setValue:(float)newValue; @end @implementation MyClass - (float)value { return value; } - (void)setValue:(float)newValue { value = newValue; } @end
A setValue metódus megvalósításának a fő @implementation blokkban kell lennie, nem lehet úgy implementálni mintha kategóriában lenne.
2.7 Protokollok A protokollok lehetővé teszik a többszörös öröklődést. Kétféle protokoll van: ad-hoc vagy informális és fordító által szabályozott, vagy formális protokollok. Egy informális protokoll egyszerűen egy metódus lista, amit az osztály implementálhat. A dokumentációban létezik, mivel a nyelvben nem jelenik meg. Ilyenek például az opcionális metódusok, ahol a metódus implementálása az osztály teljes működését befolyásolja. A formális protokoll hasonló a Java interface típusaihoz. Ez egy metódus lista amelyet az osztály felsorol implementálás céljából. A 2.0 Objective-C változatok előtt minden felsorolt metódust implementálni kellett, máskülönben a fordító hibát jelzett, ha az osztály nem implementálta minden metódusát a felsorolt protokollokban. Ez már nem kötelezö a 2.0 változattól kezdődően. A leglényegesebb különbség a Java nyelvvel szemben, hogy egy osztály implementálhatja a protokollt anélkül, hogy deklarálta volna. A különbség a kódon kívül nem látható. Szintaxis: @protocol Locking - (void)lock; - (void)unlock; @end
Ez a protokoll a lock műveletet implementálja, amelyet a következő osztály felhasznál: @interface Osztaly : OsOsztaly
@end
Ez azt jelenti, hogy az Osztaly implementálni fogja a két metódust a Locking protokollból, ahogyan akarja. Lehetőség van arra, hogy kötelezővé tegyük a protokollt implementálni szándékozó osztályok részére bizonyos metódusok megvalósítását. Erre a @required módosító ad lehetőséget: @protocol Locking @required - (void)lock; - (void)unlock; @end
2.8 SEL és @selector Minden metódus azonosítható egy selector-ral. A metódus nevével lehet selector-t létrehozni, C logikával a selector tulajdonképpen egy metódusra mutató mutató. Egy ilyen mutatóra az Objective-C nyelvben van egy speciális típus: SEL: SEL setWidthHeight;
Egy ilyen változónak a speciális @selector hívással adhatunk értéket:
setWidthHeight = @selector(setWidth:height:);
Vagy akár string-et is megadhatunk az NSSelectorFromString metódusnak: NSString *aBuffer = @'metodusnev'; setWidthHeight = NSSelectorFromString(aBuffer);
Vagy meg is hívhatunk egy metódust: [obj1 performSelector:@selector(gossipAbout:) withObject:objx];
Ami teljesen megegyezik a következő hívással: [obj1 gossipAbout:objx];
Sőt, ellenőrizni is tudjuk, hogy létezik-e egy metódus: if ( [obj1 respondsToSelector:@selector(gossipAbout:)] ) ...
2.9 Delegált fejlesztési minta (delegate design pattern) Más programozási környezetből érkezőknek (java, c, c++, delphi) ez a legfurcsább dolog az iOS fejlesztésben. A lényegét a következők írják le:
szeretnénk egy osztálynak meghívni egy metódusát (üzenetet küldünk az osztálynak), de a meghívott metódus eredményét nem a visszatérési értékében kapjuk meg, hanem a hívó osztály egy metódusát visszahívja a meghívott
a hívó osztálynak tehát lesz egy metódusa amit a hívott osztály meghív, ezt hívják úgy, hogy adott protokollt implementál a hívó
a hívott osztálynak kell ismernie azt a példányt (a hívót), aki az adott protokollt implementálja, hogy vissza tudja hívni
Nézzünk egy példát, mert így talán túl elméleti. Van egy osztály mely webszervizen keresztül képes bejelentkezni, legyen például WSLogin a neve. Ennek az interfész fájljában(h) van az a protokoll definiálva melyet a hívónak kell megvalósítania: @protocol WSLoginDelegate @required -(void) loginCompleted: (Session*) session; @end @interface WSLogin : NSObject -(WSLogin*) initWithDelegate: (id<WSLoginDelegate>) _del; -(void) login; @property (weak) id<WSLoginDelegate> delegate; @end
Tehát, a WSLogin osztály konstruktora vár egy paramétert melynek a neve _del, a típusa pedig id<WSLoginDelegate>, vagyis bármilyen osztály mely megvalósítja ezt a protokollt. A következő interfész példában látjuk, hogyan is lehet ezt megadni Objective-c-ben. @interface PGAppDelegate : UIResponder -(void) loginCompleted: (Session*) session;
@end
Tehát a PGAppDelegate osztály megvalósítja a WSLoginDelegate protokollt, ezért implementálja a loginCompleted metódust. Még egy darabka szükséges, melyben a PGAppDelegate osztály meghívja a WSLogin osztály login metódusát, ezt a PGAppDelegate osztály valamelyik metódusába helyezzük el: [[[WSLogin alloc] initWithDelegate:self] login];
A folyamat a következő: 1. a PGAppDelegate létrehoz egy WSLogin példányt, a konstruktorban átadja saját magát (self) és egyben meg is hívja a login metódusát 2. a login metódus elindít egy háttérfolyamatot és azonnal a login metódus hívás utána soron folytatódik a végrehajtás 3. a háttérben futó folyamat amikor végez, meghívja a PGAppDelegate példány loginCompleted metódusát
3 iOS Az iPhone OS, OS X iPhone vagy iOS annak az operációs rendszernek a neve, amelyet az Apple Inc. fejlesztett ki az iPhone, iPod Touch és iPad készülékekre. Mint ahogyan a Mac OS X (amelyből származtatták), a Darwin alapokat használja. Az iPhone OS négy fő rétegből tevődik össze: Core OS, Core Services, Media és Cocoa Touch. A teljes operációs rendszer alig 240 MB helyet foglal a készülék adathordozóján. Az operációs rendszernek nem volt neve, amíg az első iPhone SDK meg nem jelent 2008. márciusában. Korábban az Apple csak annyit árult el, hogy „az iPhone OS X-et használ”. 2009. június 6-án már több, mint 50 000 alkalmazás volt elérhető az iPhone-ra. Az AppStore letöltések száma meghaladta az 1 milliárdot. Az iPhone OS felhasználói felülete a multi-touch technológiára alapuló direkt manipulációra alapul. Ez azt jelenti, hogy minden objektumot, mint a valós világban, kézzel mozgatunk, manipulálunk. A felhasználói felületben kapcsolók, gombok, csúszkák vannak. A felhasználó mozdulatai egy természetes interfészt biztosítanak. A készüléknek egy belső gravitációs gyorsulásmérője van, amely az X, Y és Z koordináták irányában eső gravitációs gyorsulást méri. Mikor a készüléket bekapcsolják, egy induló képernyő jelentkezik be ikonokkal és egy "dock" a képernyő alján. A képernyő felső részén a fontosabb információk láthatók: pontos idő, akkumulátor töltöttsége, jelerősség (telefonhálózat és Wi-Fi térerő). A képernyő többi része szabadon használható az alkalmazások által. Nincs meg a kilépés koncepciója, ehelyett vagy megvárjuk, hogy az alkalmazás magától befejeződjön, vagy megnyomjuk a "home" gombot, ami befejezi az alkalmazást. Multitask csak az iPhone belső processzeinek megengedett, a felhasználók alkalmazásai nem futhatnak a háttérben. Azonban több szálas alkalmazások futhatnak.
Az iPhone és iPod Touch fő processzora egy ARM processzor, ellentétben a Macintosh Intel vagy PowerPC architektúrájával. 3D grafikához OpenGL ES-t használ amelyet a Power VR videokártya biztosít. A Mac OS X alkalmazások nem másolhatóak rá egyenesen az iPhone-ra (még akkor sem, ha Cocoa alapúak), hanem specifikusan iPhone-ra kell őket átírni és lefordítani (ehhez az Xcode fejlesztőcsomagot használjuk). Az iPhone-on futó Safari böngésző webalkalmazások futtatására is alkalmas. Független fejlesztők az AppStore-on keresztül értékesíthetik az alkalmazásaikat.
3.1 Gyári alkalmazások A 4.0-ás verzióban a következő szoftverek vannak telepítve: Messages (SMS küldés-fogadás), Calendar, Photos, Camera, YouTube, FaceTime, Stocks, Maps, Weather, Clock, Calculator, Voice Memos, Notes, Settings, iTunes, App Store, Contacts, Game Center. Négy másik alkalmazás a lényegi funkcionalitását adja a készüléknek: Safari böngésző, Mail, Telefon funkció és az iPod. Az iPhone és iPod Touch közti különbség csak annyi, hogy hiányzik a telefon, SMS/MMS funkcionalitás. Az iPhone "iPod" alkalmazása zenét és videót is le tud játszani. 2007-ben az Apple a WWDC-n bejelentette, hogy az iPhone és iPod touch a Safari böngészőben webalkalmazások futtatását is lehetővé teszi, mint például az AJAX hívások.
3.2 Liszensz politika Az SDK ingyenesen letölthető, de ahhoz, hogy valaki közvetlenül az eszközön tudjon tesztelni, és szoftvert adjon ki, az iPhone Developer Program tagja kell legyen, amihez az Apple engedélye is szükséges. Minden programhoz egy kulcs is tartozik, amit csakis az Apple webapplikációin keresztül lehet generálni. Három módja van az alkalmazások feltöltésének:
az App Store révén
belső terjesztés a fejlesztő cégen belül
és egy Ad-hoc alapon, ami maximum 100 iPhone készülékre engedi feltölteni.
Ez a terjesztési modell lehetetlenné teszi a GPL licenccel gyártott alkalmazások terjesztését, mivel a kulcsokat nem terjesztheti tovább (ez az Apple tulajdona), így az esetleges változtatásokat nem lehet továbbvinni.
3.3 Apple elvárások AppStore-ba töltéshez Az AppStore "ellenőrök" sok mindent vizsgálnak egy programon mielőtt azt az AppStore-ban elérhetővé tehetjük. Például olyasmiket is kifogásolhatnak, hogy túl keveset tud a program, elüt az Apple minőségfilozófiájától. Ezek után el lehet képzelni, hogy milyen alapos az AppStore ellenőrző rendszer. Ha gördülékennyé akarjuk tenni az AppStore-ban történő megjelenést, akkor ezen oldalon található információkat tanulmányozzuk alaposan: https://developer.apple.com/appstore/guidelines.html
Például az XCode programban van egy Product/Analyze menüpont melynek lefuttatásakor nem lenne szabad semmilyen üzenet kapni, az AppStore ellenőrzés első lépései között van ennek vizsgálata.
4 Bevezetés az iOS platform programozásába Mindenképp érdemes először 2 tutorialt végigcsinálni, az egyik a First iOS application a másik a Second iOS application, és XCode-ban az Organizer Documentation részében keressünk rá. A fejlesztőeszköz az XCode nevű program, melyet az AppStore-ból tudunk telepíteni.
4.1 XCode tippek Az XCode használata közben van néhány dolog mely eléggé el van dugva az interneten. És amíg nem találunk rá okozhat némi fejtörést.
4.1.1
#pragma mark
Az összes példaprogramban amit az XCode help rendszerből töltünk be találunk ilyen sorokat a forráskódokban. Az érdekessége az, hogy nem az iOS-nek, nem a fordítónak szól, hanem az XCode-nak, a következő képen látszik , hogy a lenyíló metódus lista menüt tudjuk szekciókra osztani ezekkel a #pragma mark sorokkal:
Azért érdemes tudnunk, hogy a #pragma másra is használatos Objective-C nyelveben, de nekünk nagy valószínűséggel nem lesz rá szükségünk. Részletesebb információk:
https://developer.apple.com/library/mac/#documentation/developertools/Conceptual/CppRunt imeEnv/Articles/SymbolVisibility.html
4.1.2
Analyze
Az AppStore-ba alkalmazást bejuttatni nem egyszerű művelet, ez a menüpont nyújt némi támogatást azzal, hogy a program teljes kódját egy alaposabb vizsgálatnak veti alá és forduló és jól működő részekben is képes problémákra felhívni a figyelmet:
A képen látszik, hogy igen látványosan próbál megmutatni egy Logic Error néven illetett problémát. Az esetek többségében ezek javítása nem kis ráfordítást igényel.
4.1.3
Hivatkozások (included by)
Eclipse-t használó fejlesztőként nagyon hamar felmerül a kérdés, hogy a jól megszokott Referenced by funkció hol található vagyis honnan tudom, hogy az osztályomat hol használják a kódban. Erre a program szerkesztő ablak bal felső sarkában található ikonra kattintáskor megjelenő menüben találunk 2 menüpontot az Includes és Included By utána zárójelben számok, hogy hányat talált az XCode. Az Includes azt mutatja meg, hogy az adott fájl milyen #include-okat tartalmaz. Az Included By pedig azt mutatja meg, hogy melyik másik fájlok includjában szerepel az adott fájl. Tehát a h és az m fájlon állva más és más lehet a menü tartalma.
4.1.4
Outlet-ek, action-ök
Ahhoz hogy a kódból elérjünk egy GUI elemet IBOutlet típusú hivatkozást kell készítenünk a h fájlba. Erre több módszert is nyújt a Storyboard editor. Az egyik lehetőség, hogy a GUI elemet kiválasztva, jobb oldalt utolsó ikon (Connections inspector) panelen a kapcsolatokat láthatjuk, és innen a megfelelő sorból a kis jobb oldali kört drag&drop módszerrel dobjuk bele a forráskódba. Ekkor egy kis feljövő ablakban megadhatjuk a főbb paramétereit az IBOutletnek vagy az IBAcion-nek, majd a Connect gombra kattintás után a képen látható állapotba jutunk. A képen figyeljük meg, hogy
a fában a Button - Guide van kiválasztva
a program kódban az IBOutlet UIButton *butGuide
és jobb oldalt a Connections inspector-ban alul a butGuide - Grid Controller kapcsolat látható
Az interneten nagyon sok videót és tutoriált találni a témában, a következő linken egy nagyon jó leírás van az outletek kezeléséről: http://klanguedoc.hubpages.com/hub/IOS-5-A-Beginners-Guide-to-Storyboard-Connection
4.2 iPhone emulátor Az XCode telepítésével kapunk egy nagyon jó emulátort iPhone és iPad fejlesztéshez. Pár eltérést biztosan tapasztalunk majd de összességében gyors és hiteles az emulátor. Egy érdekességet tapasztaltunk a fejlesztés során. Ha egy programot fektetett módban indítunk az emulátorban, akkor nem hívódik meg a willAnimateRotationToInterfaceOrientation viszont egy valós iPhone iOS-e meghívja ezt.
4.3 C o m p i l e , l i n k és beállításaik Az XCode linker részén általában semmit sem kell állítani, csak ha valami extra dolgot használunk. Esetünkben a webszerviz hívások miatt egy XML feldolgozót használunk melyet linkelni kell a programhoz.
4.4 F e l h a sználói felület Leírásokban szokták emlegetni az IB eszközt, ami az Interface Builder, vagyis az a komponens, amivel a felhasználói felületeket lehet készíteni. Régebbi verziókban ez (és még pár másik alkalmazásfejlesztői eszköz is) külön program volt. Mára integrálódtak az XCodeba. Az IB-vel készített fájlok kiterjesztése xib, ezek xml-ben tartalmazzák a képernyő leírását, más helyeken nib-nek is hívják. Az Apple legújabb fejlesztése a storyboard megjelenése volt. A storyboard editor is az XCode 4-ben található. Az Apple készített egy dokumentumot melynek címe iOS Human Interface Guidelines. Ebben a dokumentumban az alkalmazás dizájn stratégiától a megfelelő képek elkészítésének elméletéig mindenről van szó. Ez és az ilyen dokumentumok elolvasása nemcsak hasznos, hanem egyben előrevetíti az AppStore-ba feltölteni szándékozott alkalmazásokkal szemben támasztott követelményeket is. Például - hogy a dizájnnál maradjunk - az Apple szerint az iPhone-on jól megérinthető könnyen "eltalálható" grafikai elemek (gombok) minimális mérete 44 pixel a 320 széles, 480 pixel magas képernyőn.
A képernyő rajzoló eszközökkel készített fájlok feldolgozása futásidőben történik, tehát a storyboard fájl átmásolódik az iPhone-ra az alkalmazás csomagjában (ipk). Majd amikor a programot elindítjuk az iOS felolvassa, értelmezi, végrehajtja a gui megjelenítését, osztályok példányosítását. Viszont látszólag triviálisnak látszó dolgok sem biztos, hogy megtörténnek ilyenkor. Például ha mindent jól beállítunk a storyboard-ban, hogy képernyő elforgatáskor minden a megfelelő módon méreteződjön át, de ez mégsem történik meg, akkor a storyboardban látható view hierarchiát az osztályunkban is fel kell építeni. Vagyis ha egy view benne van a másikban (child view), akkor a tartalmazó view-hoz az addSubView metódussal hozzá kell adni az általa tartalmazott view-kat: [parent addSubView:childView].
4.5 Webszervizek Java környezetben könnyen lehet webszerviz klienst generálni, erre több megoldás, több keretrendszer is létezik. iOS környezetben ezt a http://sudzc.com/ weboldalon tehetjük meg, ahova egy wsdl-t lehet feltölteni, majd be kell állítani a generált kód típusát (Objective-C with ARC). A végeredmény egy zip fájlban töltődik le. Minden generált zip fájlban van Soap és TouchXML könyvtár, ezt elég egyszer a projektünkbe építeni.
4.6 Alkalmazás logika Minden alkalmazásban kell lennie egyetlen osztálynak melynek interfésze kb így néz ki: @interface OsztályNeve : UIResponder
Ha az alkalmazás fejlesztés kezdetekor az XCode-dal hozzuk létre a projektet, akkor egy ilyen osztályt automatikusan létre is hoz. Ez lesz az ugynevezett application delegate osztály vagy röviden AppDelegate. Ebből az alkalmazásban egy példány jön létre (singleton). Az operációs rendszer, az iOS, ezzel az osztállyal tudatja, hogyha például az alkalmazás futását meg kell szakítani (background), mert épp van egy bejövő hívás a telefonra. Vagy például azt is, hogy most az előtérbe került az alkalmazás (foreground). Az iOS rendszer egy másik érdekessége, hogy konzekvensen minden metódusnak teljes neve van, nincsenek rövidítések a rendszerben. Például az előző bekezdésben említett 2 esemény bekövetkezésekor az iOS az application delegate osztály következő metódusait hívja meg: applicationDidEnterBackground applicationWillEnterForeground
A metódusok nevében szinte mindig szerepel a will vagy a did szócska, ezzel jelezve, hogy az esemény előtt vagy az esemény után hívódik meg a metódus. Egy képernyő megjelenése közben 12-16 metódus is meg tud hívódni. Persze nem mindet kell implementálni, csak amik esetében mást szeretnénk futtatni mint az alapértelmezett. A dokumentáció minden metódus esetében megadja, hogy az ősosztály (super) metódusát meg kell-e hívni vagy sem, ezt mindenképp be kell tartanunk a helyes működés érdekében. Az általában igaz, hogyha valami megvalósítása csak nagyon nehezen megy, akkor azt az iOS tervezői nagy valószínűséggel máshogy képzelték el, és a program ezen az erőltetett módon megvalósított része lassú és/vagy instabil lesz.
4.7 Képernyők, hogyanok és miértek 4.7.1
Navigation Controller
Minden alkalmazásba el kell helyezni egy Navigation Controller-t a storyboardon. Amikor az iOS betölti az alkalmazást, akkor a storyboardot is beolvassa, és példányosít pár osztályt. Ha nincs navigation controller, akkor nem működik az egyes képernyők közötti átmenet, a segue. A navigation controller képernyőnek látszik, de nem az és nem helyezhető rá semmilyen view elem.
4.7.2
Induló képernyő
A navigation controllernél a storyboardban ki kell pipálni az Initial Scene jelölőnégyzet. A storyboard gondoskodok arról, hogy csak 1 Initial Scene legyen kiválasztva. Ezután az indulónak szánt képernyőt egy "Root View Controller" segue-val kell összekötni. Alkalmazásunkban az induló képernyő az úgynevezett splash screen. Ez általában egy olyan képernyő mely csak addig látszik, amíg például az alkalmazás kapcsolódik a szerverhez, adatbázishoz, vagy amíg az alap működéshez szükséges objektumok betöltődnek.
4.7.3
Az UIApplicationDelegate
AppDelegat osztály az az osztály lehet melynek így néz ki az interfésze: @interface PGAppDelegate : UIResponder
Esetünkben az AppDelegate osztály a PGAppDelegate nevet kapta. Hogy ki is az igazi AppDelegate azt a main.m fájlban találjuk. Ebben van az alkalmazást indító (C nyelvből bizonyára jól ismert) függvény a main,melyben megtalálhatjuk a PGAppDelegate osztályra hivatkozást: int main(int argc, char *argv[]){ @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([PGAppDelegate class])); } }
Az AppDelegate osztály kap értesítést az operációs rendszertől sokféle eseményről, a teljesség igénye nélkül nézzük mik lehetnek ezek:
ha az alkalmazás elindult
ha az alkalmazást háttérbe helyezte az operációs rendszer, mert például érkezett egy hívás a telefonra
ha az alkalmazás előtérbe került újból, mert a telefonbeszélgetés véget ért
Ebből az osztályból minden alkalmazás esetében csakis 1 példány jön létre Az alkalmazás indulása közben hívódik meg a következő metódus, a dokumentáció szerint akkor, amikor az alkalmazás majdnem futásra kész: - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[[WSLogin alloc] initWithDelegate:self] login]; return YES; }
Az alkalmazás ezen állapotában bátran bízhatunk benne, hogy minden osztályból a szükséges példányok létrejöttek és használhatjuk azokat. Példánkban elindítunk egy webszerviz hívás segítségével egy login folyamatot. A delegate minta értelmében a login folyamat végén egy másik metódusa hívódik majd meg az AppDelegate-nek. Sikeres bejelentkezés után elindíthatunk egy Timer-t, mely időközönként valamilyen feladatot végez: -(void) startHeartBeat { heartBeatTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(heartbeat:) userInfo:Nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:heartBeatTimer forMode: NSDefaultRunLoopMode]; }
A vastagon kiemelt szám mutatja, hogy 30 másodpercenként fog elindítani valamit a Timer. És a vastagon kiemelt heartbeat: metódust fogja meghívni. A @selector utasításról már korábban volt szó. -(void) heartbeat: (NSTimer *) sender { NSLog(@"heartbeat"); }
A következő metódus hívódik meg akkor, ha például egy bejövő hívás érkezik ezért az alkalmazást a háttérbe teszi az iOS. Ebben a metódusban annyi információt kell eltárolni, hogy amikor az alkalmazás újraaktiválódik, akkor tudja folytatni a futását. És ebben a metódusban kell leállítani a futó Timer-eket, ha vannak: - (void)applicationDidEnterBackground:(UIApplication *)application { [heartBeatTimer invalidate]; NSLog(@"Going to sleep ...."); }
A következő metódus hívódik meg, ha mondjuk vége a hívásnak és az alkalmazás visszakapja a vezérlést. De olyan is előfordulhat, hogy az alkalmazás használata közben letesszük a telefont az asztalra, majd ott felejtjük egy napra, ha egy nap múlva ismét a kezünkbe vesszük a telefont és megnyomunk egy gombot, akkor is ez a metódus hívódik meg: - (void)applicationWillEnterForeground:(UIApplication *)application {
NSLog(@"Wake up Neo"); [[[WSLogin alloc] initWithDelegate:self] login]; }
Itt látható, hogy az alkalmazás indulásakor (a didFinishLaunchingWithOptions metódusban) hívott bejelentkeztető webszervizt hívjuk ismét. Ebben a fejezetben tárgyalt metódusok hívásának nincs látható nyoma az eszköz képernyőjén. Ezért például a login folyamat alatt jelenítjük meg a Splash képernyőt.
4.7.4
Storyboard
A StoryBoard egy új fejlesztés az XCode 4-ben és az iOS 5-ben. A korábbi Interface Builderhez képest az az újdonsága, hogy több képernyő rajzolható egy nagy munkaterületen és a képernyők közötti kapcsolatok is itt adhatók meg. A storyboard editorban ezenkívül az IB-hez képest több komponens esetében is több paramétert lehet beállítani a komponenseknél. A másik nagyon fontos különbség az IB-hez képest, hogy a képernyők közötti kapcsolatok is definiálhatók, ezek neve segue. Ilyen segue egérrel húzható mondjuk egy gombról egy másik képernyőre. Innentől az alkalmazás a gombra kattintással átmegy a másik képernyőre.
Korábban, amikor xib fájlokkal dolgozott a rendszer, ez sokkal nehézkesebb volt. Ha valamiről nem tudunk segue-t indítani, akkor húzzuk az egyik képernyőről a másikra. Majd a megfelelő touch esemény kezelőbe írjuk be a segue meghívását. A storyboard hátránya, hogyha többen dolgoznak egy storyboard-on, akkor a verzió kezelőbe juttatás (svn commit) sok problémát okoz, nem egyszer manuálisan kell a storyboard xml fájlt egyenesbe tenni. Viszont egy projektben akárhány storyboard fájl lehet. Látható hogy a segue-k igencsak behálózhatják a munkaterületet, még ilyen egyszerű esetben is.
4.7.5
Splash screen
Az alkalmazás indulásakor azért a splash screent jeleníti meg az iOS, mert a Navigation Controllernek beállítottuk egy segue-val, hogy a "Root View Controller" a splash screen legyen. A következő metódus hívódik meg a splash screen létrehozása közben: - (void)viewDidLoad { [super viewDidLoad]; PGAppDelegate *del = (PGAppDelegate*)[[UIApplication sharedApplication] delegate]; [del setSplash:self]; [splashImage setImage:[UIImage imageNamed:@"splash_portrait.jpg"]]; }
Általában minden metódusról érdemes elolvasni mit írnak a developer.apple.com weboldalon. Különösen a következő mondat keresése fontos: If you override this method, you must call super at some point in your implementation. Nem minden metódusnál szerepel a leírásban ez a mondat, például a viewDidLoad-nál sem, de az ős meghívása nem árthat, és az XCode-dal létrehozott osztály vázban a viewDidLoad hívja a super-t, ezért szerepel esetünkben is a super hívása a metódus első sorában. A metódus második sorában a tipikus megoldást láthatjuk az AppDelegate példány megszerzésének. A harmadik sorban az AppDelegate egy saját készítésű metódusát hívjuk, mely ennek hatására jött létre: @property (strong) PGSplashScreenController *splash;
Vagyis az AppDelegate kap egy referenciát a splash screen példányra. Ez később nyer értelmet, amikor az AppDelegate értesíti majd a splash screen-t a sikeres loginról. A következő sor beállítja a képernyőre egy UIImageView-ba a splash képernyőn látszódó képet. A következő metódus segítségével érjük el azt, hogyha fektetett állapotban (landscape) indítják el a programot, akkor az is a landscape módba váltson: (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toI nterfaceOrientation duration:(NSTimeInterval)duration { if ((toInterfaceOrientation == UIDeviceOrientationLandscapeLeft || toInterfaceOrientation == UIDeviceOrientationLandscapeRight)) {
[splashImage setImage:[UIImage imageNamed:@"splash_landscape.jpg"]]; } else { [splashImage setImage:[UIImage imageNamed:@"splash_portrait.jpg"]]; } }
A többi képernyőnél ez máshogy van megoldva a viewDidLayoutSubviews metódusban, de a program indulása utáni első képernyőn csak így működött az elforgatás. A következő metódus megjeleníti a progress csigának elnevezett animációt. Mely egy lekerekített sarkú négyzet melyben egy vonal forog. Ez a programban használt jelzés arra, hogy valamilyen folyamat befejeződésére várunk. Ez nem csak megjelenik, hanem blokkolja is a felhasználótól érkező inputot amíg el nem tüntetik. - (void) viewDidAppear:(BOOL)animated { [MBProgressHUD showHUDAddedTo:self.view animated:YES]; }
A következő metódus az előtt hívódik meg, hogy a segue-t - mely a splash screen utáni képernyőre visz - a rendszer végrehajtaná, és ez kapcsolja ki a progress csigát, mivel itt csak egy darab segue van ezért nincs elágazás segue-nként a metódusban: - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{ [MBProgressHUD hideAllHUDsForView:self.view animated:YES]; }
Az AppDelegate a következő hívással indítja majd a splash screenről a következő képernyőre (Grid) vivő segue-t: [self.splash performSegueWithIdentifier:@"gotoGrid" sender:self];
Az AppDelegate splash adattagja a fentebb látott viewDidLoad metódusban töltődik fel, a setSplash metódus hívással. A kódban látható "gotoGrid" szöveget a storyboard editorban kell megadni az Identifier mezőben. A példa képen a gotoChannelDetail segue adatai látszanak. A különböző képernyőkön lévő segue-knak lehet ugyanaz a neve. Adott képernyőből induló segue-knak viszont mindenképp különböző nevűeknek kell lennie. A "gotoGrid" nevű segue a PGGridController képernyőre (scene) visz tovább.
4.7.6
Táblázatok, ScrollView elem
A Grid képernyő 9 táblázatot tartalmaz minden táblázat görgethető függőleges irányban. A 9 táblázat 3 csoportra van bontva, egyszerre mindig 3 táblázat látszik. A 3 táblázat csoport között vízszintes görgetéssel (swipe) lehet váltani. A függőleges görgetés folyamatos, a vízszintes görgetés pedig lapokra osztott, vagyis 3 állapota van. Vagy az első 3 vagy a második 3 vagy a harmadik 3 táblázat látszik. Tovább bonyolítja a képernyőt a felül látható dátum mező és alatt (a képen csak a fában látszó Page Controller) swipe jelölő mely mutatja,
hogy a 3 lap közül éppen melyiken tartózkodunk. A képernyő felépítése már elsőre sem tűnik túl egyszerűnek, nézzük hogyan lehet ezt iOS-ben megoldani. A képen bal oldalon egy fa struktúrában is láthatjuk az elemek egymásba ágyazását. Legkívül egy Image View van, mely a háttérképet fogja tartalmazni. Aztán jön a Page Control, majd a
Scroll View mely a vízszintes görgetést oldja meg. A Scroll View tartalmaz 3 view-t, ezt a 3 view-t mint 3 lapot lehet lapozni. Egy ilyen view 3 table view-t tartalmaz. Most akkor lássuk mit kell tennie a kódnak, hogy ez működjön. Ha több view (olyan megjelenő objektum melynek van UIView az ősei között) van egymáson, akkor vajon melyik van felül? Amelyik felül van az letakarja az alatta levőt, tehát az alatta levő csak ott látszik ahol a felül lévő átlátszó. A fában lentebb lévő van fentebb, közelebb a szemünkhöz. Tehát a képen a kijelölt Async Image View-t eltakarja a fában alatt lévő Image View. Persze a fában vannak olyan elemek melyek egymás mellett vannak, ott ez a kitakarás nem játszik szerepet. Ahhoz, hogy az iOS (5-ös sorozat esetén) ezt tényleg meg is csinálja, az egyes view elemek egymásba ágyazását kód szinten is meg kell valósítani a viewDidLoad metódusban: [self.view addSubview:toolBar]; [self.view addSubview:pageCtrl]; [self.view addSubview:gridScrollView]; [self.gridScrollView addSubview:tableViewContainerToday]; [self.gridScrollView addSubview:tableViewContainerTomorrow]; [self.gridScrollView addSubview:tableViewContainerThisWeek]; [self.toolBar addSubview:lblDatum]; [self.view addSubview:buttonView]; [self.buttonView addSubview:butOwn];
[self.buttonView addSubview:butFriends]; [self.buttonView addSubview:butBest];
Ha a fenti képen látható legfelső Image View elembe beállítunk egy képet mely a teljes képernyő háttere lesz akkor ahhoz, hogy ez látszódjon a Scroll View-t átlátszóvá kell tennünk: self.gridScrollView.backgroundColor = [UIColor clearColor]; self.gridScrollView.opaque = NO;
De például a ToolBar elem csak úgy tehető átlátszóvá, hogy ráteszünk egy háttérképet, ami egy olyan kép, amin nincs semmi csak teljesen átlátszó. A Scroll View akkor görgethető "darabosan", vagyis úgy hogy fix pontokon áll csak meg és nem folyamatos a görgetése, hogyha a Paging Enabled paraméterét YES-re állítjuk, ahogy a képen látszik. Esetünkben három oldal lesz melyek között lehet görgetni, melyek a képen a következők: Today View, Tomorrow View, This week view. Esetünkben a Scroll View elemben az oldalankénti lapozást a következők betartásával érhetjük el. 1. setContentSize metódussal adjuk meg a képernyő vízszintes méretének háromszorosát 2. setContentOffset hívással állítsuk be melyik oldal látszódjon 3. a Scroll View frame-jét állítsuk a képernyőn látható méretére 4. az egyes Table View frame-eket állítsuk be úgy, hogy a megfelelő helyre kerüljenek Minden UIView leszármazott frame adattagjával lehet beállítani, hogy az adott view mekkora legyen és hol helyezkedjen el a szülő view-ján belül. A gyakorlatban ezt meg kell még bonyolítani azzal, hogy a készülék elforgatásakor az álló és a fekvő képernyőn is jól látszódjanak az elemek. Az elforgatáskori átszámolásra a viewDidLayoutSubviews metódust használjuk, és mivel ez a képernyő megjelenítése során mindig meghívódik ezért itt állítunk be minden méretet. A következő ábrán talán jobban érthetővé válik, hogy mit is szeretnénk. Az ábrán a piros téglalap a Scroll View, a zöld téglalapok a Today View, Tomorrow View és a This week View, a fekete téglalapok pedig a Table view-k melyek függőlegesen görgethetők. A Scroll View frame mérete határozza meg az oldalak méretét, a contentSize pedig a teljes méretét. A setContentSize beállítása (fentebb 1.-es pont) és a Scroll View frame beállítása (fentebb 3-mas pont): int scree nWidt h = self. view.
frame.size.width; self.gridScrollView.frame = CGRectMake(self.gridScrollView.frame.origin.x, self.gridScrollView.frame.origin.y, screenWidth, self.gridScrollView.frame.size.height); [self.gridScrollView setContentSize:CGSizeMake(screenWidth*3, self.gridScrollView.frame.size.height)];
A screenWidth a látható képernyő szélességi méretét tartalmazza, ez álló és fekvő képernyő esetén eltér. Ezért ezt felhasználjuk a számítások során. Ha az első oldal látszik, akkor a következő kódot használjuk a Scroll View pozícionálására (fentebb 2.-es pont teljesítése): [self.gridScrollView setContentOffset:CGPointMake(0, 0)];
Ha a második oldal: [self.gridScrollView setContentOffset:CGPointMake(screenWidth, 0)];
Ha a harmadik oldal: [self.gridScrollView setContentOffset:CGPointMake(screenWidth*2, 0)];
A három view (zöld téglalapok) frame-jét is be kell állítanunk (fentebb 4.-es pont teljesítése): CGRect fr = self.gridScrollView.frame; self.tableViewContainerToday.frame = CGRectMake(0, 0, screenWidth, fr.size.height); self.tableViewContainerTomorrow.frame = CGRectMake(screenWidth, 0, screenWidth, fr.size.height); self.tableViewContainerThisWeek.frame = CGRectMake(screenWidth*2, 0, screenWidth, fr.size.height);
A screenWitdh változó fentebb kapott értéket. Figyeljük meg, hogy mindegyik frame screenWidth széles és a magassága megegyezik a Scroll View magasságával, és felülről (y koordináta) 0 pixel távolságra van. Ami változik az a Scroll View bal szélétől mért távolság. Mivel eddig mindent a screenWidth értékéből számoltunk, így a képernyő álló vagy fekvő állapotában is jó lesz a számolás. A következő feltétellel ellenőrizhető, a készülék aktuális iránya, és itt látható példa a különböző irányokban történő háttérkép beállításra: if (([[UIApplication sharedApplication] statusBarOrientation] == UIDeviceOrientationLandscapeLeft || [[UIApplication sharedApplication] statusBarOrientation] == UIDeviceOrientationLandscapeRight)) { // landscape [(UIImageView*) self.view setImage:[UIImage imageNamed:@"land_grid_hatter.jpg"]]; } else { // portrait [(UIImageView*) self.view setImage:[UIImage imageNamed:@"grid_hatter.jpg"]]; }
Azt hogy a képernyőnk milyen irányban képes megjelenni jól és ezekben az irányokban hívódjanak meg a képernyőnk metódusai azt a következő metódus visszatérési értékével jelezhetjük az iOS rendszernek:
(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interf aceOrientation { return YES; }
Fontos, hogy a view hierarchián kívül (addSubview-val a view hierarchiát definiáltuk fentebb) a View Controllerek hierarchiáját is kialakítsuk, erre szolgál az addChildViewController metódus: [self addChildViewController:tableViewCtrl];
A következő metódus összekapcsolja a view-t (ami tartalmazza a table view-t) a table view-t és a table view controller-t, ami a table view-t vezérli: -(void) connectTableViewAndController:(ProgramListTableViewController *)tableViewCtrl tableView:(UITableView *)tableView tableViewContainer:(UIView *) tableViewContainer { [tableView setDataSource:tableViewCtrl]; [tableView setDelegate:tableViewCtrl]; [tableViewCtrl setTableView:tableView]; [tableViewContainer addSubview:tableView]; [self addChildViewController:tableViewCtrl]; }
Ezt sok helyen tudjuk használni tekintettel a képernyőn található 9 table view-ra. A Scroll View-ban a görgetés befejeztével az éppen látszó 3 table view-t be kell frissítenünk, ezt a scrollViewDidEndDecelerating metódusban tehetjük meg, ez akkor hívódik meg, amikor a görgetés teljesen megáll: - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { int offset = self.gridScrollView.contentOffset.x; int screenWidth = self.view.frame.size.width; if (offset == 0) { [PGCommon getSessionData].gridAktDate = TODAY; [self refreshTableViews]; } else if (offset == screenWidth) { [PGCommon getSessionData].gridAktDate = TOMORROW; [self refreshTableViews]; } else { [PGCommon getSessionData].gridAktDate = THISWEEK; [self refreshTableViews]; } }
A TableView úgy jeleníti meg az adatokat, hogy ha készítenünk kell hozzá egy TableViewController-t. Az az osztály lehet TableViewController mely interfésze így néz ki: @interface ProgramListTableViewController : UITableViewController
Vagyis az UITableViewController az őse és megvalósítja a UITableViewDelegate, UITableViewDataSource protokollokat.
A táblázatok alapvetően úgy jelennek meg, hogy a táblázat celláját definiálni kell külön, majd az UITableViewDataSource protokollban definiált cellForRowAtIndexPath implementálásával képes lesz az iOS soronként elkérni a megfelelő cellát a kódunktól. A megjelenítéssel vagy a görgetéssel vagy a cellák létrehozásával nem kell foglalkoznunk, ezt az iOS végzi. A másik kötelező metódus a numberOfRowsInSection melyben az iOS a táblázat által tartalmazott sorok számát tudakolja az osztályunktól. Itt jegyzem meg, hogy a táblázat a sorokat szekciókra (section) tudja bontani vagyis két mélységű fát tud ábrázolni. Esetünkben csak egy szekció van. A táblázat cella egy korábbi képernyőképen már szerepelt, ezen a képen piros kerettel be is jelöltük. A fában látszik a Program List Table Cell elem mely alá rendelve látszik 4 további elem, melyek a piros négyszögön belül vannak a képernyő design terven. A TableViewCell leszármazott egy különálló osztály mely általában nagyon egyszerű. Szinte csak az elemek tárolására szolgál. Ez van a h fájlban: @interface ProgramListTableCell : UITableViewCell @property (nonatomic, strong) IBOutlet AsyncImageView *prgImage; @property (nonatomic, strong) IBOutlet UIImageView *frameImage; @property (nonatomic, strong) IBOutlet UILabel *prgLabel; @property (nonatomic, strong) IBOutlet UILabel *dateLabel; @property (nonatomic, strong) NSString *category; @end
Ez pedig az m fájl: @implementation ProgramListTableCell @synthesize prgImage = _prgImage; @synthesize frameImage = _frameImage; @synthesize prgLabel = _prgLabel; @synthesize dateLabel = _dateLabel; @synthesize category = _category; - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; return self; } @end
A cellák beállítása, feltöltése egyesével történik a TableViewController osztályban a cellForRowAtIndexPath metódusban, ezt az iOS hívja amikor ki szeretne rajzolni egy cellát. TableView frissítése a reloadData metódussal történik, amikor már a táblázat mögötti adatlista frissítése megtörtént. A reloadData hatására az iOS az összes látszó cellát újra elkéri egyesével a cellForRowAtIndexPath metódustól.
4.7.7
Touch Event kezelése
Általában ez nem nehéz feladat, ezért az alap tutorial-okban leírt eseteket nem is tárgyaljuk. Nézzünk inkább egy komplexebb problémát. Egy táblázat egy sora egy cella, ebben a cellában alapvetően két terület határolható el:
a bal oldali piros négyszög, ezt megérintve egy képernyőre szeretnénk jutni
a jobb oldali piros négyszög, ezt megérintve egy másik képernyőre szeretnénk jutni
A képen bal oldalon piros téglalapban látható, hogy két view-t helyeztünk el először a táblázat cellában majd ezekbe a view elemekbe pakoltuk a többi elemet. A táblázat cellát előállító metódusban (cellForRowAtIndexPath) a következő kódot helyezzük el (a bal oldali view a channelImageView a jobb oldali view a prgListView): if ([[cell.channelImageView gestureRecognizers] count] == 0) { [cell addSubview:cell.channelImageView]; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleChannelTap:)]; tap.cancelsTouchesInView = YES; tap.numberOfTapsRequired = 1; [cell.channelImageView addGestureRecognizer:tap]; [cell.channelImageView setUserInteractionEnabled:YES]; }
Tehát a Touch Event a channelImageView-n a handleChannelTap metódust fogja hívni. A prgListView esetében is hasonló a kód csak itt a handlePrgTap metódust hívjuk: if ([[cell.prgListView gestureRecognizers] count] == 0) { [cell addSubview:cell.prgListView]; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handlePrgTap:)]; tap.cancelsTouchesInView = YES; tap.numberOfTapsRequired = 1; [cell.prgListView addGestureRecognizer:tap]; [cell.prgListView setUserInteractionEnabled:YES]; }
A fentebb beállított metódusok így néznek ki:
- (void) handlePrgTap:(UIGestureRecognizer *)gestureRecognizer { ChannelListTableCell *x = (ChannelListTableCell*)gestureRecognizer.view.superview; [[self parentViewController] performSegueWithIdentifier:@"gotoProgramList" sender:x.channel]; }
Ahhoz, hogy ez a 2 sor működjön össze kell hangolni a kód több pontját. 1. A ChannelListTableCell-nek tartalmaznia kell a channel adattagot, mely a megjelenített csatorna adatait tartalmazza. 2. Ezek a kódok egy TableViewController-ben vannak, a parentViewController hivatkozás csak akkor működik, ha a táblázatot tartalmazó képernyő (View Controller) addChildViewController metódusával össze vannak kapcsolva. 3. A táblázatot tartalmazó képernyő (View Controller) definiálja a gotoProgramList segue-t. A parentViewController-ben tehát le kell kezelni a segue-t: - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{ if ([[segue identifier] isEqualToString:@"gotoProgramList"]) { PGProgramListController *vc = [segue destinationViewController]; [vc setFromView:GUIDE]; [vc setChannel:sender]; } }
Fentebb látszik, hogy a sender:x.channel, itt pedig a setChannel:sender hívást hajtjuk végre. Tehát nézzük a hívási láncot: 1. A felhasználó megérinti a jobb oldalát egy táblázat sornak. 2. A TableViewController meghívja a handlePrgTap metódust. 3. A handlePrgTap metódus az ő szülő ViewController-ének elindít egy gotoProgramList nevű segue-t melynek paraméterül adja a kiválasztott sor channel adattagját. 4. Szülő ViewController-nek meghívódik a prepareForSegue metódusa, mely a destinationViewController megfelelő beállítását végrehajtja (setChannel). 5. Az iOS megjeleníti a destinationViewController-t. Az esemény kezelés egy külön tudományág az iOS rendszerben, ennél bonyolultabb esetek is előfordulhatnak. És nagyon érdekes működési anomáliák bölcsője is lehet. Például egy Tap esemény egyszerűen nem akar aktiválódni, mintha a felhasználó nem is nyomkodná a képernyőt, ilyenkor esetleg gyanakodjunk, hogy egy másik view letakarja azt amelyik a Tap eseményt várja.
5 Források Az Apple nagyon sok dokumentációt készített mind iOS mind MacOSX programozáshoz. Nem csak száraz api dokumentációkat, hanem tutorial-okat, guide-okat: https://developer.apple.com/library/ios/#referencelibrary/GettingStarted/RoadMapiOS/chapter s/Introduction.html Amikor az XCode-ot installáljuk a machintosunkba biztosan elgondolkodunk, hogy miért 1.5 Giga bájt az install mérete. Azért mert a teljes developer dokumentáció, minden tutorial, minden példa alkalmazás megtalálható benne, ami a developer.apple.com oldalon fent van. Tehát ha nincs internetelérésünk éppen, akkor is a teljes dokumentáció ott van a Mac-ünkön. Ha valamit nem találunk, akkor google-val keresve 2 oldal az, ahova nagyon sokszor eljutunk: http://cocoawithlove.com/ http://stackoverflow.com/ Egyéb internetes források a témában: http://hu.wikipedia.org/wiki/Objective-C https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjectiveC/Introd uction/introObjectiveC.html http://hu.wikipedia.org/wiki/IOS