iPhone OS – tutorial część 4

Wstęp

W tej części samouczka postaram się pokazać jak stworzyć aplikację natywną wykonującą proste obliczenia na liczbach. Będę starał się nie pominąć żadnych ważnych elementów. Jeżeli jednak zdarzyłoby się coś niejasnego dajcie znać w komentarzach.

W tej części poznacie między innymi jak:

  1. Stworzyć nowy projekt – w ramach przypomnienia
  2. Stworzyć widok
  3. Stworzyć kontroler widoku
  4. Obsłużyć zdarzenia
  5. Dodać widok do okna
  6. Połączyć obiekt w widoku z obiektem w kontrolerze

Nowy Projekt

Zacznijmy od otwarcia środowiska XCode i przeskoczeniu ewentualnego ekranu witającego. Tworzymy nowy projekt wybierając z menu File -> New Project i wybraniu Window-Based Application z grupy iPhone OS. Wybieramy nazwę projektu „BaseCalculator” i zapisujemy. Użyta nazwa projektu będzie później wykorzystywana, dlatego jak wybierzesz inną pamiętaj o tym.

Część z plików została stworzona już automatycznie. Jednym z nich jest plik main.m, który posiada tylko jedną metodę, przyjmującą dwa argumenty i zwracający zmienną typu int.

#import 

int main(int argc, char *argv[]) {

	NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	int retVal = UIApplicationMain(argc, argv, nil, nil);
	[pool release];
	return retVal;
}
Główna funkcja aplikacji dla iPhona

Główna funkcja aplikacji dla iPhona

Metoda ta będzie miała taki sam kształt, w prawie wszystkich aplikacjach na telefon iPhone.

Kolejnym ważnym plikiem stworzonym automatycznie jest delegator aplikacji, w tym projekcie nazywa on się BaseCalculatorAppDelegate. Definicja klasy jest w pliku o rozszerzeniu .h a implementacja w pliku .m. Tak stworzony projekt można uruchomić kombinacją Cmd + B lub klikając Buil and Go. To co się pokaże to pusty ekran bez specjalnych funkcjonalności.

Warte tu przypomnienia są dwie rzeczy, o których trzeba pamiętać projektując aplikację na iPhona:

  1. Aplikacja posiada tylko jedno okno. W programie tworzymy tylko widoki, które pokazujemy lub ukrywamy
  2. iPhone OS nie wspiera zarządzaniem poprzez garbage collection.

Tworzenie Widoku

W celu stworzenia widoku, musimy stworzyć plik o rozszerzeniu xib. Pliki nib służą do projektowania interfejsu używając programu Interface Builder, dostarczanego wraz z SDK. Jeden taki plik mamy już w projekcie stworzony automatycznie, który opisuje główne okno. To samo, które można obejrzeć po uruchomieniu projektu w symulatorze. By edytować ten plik wystarczy dwa razy kliknąć na MainWindow.xib

MainWindow.xib posiada już cztery obiekty (screenshot ma jeden dodatkowy o którym za chwilę):

Obiekty w Interface Builderze

Obiekty w Interface Builderze

  1. File’s Owner – jest to obiekt reprezentujący aplikację (UIApplication)
  2. First Responder – Jest to obiekt przechwytujący zdarzenia multi-touch (pol. zdarzenia wielodotykowe).
  3. Base Calculator App Delegate – Obiekt odpowiedzialny za wyświetlenie okna i zainicjalizowanie widoku. Jest także odpowiedzialny za przywrócenie aplikacji do poprzedniego stanu, przechwytywanie ostrzeżeń o braku pamięci, odpowiadanie na zmiany orientacji urządzenia. Obiekt ten jest powiązany z klasą BaseCalcultor App Delegate. Można to sprawdzić klikając w menu Tools -> Identity Inspector.
  4. Window – obiekt reprezentujący okno, które widać w aplikacji

W celu stworzenia nowego widoku używając Interface Buildera, klikamy w menu File -> New i dalej View. Następnie pokaże się nowy widok. Po pierwsze musimy zapisać widok w naszym projekcie Xcode. W tym celu klikamy w menu File -> Save, wybieramy katalog naszego projektu, nazywamy widok „BaseCalculatorView” i klikamy save. Jak IB zapyta do jakiego projektu dodać widok, należy wybrać projekt, który właśnie tworzymy.

