Obsługa zdarzeń i logika aplikacji¶
Żeby w wygodny sposób oprogramować zdarzenia, ustaw XCode w taki sposób, żeby jednocześnie mieć dostęp do panelu widoku interfejsu graficznego (Main.storyboard) oraz wymiennie do każdego z dwóch plików źródłowych kontrolera - pliku nagłówkowego (TANGViewController.h) i implementacyjnego (TANGViewController.m). W tym celu możesz posłużyć się panelem Assistant (druga ikona spośród sześciu symboli paneli w górnym pasku narzędziowym, po prawej stronie).
Pierwszym krokiem będzie obsługa elementów interfejsu graficznego, czyli pola tekstowego (wyświetlacz) i przycisków (reprezentujących cyfry i działania). W tym celu będziemy potrzebować pola reprezentujące elementy interfejsu przez które będziemy wyprowadzać informacje (IBOutlet) i metody obsługujące zdarzenia związane z elementem interfejsu związanym z daną metodą (IBAction). Identyfikatory IBOutlet i IBAction stanowią informację dla Interface Buildera. Przed rozpoczęciem kompilacji są przez dyrektywę preprocesora #define zastępowane przez void. Oczywiście odpowiednie deklaracje powinny znaleźć się w pliku nagłówkowym klasy kontrolera, a implementacje metod - w pliku implementacyjnym tej klasy. Odpowiednie wpisy możemy wprowadzić ręcznie i następnie za pomocą Interface Buildera połączyć je z odpowiednimi elementami interfejsu, ale możemy też utworzyć je za pomocą Interface Buildera i dopiero wtedy dopisać implementacje metod. Żeby posłużyć się Interface Builderem, trzeba przeciągnąć myszką od danego elementu interfejsu do odpowiedniego miejsca w pliku nagłówkowym, trzymając jednocześnie naciśnięty klawisz Ctrl na klawiaturze. Jeżeli wybierzemy IBOutlet, to zostanie utworzona odpowiednia deklaracja pola w pliku nagłówkowym, natomiast jeżeli wybierzemy IBAction, to w pliku nagłówkowym zostanie utworzona deklaracja odpowiedniej metody, a w pliku implementacyjnym zostanie utworzona pusta definicja tej metody. Użyj Interface Buildera żeby utworzyć pole IBOutlet dla pola tekstowego reprezentującego wyświetlacz i metody IBAction dla każdego z przycisków działań (plus i równa się). Dla wszystkich przycisków z cyframi napiszemy wspólną metodę IBAction, w związku z czym napiszemy ją ręcznie, a następnie użyjemy Interface Buildera do połączenia tej metody kolejno ze wszystkimi przyciskami reprezentującymi cyfry. W pliku nagłówkowym mamy teraz deklaracje odpowiednich pól i nagłówki odpowiednich metod obsługi zdarzeń.
#import <UIKit/UIKit.h> !
@interface TANGViewController : UIViewController
@property (strong, nonatomic) IBOutlet UITextField *display;
- (IBAction)plus_pressed:(id)sender;
- (IBAction)equals_pressed:(id)sender;
- (IBAction)clear_pressed:(id)sender;
- (IBAction)digit_pressed:(UIButton*)sender;
@end
Jeżeli wskaźnik myszy znajdzie się na symbolu połączenia (kropka na lewym marginesie, obok nagłówka pola albo metody), to Interface Builder będzie wskazywał elementy interfejsu graficznego, które są skojarzone z danym wpisem. Sprawdź, czy wszystkie połączenia zostały wykonane prawidłowo.
W pliku implementacyjnym natomiast mamy (jeszcze) puste definicje odpowiednich metod (pamiętając, że jedną z nich wpisaliśmy ręcznie).
#import "TANGViewController.h"
@interface TANGViewController ()
@end
@implementation TANGViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)plus_pressed:(id)sender
{
}
- (IBAction)equals_pressed:(id)sender
{
}
- (IBAction)clear_pressed:(id)sender
{
}
- (IBAction)digit_pressed:(UIButton*)sender
{
}
@end
Możemy teraz zbudować naszą aplikację i przetestować ją w emulatorze, żeby sprawdzić, czy nasz projekt nie zawiera błędów składniowych. Ponieważ metoda digit_pressed będzie wywoływana po naciśnięciu dowolnego spośród przycisków cyfrowych, będziemy potrzebować informacji który przycisk (cyfra) spowodował jej wywołanie. W tym celu wykorzystamy informacje zapisaną w polu tag obiektu sender, który jest przekazywany do tej metody podczas wywołania (wartość pola tag ustaliliśmy wcześniej, podczas konstruowania interfejsu graficznego) i zapiszemy ją do zmiennej lokalnej.
- (IBAction)digit_pressed:(UIButton*)sender
{
int tag = sender.tag;
}
Podstawowe operacje w kalkulatorze polegają na wprowadzeniu liczby (poprzez wprowadzenie kolejnych cyfr), wybraniu przycisku reprezentującego operator działania (na przykład plus), wprowadzeniu drugiej liczby i wreszcie wybraniu przycisku wyniku. Do przechowywania wprowadzanych liczb będziesz potrzebował dwóch zmiennych (liczby po lewej i po prawej stronie operatora). Ponieważ każdą z tych liczb będziemy składać z kolejnych cyfr, wygodnie będzie użyć do tego celu obiektów klasy NSString, służącej do przechowywania tekstów (łańcuchów znakowych). Zacznij od deklaracji zmiennych w pliku nagłówkowym (.h) kontrolera. Do sekcji @interface dodaj nawiasy klamrowe, reprezentujące blok oraz dodaj odpowiednie deklaracje wskaźników do tych obiektów.
@interface TANGViewController : UIViewController
{
NSString *left_number;
NSString *right_number;
}
Dodatkowo, będziesz także potrzebował zmiennej przechowującej informacje, czy był już naciśnięty przycisk operatora działania, żeby rozróżnić pomiędzy liczbami po lewej i prawej stronie operatora działania, oraz zmiennej do przechowywania informacji który spośród operatorów działań został wybrany. Użyj do tego celu zmiennej typu logicznego (bool) i zmiennej typu znakowego (char). Ta ostatnia będzie przechowywała znak reprezentujący symbol operatora (na przykład plus). Blok interface będzie teraz zawierał cztery deklaracje.
@interface TANGViewController : UIViewController
{
NSString *left_number;
NSString *right_number;
bool operand_pressed;
char operand;
}
Zmienne możemy zainicjować do pożądanych wartości w momencie uruchomienia aplikacji, używając do tego metody viewDidLoad, wywoływanej automatycznie przy pojawieniu się danego interfejsu na ekranie.
- (void)viewDidLoad
{
[super viewDidLoad];
operand_pressed=FALSE;
left_number=NULL;
right_number=NULL;
}
Kolejnym krokiem będzie wprowadzanie kolejnych cyfr i zestawienie z nich liczby po lewej stronie operatora działania (left_number) i po prawej stronie operatora działania (right_number). W tym celu musisz napisać odpowiednią implementację metody digit_pressed. Poniżej znajdziesz przykładową implementację takiej metody.
- (IBAction)digit_pressed:(UIButton*)sender
{
int tag = sender.tag;
if (operand_pressed == FALSE)
{
if (left_number == NULL)
{
left_number = [NSString stringWithFormat:@"%i",tag];
_display.text = left_number;
}
else
{
left_number = [NSString stringWithFormat:@"%@%i",left_number,tag];
_display.text = left_number;
}
}
else
{
if (right_number == NULL)
{
right_number = [NSString stringWithFormat:@"%i",tag];
_display.text = right_number;
}
else
{
right_number = [NSString stringWithFormat:@"%@%i",right_number,tag];
_display.text = right_number;
}
}
}
Żeby obsłużyć przycisk kasowania, musisz napisać odpowiednią definicję metody obsługi tego przycisku. W naszym przypadku kasowanie powinno polegać na usunięciu zawartości wyświetlacza oraz na przygotowaniu kalkulatora do wykonania nowej operacji poprzez ustawienie początkowych zawartości zmiennych operand_pressed, left_number, right_number. Ponieważ zmienną przechowującą informację o wybranym operatorze będziemy zmieniać każdorazowo przyciskiem działania, to nie ma potrzeby ustawiania jakiejś konkretnej wartości tej zmiennej przed rozpoczęciem nowego działania.
- (IBAction)clear_pressed:(id)sender
{
_display.text = NULL;
operand_pressed=FALSE;
left_number=NULL;
right_number=NULL;
}
Pozostały jeszcze do obsłużenia zdarzenia związane z przyciskami wyboru działań oraz przycisk wykonujący działania (równa się). Zacznij od przycisku wyboru działania.
- (IBAction)plus_pressed:(id)sender
{
operand = '+';
operand_pressed = TRUE;
}
Obsługa zdarzenia polegającego na naciśnięciu przycisku wykonania działania (równa się) będzie polegała na wykonaniu parsowaniu tekstów przechowywanych w obiektach left_number i right_number do liczb typu int, wykonaniu odpowiedniej operacji arytmetycznej w zależności informacji przechowywanej w zmiennej operand (znak działania), konwersji wyniku to postaci tekstowej, wstawieniu tej wartości do pola text obiektu reprezentującego wyświetlacz kalkulatora oraz przygotowaniu kalkulatora do nowego działania poprzez ustawienie odpowiednich zmiennych do wartości początkowych.
- (IBAction)equals_pressed:(id)sender
{
int result;
if(operand == '+')
{
result = [left_number intValue] + [right_number intValue];
}
else if(operand == '-')
{
result = [left_number intValue] - [right_number intValue];
}
else if(operand == '*')
{
result = [left_number intValue] * [right_number intValue];
}
else if(operand == '/')
{
result = [left_number intValue] / [right_number intValue];
}
_display.text = [NSString stringWithFormat: @"%i",result];
operand_pressed = FALSE;
first_entry = NULL;
second_entry = NULL;
}
Przetestuj dokładnie swój przykład, przeanalizuj jego działanie i zastanów się w jaki sposób należy go rozwinąć, żeby otrzymać w pełni funkcjonalny kalkulator.
Ćwiczenie 1¶
Zablokuj możliwość wpisywania tekstów do wyświetlacza bezpośrednio z klawiatury ekranowej (ustaw pole tekstowe jako nieedytowalne, albo użyj komponentu Label zamiast pola tekstowego).
Ćwiczenie 2¶
Dodaj przycisk kropki dziesiętnej i rozwiń kalkulator w ten sposób żeby obsługiwał nie tylko liczby całkowite. Napisz odpowiednie funkcje obsługi zdarzeń (pamiętaj że w jednej liczbie może pojawić się co najwyżej jedna kropka dziesiętna).
Ćwiczenie 3¶
Dodaj przycisk kropki dziesiętnej i rozwiń kalkulator w ten sposób żeby obsługiwał nie tylko liczby całkowite. Napisz odpowiednie funkcje obsługi zdarzeń (pamiętaj że w jednej liczbie może pojawić się co najwyżej jedna kropka dziesiętna).
Ćwiczenie 4¶
Dodaj obsługę procentów, pamięci (lub nawiasów grupujących działania), pierwiastkowania, logarytmów, funkcji trygonometrycznych i trzech innych działań według Twojego własnego uznania.