iphone programozás alapjai II Gyakorlat 1 A mai gyakorlat témái I Modell szétválasztás Modell logika osztályainak létrehozásának módjai Szakácsköny model kialakítása II Hálózat kezelés Hálózat kezelés típusai ASI HTTP 2 I MVC Architektúra 3
A hierarchia alapjai A nézetek egymásra rétegződnek A nézetek szülő - gyermek kapcsolatban állnak egymással Nézetet és controllereket létrehozhatunk IB-ben, vagy programatikusan, a controllerek implementációját el kell készíteni 4 Az aktív controller Mindig csak egy controller aktív Többféle módon tudunk váltani az aktív kontrollerek, vagy nézetek között addsubview - hozzáadja és megjeleníti a nézetet az aktuális nézethez, de a szülő nézet controllere marad aktív [UIView presentmodalviewcontroller] - modálisan megjeleníti a kiválasztott nézetet, aktivizálja a controllerét, és beállítja parentcontrollernek az előzőleg aktuális kontrollert pushviewcontroller - NavigationController esetén ezzel a metódussal tudunk a stackhez hozzáadni egy új nézetet, ez lesz az aktív controller, és a szülője a navigation controller példánya lesz Ilyenkor elérhető a navigationcontroller, vagy tabbarcontroller property az adott controllerben 5 A projekt struktúrálása Nincsenek package-k Ún groupokat hozhatunk létre (forrás könyvtárak) Érdemes elkülöníteni a model és controller osztályokat (esetleg view-t is - programozott nézetek) XIB-ek általában a Resources könyvtárban vannak Húzzuk át a controller osztályokat a Controller könyvtár alá 6
A singleton minta Objective-C-ben is gyakran használt pattern Igen jó minta a program funkcionális részeinek szeparálásához Modell funkciókra hasznos, mert így a legtöbb controllerből könnyen lehet kezelni az adatokat Példák: Működés főbb moduljainak megvalósítása Hálózati kapcsolatért felelős osztályok Utility osztályok 7 Singleton példa @interface MySingleton : NSObject { + (MySingleton*) getinstance; @end static MySingleton *instance = nil; +(MySingleton*)getInstance{ if (instance==null){ @synchronized([uiapplication sharedapplication]){ if (instance==null) instance=[[mysingleton alloc] init]; return instance; 8 Selector A @selector nem más mint egy módszer egy metódus kiválasztására SEL aselector = @selector(run); [anobject performselector:aselector]; A selectoroknak fontos szerepe van ha több szálon dolgozunk A GUI-t nem szabad külső szálról módosítani // GUI módosítás beütemezése a fő szálba [handler performselectoronmainthread:@selector(messagearrived:) withobject:msg waituntildone:yes]; // Új szál indítása ha a szál implementációja az aktuális osztály [NSThread detachnewthreadselector:@selector(run) totarget:self withobject:nil]; 9
Folytassuk a szakácskönyvet Hozzunk létre egy CookBookManager singleton osztályt Készítsük el az adatokat reprezentáló modell osztályokat: Recipe Készítsünk metódusokat a CookBookManager osztályba a következő feladatokra: Ajánlatok Kategóriák lekérdezése Kategóriához tartozó receptek lekérdezése Kedvencek lekérdezése Hozzáadás a kedvencekhez 10 Recipe Osztály Készítsünk egy új osztályt (Recipe), amely egy recept tárolására szolgál (Recipeh) @interface Recipe : NSObject { NSString* title; NSString* subtitle; NSString* description; NSString* complexity; - (id) initwithtitle:(nsstring*)_title andsubtitle:(nsstring*) _subtitle anddescription:(nsstring*)_description andcomplexity: (NSString*)_complexity; @property (nonatomic, retain) NSString* title; @property (nonatomic, retain) NSString* subtitle; @property (nonatomic, retain) NSString* description; @property (nonatomic, retain) NSString* complexity; @end 11 Recipe Osztály Recipem (végéről ne felejtsük el a dealloc-ot) @implementation Recipe @synthesize title, subtitle, description, complexity; - (id) initwithtitle:(nsstring*)_title andsubtitle:(nsstring*) _subtitle anddescription:(nsstring*)_description andcomplexity: (NSString*)_complexity; { self = [super init]; if(self = nil) { selftitle = _title; selfsubtitle = _subtitle; selfdescription = _description; selfcomplexity = _complexity; return self; @end 12
Adattárolás a memóriában Adatok (objektumok) tárolására használjuk valamelyiket az alábbiak közül: NSArray - tömb, mérete és tartalma a létrehozáskor eldől NSMutableArray - változó tartalmú és méretű tömb NSDictionary - kulcs - érték párokat tartalmazó tároló NSMutableDictionary - előzőhöz hasonló, változó méretű és tartalmú tároló 13 CookBookManager Osztály Készítsünk egy új osztályt (CookBookManager), amely kezeli a receptekkel kapcsolatos műveleteket, és tárolja az adatokat (CookBookManagerh) @interface CookBookManager : NSObject { NSMutableArray* hot; NSMutableArray* favorites; NSMutableArray* categories; NSMutableDictionary* recipesbycategories; + (CookBookManager*) getinstance; - (NSArray*) gethot; - (NSArray*) getcategories; - (NSArray*) getrecipesbycategory: (NSString*) category; - (NSArray*) getfavourites; @end 14 CookBookManager Osztály CookBookManagerm Singleton minta kezelése static CookBookManager *instance = nil; + (CookBookManager*) getinstance { if (instance==null){ @synchronized([uiapplication sharedapplication]){ if (instance==null) instance=[[cookbookmanager alloc] init]; return instance; 15
CookBookManager CookBookManagerm Konstruktor - (id) init { self = [super init]; if(self) { hot = [[NSMutableArray alloc] initwithcapacity:5]; favorites = [[NSMutableArray alloc] initwithcapacity:5]; categories = [[NSMutableArray alloc] initwithcapacity:5]; recipesbycategories = [[NSMutableDictionary alloc] init]; [categories addobject:@"levesek"]; [categories addobject:@"előtelek"]; [categories addobject:@"húsételek"]; Recipe* r = [[Recipe alloc] initwithtitle:@"bableves" andsubtitle:@"mari néni receptje alapján" anddescription:@"leírás" andcomplexity:@"30 perc"]; Recipe* r2 = [[Recipe alloc] initwithtitle:@"pulyka" andsubtitle:@"rózsi néni receptje alapján" anddescription:@"leírás" andcomplexity:@"45 perc"]; 16 CookBookManager CookBookManagerm Konstruktor NSMutableArray* soups = [[NSMutableArray alloc] initwithcapacity:5]; [soups addobject:r]; NSMutableArray* meats = [[NSMutableArray alloc] initwithcapacity:5]; [meats addobject:r2]; [recipesbycategories setvalue:soups forkey:@"levesek"]; [recipesbycategories setvalue:meats forkey:@"húsételek"]; [favorites addobject:r2]; [hot addobject:r]; return self; 17 CookBookManager CookBookManagerm Lekérdező metódusok - (NSArray*) gethot { return hot; - (NSArray*) getcategories { return categories; - (NSArray*) getrecipesbycategory: (NSString*) category { return [recipesbycategories objectforkey:category]; - (NSArray*) getfavourites { return favorites; 18
Hozzáférés az adatokhoz Minden controllerben használjuk a singleton CookBookManagert Táblázat feltöltéséhez a UITableView néhány metódusát kell implementálni: numberofrowsinsection - megadja hány sort kell a táblázatnak megjeleníteni cellforrowatindexpath - az adott cella indexre visszaad egy cella nézetet, ami lehet bármi, akár egy tetszőleges saját nézet implementáció Töltsük fel minden listában a cellák adatait a megfelelő tartalommal Használjuk egyelőre a manager osztályban definiált mock tartalmat 19 CategoryViewController Kiegészítjük a múlt órán megírt kontrollert a modell rész használatával: - (NSInteger)tableView:(UITableView *)tableview numberofrowsinsection:(nsinteger)section { return [[[CookBookManager getinstance] getcategories] count]; 20 CategoryViewController Kiegészítjük a múlt órán megírt kontrollert a modell rész használatával: - (UITableViewCell *)tableview:(uitableview *)tableview cellforrowatindexpath:(nsindexpath *)indexpath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableview dequeuereusablecellwithidentifier:cellidentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initwithstyle:uitableviewcellstyledefault reuseidentifier:cellidentifier] autorelease]; cellimageviewimage=[uiimage imagenamed:@"categorygif"]; cellaccessorytype=uitableviewcellaccessorydetaildisclosurebutton; celltextlabeltext = [[[CookBookManager getinstance] getcategories] objectatindex:indexpathrow]; return cell; 21
Receptlista A fentiek elkészülte után most már megjelennek a kategóriák, úgy ahogy a modell részben meghatározásra kerültek Csináljuk meg ugyanazt a recept listával is Ez már egy kicsit összetettebb hiszen a receptlista van mind az ajánlatoknál, mind a kategóriákon belül Ezért itt felveszünk egy @propertyt, ami azt tárolja van e kiválasztott kategória Ha nincs akkor az ajánlatokat jelenítjük meg Azért nem a konstruktort írjuk át, mert a tabokat nem mi példányosítjuk kódból 22 RecipeListViewController Felvesszük a h fájlba a propertyt és a segéd változót: @interface RecipeListViewController : UITableViewController { NSString* selectedcategory; NSArray* recipes; @property (nonatomic, retain) NSString* selectedcategory; @property (nonatomic, retain) NSArray* recipes;; @end 23 RecipeListViewController Az m fájlban megjelenéskor lekérjük az új listát, majd felhasználjuk: - (void)viewwillappear:(bool)animated { if (selfselectedcategory=nil){ selfrecipes=[[cookbookmanager getinstance] getrecipesbycategory:selfselectedcategory]; else { selfrecipes=[[cookbookmanager getinstance] gethot]; - (NSInteger)tableView:(UITableView *)tableview numberofrowsinsection:(nsinteger)section { if(selfrecipes=nil) { return [recipes count]; else return 0; 24
RecipeListViewController // Customize the appearance of table view cells - (UITableViewCell *)tableview:(uitableview *)tableview cellforrowatindexpath: (NSIndexPath *)indexpath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableview dequeuereusablecellwithidentifier:cellidentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initwithstyle:uitableviewcellstylesubtitle reuseidentifier:cellidentifier] autorelease]; cellimageviewimage=[uiimage imagenamed:@"recipejpeg"]; cellaccessorytype=uitableviewcellaccessorydetaildisclosurebutton; if(selfrecipes = nil) { Recipe* r = [recipes objectatindex:indexpathrow]; celltextlabeltext = rtitle; celldetailtextlabeltext= rsubtitle; return cell; 25 RecipeViewController Felvesszük a h fájlban a recept elemeit (ne felejstük el IB-ben bekötni): @interface RecipeViewController : UIViewController { UILabel* recipetitle; UILabel* recipesubtitle; UILabel* complexity; UITextView* description; Recipe* recipe; @property (nonatomic,retain) IBOutlet UILabel* recipetitle; @property (nonatomic,retain) IBOutlet UILabel* recipesubtitle; @property (nonatomic,retain) IBOutlet UILabel* complexity; @property (nonatomic,retain) IBOutlet UITextView* description; @property (nonatomic,retain) Recipe* recipe; @end 26 RecipeViewController Az m fájlban megjelenéskor betöltjük a mezőket - (void)viewwillappear:(bool)animated { if(selfrecipe = nil){ selftitle = selfrecipetitle; selfrecipetitletext = selfrecipetitle; selfrecipesubtitletext = selfrecipesubtitle; selfcomplexitytext = selfrecipecomplexity; selfdescriptiontext = selfrecipedescription; [super viewwillappear:animated]; 27
Kattintások kezelése Mind a kategóriák, mind a receptlista, mind a receptek már képesek megjelenni, de még nem készítettük el, hogy egy sorra való kattintáskor működjön is Lényegében a didselectrowatindexpath hívást kell bővítenünk Eddigi ismereteink alapján ezt próbáljuk meg önállóan megcsinálni 28 CategoryViewController Megfelelően felparaméterezzük a receptlistát: - (void)tableview:(uitableview *)tableview didselectrowatindexpath: (NSIndexPath *)indexpath { RecipeListViewController *recipelistviewcontroller = [[RecipeListViewController alloc] initwithnibname:@"recipelist" bundle:nil]; NSString* category = [[[CookBookManager getinstance] getcategories] objectatindex:indexpathrow]; recipelistviewcontrollertitle = category; recipelistviewcontrollerselectedcategory = category; [selfnavigationcontroller pushviewcontroller:recipelistviewcontroller animated:yes]; [recipelistviewcontroller release]; 29 RecipeListViewController Megfelelően felparaméterezzük a recept megjelenítőt: - (void)tableview:(uitableview *)tableview didselectrowatindexpath: (NSIndexPath *)indexpath { RecipeViewController *recipeviewcontroller = [[RecipeViewController alloc] initwithnibname:@"recipe" bundle:nil]; recipeviewcontrollerrecipe = [recipes objectatindex:indexpathrow]; [selfnavigationcontroller pushviewcontroller:recipeviewcontroller animated:yes]; [recipeviewcontroller release]; 30
Hol tartunk most? 31 II Hálózatkezelés 32 Iphone a hálózaton Az Iphone része az IP hálózatnak amennyiben A felhasználónak van internethasználat engedélyezve a mobilszolgáltatónál (ez csak mobilhálózatra vonatkozik) Van megfelelő mobilhálózat Van elérhető Wifi a környezetben Mobile Network, 3G, Edge, etc Wireless LAN Ip address is granted by the current network provider 33
Áttekintés BSD Sockets, OpenSSL WebKit, CFNetwork Bonjour - itunes, ichat, printers, music sharing Peer2Peer - GameKit, Bluetooth, Bonjour ASIHTTPRequest - Third party library, POST 34 UIWebView Internetes oldalak egyszerű beépítése tetszőleges helyre WebView elérhető az InterfaceBuilderben Támogatott formátumok a HTML-en kívül: Excel, Keynote, Numbers, Pages, PDF, Powerpoint, Word, MHTML MS Office documentumok Word 97 formátumban működnek csak IPhone OS 30: RTF, Keynote, Number, Pages 09 verziók 35 NsUrlConnection Különböző protokollokat támogat Szinkron és aszinkron módon is tud működni, alapértelmezett üzemmód aszinkron A kérés kiszolgálásának folyamatát lehet követni, file fel/ letöltés hol tart, becslés számítása, stb Feladatai: autentikáció, protokoll implementáció, cacheelés, cookie-k 36
ASIHTTPRequest NsUrlConnection körülményes Open source könyvtár Aszinkron és szinkron módon is tud működni HTTP post és file feltöltést egyszerűvé teszi NSURL *url = [NSURL URLWithString:@"http://wwwpontehu"]; *request = [ASIHTTPRequest requestwithurl:url]; http://allseeing-icom/asihttprequest/ [request startsynchronous]; NSError *error = [request error]; if (error) { NSLog(@"Error connecting %@", error); else { NSLog(@"Response arrived: %@", [request responsestring]); 37 JSON Javascript Object Notation Általános eszköz a webes technológiákban Tömörebb, hatékonyabb, mint az XML Jól használható Iphone alkalmazásokban, ha egy webes alkalmazáshoz kell kapcsolódni, könnyű az integráció { "firstname": "John", "lastname": "Smith", "age": 25, "address":{ "streetaddress": "21 2nd Street", "city": "New York", "state": "NY", "postalcode": "10021", "phonenumber":[{"type": "home","number": "212 555-1234", {"type": "fax", "number": "646 555-4567"] 38 JSON értelmezése JSON Api kiegészíti az NSString interface-t JSON feldolgozása innentől az NSString osztály JSONValue metódusa lesz Visszaadhat NSArray, NSDictionary a választól függően NSArray* recipesjson = [[request responsestring] JSONValue]; NSMutableArray* recipeslist = [NSMutableArray arraywithcapacity: [recipesjson count]]; for (NSDictionary* dict in recipesjson) { Recipe* r = [Recipe recipewithdictionary:dict]; [recipeslist addobject:r]; 39
Külső komponensek http://pontehu/oktatas/utilszip Húzzuk be a Classes alá a utils könyvtárat Mindenképpen másoljuk be az állományokat a projekt alá Adjuk hozzá a projekthez a következő keretrendszereket: CFNetwork CoreGraphics MobileCoreFramework SystemConfiguration libz123dylib 40 Folytassuk a szakácskönyvet Egészítsük ki a CookBookManager osztályunkat, hogy az adatokat mostantól a webes szerverünkről töltse le Ehhez hozzunk létre modell osztályokat, illetve egészítsük ki a már meglévőket, hogy a megfelelő adatokat tudjuk tárolni Használjuk az ASIHTTP API-t a szerverhívásokhoz A beérkező JSON választ dolgozzuk fel a JSON API segítségével Hozzunk létre objektumokat a JSON adatok alapján 41 Az interface Három nézetünk van jelenleg, amihez szerver kommunikáció szükséges, ezekhez pedig a következő adatokat kell lekérdeznünk: Aktuális ajánlatok Kategóriák Egy adot kategóriához tartozó receptek Tetszőleges recept az azonosítója alapján 42
A szerver Az alkalmazás szerver a következő lekérdezéseket támogatja: http://cookbookserverappspotcom/gethots http://cookbookserverappspotcom/getrecipe?recid=%@ http://cookbookserverappspotcom/getcategories http://cookbookserverappspotcom/getrecipesbycategory?catid=%@ Próbáljuk ki, ha beírjuk a böngészőbe mit kapunk, nézzük meg a forrást is 43 Kibővítjük a Recipe osztályt Megjelent az imageurl mező Készítsünk egy factory metódust ami a JSON adatokból létrehoz egy objektum példányt NSString* imageurl; + (id) recipewithdictionary: (NSDictionary*) dict; - (id) initwithtitle:(nsstring*)_title andsubtitle:(nsstring*) _subtitle anddescription:(nsstring*)_description andcomplexity: (NSString*)_complexity andimageurl:(nsstring*)_imageurl; @property (nonatomic, retain) NSString* imageurl; 44 Kibővítjük a Recipe osztályt Vezessük át az új mezőt (property, konstruktor, synthesize, dealloc), valamint vegyük fel a factory metódust: + (id) recipewithdictionary: (NSDictionary*) dict { Recipe* r = [[[Recipe alloc] init] autorelease]; rtitle = [dict objectforkey:@"title"]; rsubtitle = [dict objectforkey:@"subtitle"]; rdescription = [dict objectforkey:@"description"]; rcomplexity = [dict objectforkey:@"complexity"]; rimageurl = [dict objectforkey:@"imageurl"]; return r; 45
Category Létrehozunk egy Category osztályt is: @interface Category : NSObject { NSString* catid; NSString* name; NSString* imageurl; @property (nonatomic, retain) NSString* catid; @property (nonatomic, retain) NSString* name; @property (nonatomic, retain) NSString* imageurl; + (id) categorywithdictionary: (NSDictionary*) dict; @end 46 Category @implementation Category @synthesize name, catid, imageurl; + (id) categorywithdictionary: (NSDictionary*) dict { Category* cat = [[[Category alloc] init] autorelease]; catname = [dict objectforkey:@"name"]; catcatid = [dict objectforkey:@"catid"]; catimageurl = [dict objectforkey:@"imageurl"]; return cat; -(void)dealloc{ [name release]; [catid release]; [imageurl release]; [super dealloc]; @end 47 CookBookManager Kibővítjük a metódusokat hálózati kommunikcióval - (NSArray*) gethotrecipes { NSURL *url = [NSURL URLWithString:@"http:// cookbookserverappspotcom/gethots"]; ASIHTTPRequest *request = [ASIHTTPRequest requestwithurl:url]; [request setdefaultresponseencoding:nsutf8stringencoding]; [request startsynchronous]; NSError *error = [request error]; if (error) { NSLog(@"Error getting hot recipes from server: %@", error); return nil; else { NSArray* recipesjson = [[request responsestring] JSONValue]; NSMutableArray* recipeslist = [NSMutableArray arraywithcapacity:[recipesjson count]]; for (NSDictionary* dict in recipesjson) { Recipe* r = [Recipe recipewithdictionary:dict]; [recipeslist addobject:r]; return recipeslist; 48
CookBookManager - (NSArray*) getcategories { NSURL *url = [NSURL URLWithString:@"http:// cookbookserverappspotcom/getcategories"]; ASIHTTPRequest *request = [ASIHTTPRequest requestwithurl:url]; [request setdefaultresponseencoding:nsutf8stringencoding]; [request startsynchronous]; NSError *error = [request error]; if (error) { NSLog(@"Error getting categories from server: %@", error); return nil; else { NSArray* categoriesjson = [[request responsestring] JSONValue]; NSMutableArray* categorylist = [NSMutableArray arraywithcapacity:[categoriesjson count]]; for (NSDictionary* dict in categoriesjson) { Category* c = [Category categorywithdictionary:dict]; [categorylist addobject:c]; return categorylist; 49 CookBookManager - (NSArray*) getrecipesbycategory: (NSString*) catid { NSURL *url = [NSURL URLWithString:[NSString stringwithformat:@"http://cookbookserverappspotcom/ getrecipesbycategory?catid=%@", catid]]; ASIHTTPRequest *request = [ASIHTTPRequest requestwithurl:url]; [request setdefaultresponseencoding:nsutf8stringencoding]; [request startsynchronous]; NSError *error = [request error]; if (error) { NSLog(@"Error getting recipes for category from server: %@", error); return nil; else { NSArray* recipesjson = [[request responsestring] JSONValue]; NSMutableArray* recipeslist = [NSMutableArray arraywithcapacity:[recipesjson count]]; for (NSDictionary* dict in recipesjson) { Recipe* r = [Recipe recipewithdictionary:dict]; [recipeslist addobject:r]; return recipeslist; 50 CookBookManager - (Recipe*) getrecipebyid: (NSString*) recid { NSURL *url = [NSURL URLWithString:[NSString stringwithformat:@"http://cookbookserverappspotcom/getrecipe? recid=%@", recid]]; ASIHTTPRequest *request = [ASIHTTPRequest requestwithurl:url]; [request setdefaultresponseencoding:nsutf8stringencoding]; [request startsynchronous]; NSError *error = [request error]; if (error) { NSLog(@"Error getting recipe from server: %@", error); return nil; else { NSDictionary* dict = [[request responsestring] JSONValue]; return [Recipe recipewithdictionary:dict]; 51
Controllerek Módosítsuk a kontrollereinket, hogy a plussz adatokat is megjelenítsék (CategoryViewController) - (UITableViewCell *)tableview:(uitableview *)tableview cellforrowatindexpath:(nsindexpath *)indexpath { Category* category=[[[cookbookmanager getinstance] getcategories] objectatindex:indexpathrow]; NSURL *url = [NSURL URLWithString:catimageURL]; NSData *data = [NSData datawithcontentsofurl:url]; UIImage *img = [UIImage imagewithdata:data]; cellimageviewimage = img; celltextlabeltext = catname; return cell; 52 Controllerek Módosítsuk a kontrollereinket, hogy a plussz adatokat is megjelenítsék (RecipelistViewController) - (UITableViewCell *)tableview:(uitableview *)tableview cellforrowatindexpath:(nsindexpath *)indexpath { Recipe* r = [recipes objectatindex:indexpathrow]; celltextlabeltext = rtitle; celldetailtextlabeltext= rsubtitle; NSURL *url = [NSURL URLWithString:rimageURL]; NSData *data = [NSData datawithcontentsofurl:url]; UIImage *img = [UIImage imagewithdata:data]; cellimageviewimage = img; return cell; 53 A recept Hozzunk létre egy UIImageView típusú adattagot a recept nézet controllerébe Kössük be az Interface Builderben a képhez tartozó elemet is - (void)viewwillappear:(bool)animated { if(selfrecipe = nil){ selfrecipetitletext = selfrecipetitle; selfrecipesubtitletext = selfrecipesubtitle; selftimetomaketext = @"30 perc"; selfdescriptiontext = selfrecipedescription; NSURL *url = [NSURL URLWithString:recipeimageURL]; NSData *data = [NSData datawithcontentsofurl:url]; UIImage *img = [UIImage imagewithdata:data]; selfimageviewimage = img; [super viewwillappear:animated]; 54
A memória felszabadítása Ne feledjük el felszabadítani a lefoglalt memóriát A nézetekben tárolt listákat végül magunknak kell felszabadítani Receptlista nézetben a recepteket Recept nézetben a receptet - (void)dealloc { [recipes release]; [super dealloc]; 55 Próbáljuk ki 56 Köszönöm a figyelmet Sallai Péter petersallai@pontehu 57