Wróć do Xcode i przeciągnij stworzony widok do grupy/katalogu resources. Robimy to by zachować porządek ;)

Powróćmy do Interface Buildera. Dodamy teraz parę kontrolek do stworzonego widoku. Najpierw przywołajmy Bibliotekę do środowiska, klikając w menu Tools -> Library. Następnie dodaj dwa pola tekstowe (Text Fields), jedną etykietę (Label) i jeden przycisk (Button). Po tych zabiegach widok powinien wyglądać mniej więcej tak:

Widok po ustawieniu elementów

Widok po ustawieniu elementów

Inspektor atrybutów intefejsu iPhona

Inspektor atrybutów interfejsu iPhona

Ustawmy parę właściwości w użytych kontrolkach. Zaznacz pierwsze pole tekstowe i wybierz Attributes Inspector ( menu: Tools -> Attributes Inspector). Ustaw atrybuty:

  • Placeholder 2 – podpowiedź w polu
  • Phone PadNumber – typ klawiatury
  • Return-keyNext – klawisz zatwierdzający wpisane dane
  • Clear Context before Drawing – wyczyści zawartość pola przed wyświetleniem

Te same ustawienia zastosuj do drugiego pola. Zmień tylko Return-key na Done. Kliknij dwukrotnie na przycisku i wpisz „Dodaj”. Można także wykonać to w Attributes Inspector zmieniając atrybut Title. Zaznacz etykietę i upewnij się, że zaznaczone jest Clear Context before Drawing. Zapisz widok. Tym samym zakończyliśmy projektowanie pierwszego widoku.

Tworzenie kontrolera

Przyszedł czas na wykonanie kontrolera obsługującego naszą aplikację. Odpowiedzialny będzie on za obsługę akcji wykonywanych przez użytkownika, nawigację oraz zarządzanie pamięcią. Każdy widok w aplikacji musi być połączony z takim kontrolerem, który będzie obsługiwał funkcjonalności. Klasą odpowiedzialną w SDK za obsługę widoków jest UIViewController dostarczająca podstawowe funkcjonalności. W celu stworzenia kontrolera w XCode wybieramy File -> New File wybierając jako typ pliku UIViewController subclass. Jako nazwę wybieramy „BaseCalculatorViewController”.
Następnie trzeba stworzyć zmienne dla elementów stworzonych w widoku. Pozwoli to na połączenie widoku z kontrolerem oraz na sterowanie widokiem. W tym celu otwórz nowo utworzony plik BaseCalculatorViewController.h i dodaj kod:

#import 

@interface BaseCalculatorViewController : UIViewController {
    IBOutlet UITextField *firstNumber;
    IBOutlet UITextField *secondNumber;
    IBOutlet UILabel *result;
    NSInteger fNumber, sNumber;
}

@property (nonatomic, retain) UITextField *firstNumber;
@property (nonatomic, retain) UITextField *secondNumber;
@property (nonatomic, retain) UILabel *result;
@property (nonatomic) NSInteger fNumber;
@property (nonatomic) NSInteger sNumber;

-(IBAction)displayMessage:(id)sender;

@end

Słowo IBOutlet mówi kompilatorowi, że dana zmienna ma być widoczna dla Interface Buildera. Linijki kodu poprzedzone @property definiują właściwości klasy. Atrybut retain oznacza, że przy przypisywaniu obiekt jest zachowywany.
Zdefiniowana metoda posłuży przechwyceniu zdarzenia, kliknięcia ma klawiszu.

Obsługa zdarzeń

Wykorzystamy teraz zadeklarowaną w pliku nagłówkowym metodę. Metoda ta jest zadeklarowana ze słówkiem IBAction, które udostępnia ją Interface Builderze. Metoda ta przyjmuje za parametr uniwersalny wskaźnik obiektu, który ją wywołał, a zwraca typ void stowarzyszony ze słówkiem IBAction. Teraz przyszedł czas na zaimplementowanie metody obliczającej sumę oraz na dołożenie setterów/getterów dla właściwości klasy z użyciem słówka @synthesize (tworzy magiczne gettery/ssettery). W tym celu otwórz plik BaseCalculatorViewController.m i dodaj brakujące linie z poniższego listingu.

@implementation BaseCalculatorViewController

@synthesize firstNumber, secondNumber, result, fNumber, sNumber;

-(IBAction)displayMessage:(id)sender
{
    self.fNumber = [firstNumber.text integerValue];
    self.sNumber = [secondNumber.text integerValue];
    NSString *tmpString = nil;
    if(self.fNumber == 0 || self.sNumber == 0) {
         tmpString = [[NSString alloc] initWithFormat:@"Insert some number"];
    } else {
         tmpString = [[NSString alloc] initWithFormat:@"Result: %d",
                            (self.fNumber + self.sNumber)];
    }
    result.text = tmpString;
    [tmpString release];
}

W celach edukacyjnych została tu nadmiarowo stworzona zmienna tmpString. Pokazuje ona jak alokować (alloc), inicjalizować (initWithFormat) i uwalniać pamięć (release). Przy użyciu metody initWithFormat można tworzyć sformatowane ciągi znaków. %@ jest podmieniany na parametr podany dalej. Można też używać innych znaków formatujących np. %s, %d występujące w funkcji printf z języka C. Na końcu metody uwalniam zmienną, którą alokowałem w tej metodzie.

W stworzonym kontrolerze mamy kilka właściwości o atrybucie retain, które powinniśmy uwolnić z pamięci. Robimy to w metodzie dealloc jak poniżej:

- (void)dealloc
{
    [firstNumber release];
    [secondNumber release];
    [result release];
    [super dealloc];
}

Dodanie widoku do okna

Kolejnym krokiem jest dodanie widoku jako podwidoku istniejącego okna. W tym celu otwórz plik BaseCalculatorAppDelegate.h i stwórz zmienną typu BaseCalculatorViewController. Ponieważ używamy typu nieznanego kompilatorowi trzeba zadeklarować ten typ używając słówka @class.

#import 

@class BaseCalculatorViewController;

@interface BaseCalculatorAppDelegate : NSObject  {
    IBOutlet UIWindow *window;
    BaseCalculatorViewController *viewController;
}

@property (nonatomic, retain) UIWindow *window;
@property (nonatomic, retain) BaseCalculatorViewController *viewController;

@end

W pliku implementującym (.m) wykorzystamy metodę applicationDidFinishLaunching w celu przypisania stworzonego widoku do okna. Metoda ta jest wywoływana jak tylko aplikacja zakończy proces uruchamiania się. Powinniśmy wykorzystywać ją do ustawiania pierwszego widoku i przywracania stanu aplikacji.

#import "BaseCalculatorAppDelegate.h"
#import "BaseCalculatorViewController.h"

@implementation BaseCalculatorAppDelegate

@synthesize window, viewController;

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    //załadowanie widoku z bliku nib
    BaseCalculatorViewController *vController = [[BaseCalculatorViewController alloc]
                                       initWithNibName:@"BaseCalculatorView"
                                       bundle:[NSBundle mainBundle]];
    //ustawienie widoku
    self.viewController = vController;

    // uwolnienie zmiennej
    [vController release];

    //dodanie widoku do okna
    [window addSubview:[viewController view]];

    //uwidocznienie widoku
    [window makeKeyAndVisible];
}

- (void)dealloc {
    [viewController release];
    [window release];
    [super dealloc];
}

@end

Połączenie elementów w widoku z obiektami w kontrolerze widoku

Na początku potrzebujemy połączyć elementy widoku z obiektami, które stworzyliśmy. W tym celu dwukrotnie kliknij na BaseCalculatorView.xib, który otworzy Interface Buildera.
Następnie połącz kontroler widoku z obiektem File’s Owner Proxy. W tym celu zaznacz File’s Owner i wybierz Identity Inspector w polu Class wybierz z listy BaseCalculatorViewController. Warto zauważyć, że poniżej są też wyświetlone zmienne zdefiniowane w pliku nagłówkowym.
Teraz połączymy kontroler widoku z widokiem. W tym celu kliknij na Connections Inspector, wyświetli ci się widok podobny do poniższego:

Connection inspector

Connection inspector

Obok zmiennej view jest puste kółko co oznacza, że nie jest połączone. Kliknij kółko przy view i przeciągnij na widok i puść klawisz. Zamalowane kółko będzie informowało o połączeniu. Podobnie postąp z elementami result, firstNumber, secondNumber łącząc je z odpowiednimi elementami na widoku.
Ostatnie połączenie jakie musimy wykonać to połączenie metody wyświetlającej wynik z akcją kliknięcia przycisku. W tym celu zaznacz przycisk i wybierz connection inspector. Znajdź zdarzenie „Touch Up Inside” oznaczające zakończenie kliknięcia (dotyku). Zaznacz puste kółko i przeciągnij je na obiekt File’s Owner. Kiedy zwolnisz klawisz pojawi się metoda, displayMessage, wybierz ją, a stworzysz połączenie.
Wróć do Xcode skompiluj i uruchom Build and Go. Aplikacja powinna już działać, ale pojawi się pewna niedogodność. Mianowicie po wpisaniu liczb klawiatura nie będzie się chowała :/ By to naprawić trzeba dodać protokół do klasy kontrolera.
Deklaracja interfejsu kontrolera widoku powinna wyglądać teraz tak:

@interface BaseCalculatorViewController : UIViewController <UITextFieldDelegate>  {

Kolejnym krokiem jest ustawienie kontrolera widoku jako odbiorcę zdarzeń pól tekstowych oraz zaimplementowanie metody obsługującej ukrywanie klawiatury:

- (void)viewDidLoad {
    firstNumber.delegate = self;
    secondNumber.delegate = self;
}

- (BOOL)textFieldShouldReturn:(UITextField *)theTextField {
     if(theTextField == firstNumber || theTextField == secondNumber) {
         [firstNumber resignFirstResponder];
         [secondNumber resignFirstResponder];
     }
     return YES;
}

W metodzie viewDidLoad następuje ustawienie kontrolera jako obiektu odbierającego zdarzenia pól tekstowych. Natomiast w metodzie textFieldShouldReturn sprawdzamy z jaki obiekt wywołał metodę, jeżeli te obiekty mogą powodować ukrycie klawiatury wysyłamy wiadomość resignFirstResponder do obiektów oraz zwracamy YES.

Pozostało jeszcze ustawienie delegacji dla pól tekstowych na File’s Owner (obiekt proxy dla naszego kontrollera). W tym celu w Interface Builderze wybieramy widok i klikamy na pierwszym polu tekstowym i wchodzimy do Connections Inspector. Znajdziemy tam pole delegate. Kliknij na nim i przeciągnij na obiekt File’s Owner i puść. Zrób tak samo z drugim polem.
Zapisz zmiany i uruchom aplikację w Xcode.

Koniec

Tym samym stworzyłeś swoją pierwszą aplikację na iPhona. Jeżeli coś było nie zrozumiałe daj znać w komentarzu, lub napisz na maila kamilmaras [malpeczka] gmail [kropeczka] pl.

24 myśli nt. „iPhone OS – tutorial część 4

  1. Andrzej

    Przydal mi sie ten tutorial bo zaczynam sie uczyc programowania pod iPhone i jezyk jest dla mnie duza trudnoscia. W twoim tekscie jest jednak sporo bledow i aplikacja sie nie kompiluje od razu. Oto kilka ktore wylapalem:

    1. W pliku BaseCalculatorAppDelegate.m w funkcji applicationDidFinishLaunching powinna byc uzyta klasa BaseCalculatorViewController a nie BaseViewController.

    2. W pliku BaseCalculatorViewController.h property fNumber i sNumber nie moga miec atrybutu copy gdyz nie sa obiektami.

    3. W pliku BaseCalculatorViewController.h zmienne *fNumber i *sNumber nie moga byc wskaznikami gdyz powoduje to bledy pozniej w funkcji displayMessage.

    4. W pliku BaseCalculatorViewController.m w funkcji dealloc nalezy usunac linie [sNumber release]; i [fNumber release]; gdyz tych typow nie mozna zwalniac.

    5. W pliku BaseCalculatorViewController.m property UITextField *result; powinno byc typu UILabel.

    Po naprawieniu tych rzeczy dziala mi wszystko poza tym ostatnim trickiem do chowania klawiatury. Kompilator wyswietle mi warning na linii [secondNumber resignFirstReponder]; mowiac:
    Messages without a matching method signature will be assumed to return „id” and accept ‚…’ as arguments.
    Wlasciwie to nie widze tez przyciskow „Next” i „Done” ktorych sie tak jakby spodziewalem gdy wchodzi sie w pola tekstowe wiec mam przeczucie ze tutaj tez cos jest nie tak.

  2. sparhawk Autor wpisu

    Dzięki wielkie za komentarz, cieszę się, że pomógł :)
    Poprawiłem błędy, na które zwróciłeś mi uwagę.

    Co do ostatniej uwagi to literowkę, miałem:
    [secondNumber resignFirstReponder];

    Ogólna wskazówka. Jak widzisz komunikat podobny do poniższego:
    Messages without a matching method signature will be assumed to return “id” and accept ‘…’ as arguments.
    To zazwyczaj oznacza, że nie ma takiej metody w interfejsie, tudzież jest jakaś literówka w wywołaniu, interfejsie lub w implementacji

  3. Paweł Mucha

    Zastanawiam się czy te własne widoki można zapisywać, we własnej strukturze folderów ? Mam na myśli coś jak namespace w .NET ?

    Czy jeśli powiedzmy stworzymy sobie strukture /UI/User/loginView.h
    to czy to będzie widoczne z głównego węzła ./ ?

    Znowu dobra robota, jak będziesz w Warszawie zapraszam na dobre piwo !

    Paweł

  4. sparhawk Autor wpisu

    @Paweł Mucha:
    Możesz stworzyć własne katalogi a pliki i tak będą widoczne jakby w nich nie były, z wyjątkiem gdy chcesz np. wczytać dane z jakiegoś pliku (np. pList) w trakcie uruchomienia. Wtedy trzeba podać ścieżkę do katalogu.
    Oprócz tego w Xcode możesz tworzyć grupy (prawym klawiszem na nazwę projektu lub grupę w prawej części obszaru roboczego Xcode – Overview). Grupy grupują Ci pliki. Ja mam np. grupę Models, Helpers, Resources. Bardzo ułatwia to orientację w plikach. :)

    Mam nadzieję, że pomogłem :)

  5. Paweł Sołyga

    „W celu stworzenia widoku, musimy stworzyć plik o rozszerzeniu xib. Pliki nib służą do projektowania interfejsu używając programu Interface Builder, dostarczanego wraz z SDK.”

    Warto wspomnieć o różnicy między xib a nib. Pliki nib mają format binarny a pliki xib są ich xmlowymi odpowiednikami. Dzięki plikom xib możemy w łatwy sposób śledzić zmiany w interfejsie za pomocą zwykłych narzędzi diff czy też śledząć historię zmian w systemach kontroli wersji takich jak Subversion.

  6. sparhawk Autor wpisu

    Cały potrzebny kod jest w tutorialu. Reszta jest opisana. Jak czegoś nie próbujesz to się nie nauczysz. Po sobie wiem, że jak dostane gotowca to niczego się nie nauczę ;)

    Miłej nauki :)

  7. Łukasz Grabski

    Nie wiem czy wam to zadziałało, w każdym razie ja musiałem zmienić jeszcze jedną rzecz, chyba kluczową bo samo dodawanie:
    poprzedni wpis w metodzie displayMessage:
    tmpString = [[NSString alloc] initWithFormat:@"Result: %@", self.fNumber + self.sNumber];

    powodował crash aplikacji przy testowaniu.
    Zadziałało dopiero po zmianie go na taki:
    tmpString = [[NSString alloc] initWithFormat:@"Wynik: %@", [NSString stringWithFormat:@"%d", (self.fNumber + self.sNumber)]];
    dał rezultat w postaci dodania liczba i właściwego ich wyświetlenia.
    Problemem był tutaj fakt iż zmienne integer próbowaliśmy użyć jako stringu do wpisania do Label’u.

    Cały czas mam jednak problem z chowaniem klawiatury. Nie chce się skubana schować.

  8. sparhawk Autor wpisu

    Co do dodawania liczb nie wystarczyłoby tak:
    [[NSString alloc] initWithFormat:@"Result: %d",
    (self.fNumber + self.sNumber)];

    Co do chowania klawiatury dodaj metodę w BaseCalculatorViewController:

    - (void)viewDidLoad {
        firstNumber.delegate = self;
        secondNumber.delegate = self;
    }
    

    To jest ustawienie obiektu odbierającego zdarzenia dla teks fieldów :)
    Daj znać czy działa.
    Dzięki za poprawki :)

  9. mrt

    zatrzymalem sie tutaj „Wróć do Xcode i przeciągnij stworzony widok do grupy/katalogu resources. Robimy to by zachować porządek ;)” skad mam przeciagnac ten widok?

  10. granat

    witam,

    ja mam problem z „return key” na klawiaturze, takie coś w ogóle nie pojawia się obojętnie jak bym ustawił tą klawiaturę w IB. jak to zrobić, żeby się pojawił? wszystko ustawiłem dokładnie tak jak napisane w tutorialu.

  11. sparhawk Autor wpisu

    Return key ustawiasz na ,,Next” na pewno na dobrym elemencie to ustawiasz? On ma zmienić swoją wartość, a nie pojawić się lub zniknąć (żebyśmy się dobrze zrozumieli)

  12. granat

    wiem, ze powinien pojawić się napis „next” jak ustawię next, ale problem polega na tym, że ja nie mam żadnego klawisza „return” na którym mogło by się cokolwiek pojawić na klawiaturze numerycznej, i który mógłbym nacisnąć (działa tylko „return” z normalnej klawiatury w kompie) jest tylko 11 pól z czego 10 to cyfry i jedno to „backspace” z „x” w środku z lewej strony pozostaje puste pole spośród tych wszystkich 12 klawiszy jakie tam są

  13. sparhawk Autor wpisu

    Szczerze mówiąc do końca nie wiem jaką klawiaturę opisujesz. Tą omawianą wartość trzeba zmienić w „Inspektorze atrybutów” spójrz na screenshota po prawej koło tego tekstu w poście „Return-key – Next – klawisz zatwierdzający wpisane dane”. Tam jednym z ustawień jest „Return-key”. Może spróbuj na wysłać mi opis problemu wraz ze screenshotami. Będę w stanie Ci wtedy pomóc.

  14. kylo

    Mam dokładnie ten sam problem co granat. Też mi się nie pokazuje żaden przycisk umożliwiający zakończenie edycji.

  15. TaW

    Witam
    Ktoś zna może przyczynę dlaczego w ostatniej fazie programu w Connection Inspector nie moge polaczyc first i seconNumber z textfieldami? Label bez problemu polaczyl sie z result a te dwa textfieldy jakby niedostepne sa do połączenia.
    Z góry dzieki za odpowiedź

  16. MaCiek

    Niezły tutek!!
    U mnie wszystko sie ładnie skompilowało bez błędów , tylko aplikacja w symulatorze sie nie włancza. Wychodzi odrazu do menu i wyskakuje „The application Basecalculator quit unexpectedly.” I jak teraz sprawdzic co jest zle w kodzie skoro zaden warning nie wyskoczył przy kompilacji? czarna magia :)

  17. sparhawk Autor wpisu

    Musisz po debugować ;) Takie błędy są najczęściej spowodowane złym zarządzaniem pamięcią, jakąś metodą na obiekcie null itp.

  18. Hellboy

    Ciekawe tutoriale ;) ale dużo łatwiej jest pisać za pomocą tego narzędzia ->
    http://www.dragonfiresdk.com/index.htm

    DragonFIRE, nie trzeba mieć MACa ani umieć Objective-C.
    Z reszta z tym kupowaniem Maca troszkę przegiąłeś bo można zainstalować wirtualną maszynę MacOS i wszystko ładnie działa (oczywiście procesor musi obsługiwać wirtualizację)

  19. mxavier666

    nie trzeba miec Mac’a i mkzna na wirtualnej maszynie zainstalowac Mac OS ale to wiaze sie z poborem duzej ilosci pamieci ram (windows a nq nim jeszcze Mac OS)
    Istnieje jeszcze sposob na instalacje Snow Leopard’a (Mac OS) na PC rownolegle z windowsem , samemu mi sie to udalo zrobic (szulaj na YouTube.pl uzytkownika mxavier666 i sam zobacz) no ale tamten komp sprzedany i teraz iMac zawital :P
    Fajny tutorial .
    Pozdrawiam

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *