1. Создадим новый проект:
2. В MainMenu.xib создадим интерфейс:
3. AGAppDelegate.h
Аннотация @property означает, что мы хотим чтобы компилятор создал setter и getter методы.
Note that the outlets are declared as assign rather than weak. Typically under Automatic Reference Counting (ARC) they would be weak (since they are owned by their super view) but we can also declare them assign if we want to be compatible with earlier OS versions that don't support ARC.
Notice the @property declaration for mLastUpdateTime and mCurrencyRates is strong rather than assign. This is required because our project uses Automatic Reference Counting (ARC). Because ARC is going to manage our program's object lifetime use, we need to tell ARC that we want to retain a copy of these objects. If we fail to provide this hint, then ARC will release the objects as soon as we create them (this means their retain count will be zero and they will be deallocated at an unknown time in the future by the memory manager), which would leave us with what is known as a dangling pointer that will cause our program to randomly crash based on how often memory is cleaned up.
4. AGAppDelegate.m
NSDictionary делает себе личную копию ключа, поэтому объект используемый в качестве ключа должен поддерживать копирование (NSString). Если нужно обойти это ограничение, то надо использовать NSMapTable. Чтобы получить список всех ключей в NSArray надо послать сообщение allKeys объекту NSDictionary (или NSMutableDictionary).
Главный поток управляет обработкой событий (mouse, keyboard, и т. д.) и GUI.
Если главный поток занят какой-нибудь длинной задачей это останавливает обработку событий, и Mac OS покажет spinning multicolored cursor и приложение будет помечено как не отвечаемое. Фоновый поток убивается когда закрывается приложение.
5. ПКМ перетягиваем наш App Delegate object в MainMenu.xib на компоненты интерфейса.
6. ПКМ перетягиваем pop-up menu на App Delegate и выбираем selectToCurrency:.
7. Добавим новый класс BTS_GCTableViewDelegate с Subclass равным NSObject
BTS_GCTableViewDelegate.h
BTS_GCTableViewDelegate.m
8. В MainMenu.xib перетягиваем компонент Object на панель объектов и в Custom Class меняем для него Class на BTS_GCTableViewDelegate
9. Три раза щелкаем на таблицы чтобы выделить NSTableView (первый раз выделяется NSScrollView). И ПКМ перетягиваем его на объект BTS_GCTableViewDelegate. В появившемся окне выбираем dataSource. А затем еще раз, только выбираем delegate.
Объект delegate реализует методы протокола NSTableViewDelegate, которые вызываются когда table view нужно выполнить такие дейтсвия как информирование delegate о том что выделение было изменено или спросить у delegate должен ли столбец быть выделен.
Объект dataSource реализует методы протокола NSTableViewDataSource, которые вызываются автоматически, когда table view необходимо отобразить данные.
10. Три раза щелкаем на таблицы чтобы выделить NSTableView (первый раз выделяется NSScrollView). И в Identity Inspector, для первого столбца ставим Identifier значение currency, а для второго столбца value
- Product Name: Global Currency
- Company Identifier: com.yourdomain
- Class Prefix: Your Initials (for consistency we use BTS throughout this book)
2. В MainMenu.xib создадим интерфейс:
3. AGAppDelegate.h
#import <Cocoa/Cocoa.h> @interface AGAppDelegate : NSObject <NSApplicationDelegate> /* Global Currency defines */ // The European Central Bank exchange rate XML File #define D_ECB_RATES_URL @"http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml" // The path to the last update time in the XML tree #define D_XML_PATH_TIME @"/gesmes:Envelope/Cube/Cube" // The path to the exchange rates in the XML tree #define D_XML_PATH_RATES @"/gesmes:Envelope/Cube/Cube/Cube" // The popup menu title #define D_SELECT_CURRENCY @"Select Currency" // The keys for the XML attributes #define kTimeKey @"time" #define kCurrencyKey @"currency" #define kRateKey @"rate" @property (assign) IBOutlet NSWindow *window; /* Define the GUI elements */ @property (assign) IBOutlet NSTextField *mLastUpdateTimeTextField; @property (assign) IBOutlet NSProgressIndicator *mLastUpdateTimeProgressIndicator; @property (assign) IBOutlet NSTextField *mValueToConvertTextField; @property (assign) IBOutlet NSPopUpButton *mCurrencyToConvertPopUp; @property (assign) IBOutlet NSTableView *mConvertedCurrencyTableView; /* Define the Member elements */ @property (strong) NSString *mLastUpdateTime; @property (strong) NSMutableDictionary *mCurrencyRates; /* Methods we need to implement */ // Define the method to get the exchange // rates and parse them. It takes an object // as an argument because it will be run on // a background NSThread - (void) getExchangeRatesFromECB: (id)a_object; // Handle the Pop Up Menu selection - (IBAction)selectToCurrency:(id)a_sender; @end
Аннотация @property означает, что мы хотим чтобы компилятор создал setter и getter методы.
Note that the outlets are declared as assign rather than weak. Typically under Automatic Reference Counting (ARC) they would be weak (since they are owned by their super view) but we can also declare them assign if we want to be compatible with earlier OS versions that don't support ARC.
Notice the @property declaration for mLastUpdateTime and mCurrencyRates is strong rather than assign. This is required because our project uses Automatic Reference Counting (ARC). Because ARC is going to manage our program's object lifetime use, we need to tell ARC that we want to retain a copy of these objects. If we fail to provide this hint, then ARC will release the objects as soon as we create them (this means their retain count will be zero and they will be deallocated at an unknown time in the future by the memory manager), which would leave us with what is known as a dangling pointer that will cause our program to randomly crash based on how often memory is cleaned up.
4. AGAppDelegate.m
#import "AGAppDelegate.h" @implementation AGAppDelegate /* Define the GUI elements */ @synthesize mLastUpdateTimeTextField; @synthesize mLastUpdateTimeProgressIndicator; @synthesize mValueToConvertTextField; @synthesize mCurrencyToConvertPopUp; @synthesize mConvertedCurrencyTableView; /* Define the Member elements */ @synthesize mLastUpdateTime; @synthesize mCurrencyRates; - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { // Insert code here to initialize your application // Create a mutable NSDictionary so that we can // save the exchange rate information mCurrencyRates = [[NSMutableDictionary alloc] init]; // Modify the Progress Indicator // so that it is not visible when // it is not animating [mLastUpdateTimeProgressIndicator setDisplayedWhenStopped:NO]; // Start the Progress Indicator spinning // It will spin until the XML file is // downloaded and parsed. It should be // very brief [mLastUpdateTimeProgressIndicator startAnimation:self]; // Start the background thread to download // the XML file [NSThread detachNewThreadSelector:@selector(getExchangeRatesFromECB:) toTarget:self withObject:nil]; } /* This method will download the XML file from the European Central Bank and parse it into member variables. Input: a_object - the protocol for NSThread requires this method to take an arbitrary object as an argument. It will not be used. Output: As a side effect, the member variables mLastUpdateTime and mCurrencyRates will be populated. */ - (void) getExchangeRatesFromECB: (id)a_object { // Create a URL object using a string NSURL *l_URL = [NSURL URLWithString: D_ECB_RATES_URL]; // Create an XML Document object and // initialize it with the contents of the URL // This downloads the contents of the URL // from the internet NSXMLDocument * l_xmlDocument = [[NSXMLDocument alloc] initWithContentsOfURL:l_URL options:NSXMLDocumentTidyXML error: Nil]; // If the XML document was // successfully retrieved we // can parse it if (l_xmlDocument) { // Declare an array object that we // can use to examine nodes NSArray *l_nodes; // Create an array of nodes at the path // where we can find the time attribute l_nodes = [l_xmlDocument nodesForXPath:D_XML_PATH_TIME error:Nil]; // Extract the time attribute from the node mLastUpdateTime = [[[l_nodes objectAtIndex:0] attributeForName: kTimeKey] stringValue]; // Create an array of nodes at the path // where we can find the currency and rate // attributes l_nodes = [l_xmlDocument nodesForXPath:D_XML_PATH_RATES error:Nil]; // Declare some working variables NSString *l_currency; NSString *l_rate; NSXMLElement *l_element; // Loop over all the currency and rate nodes // and look at each element for (l_element in l_nodes) { // Extract the currency attribute into a NSString l_currency = [[l_element attributeForName: kCurrencyKey] stringValue]; // Extract the rate attribute into a NSString l_rate = [[l_element attributeForName: kRateKey] stringValue]; // Add the rate to the mutable NSDictionary using // the currency as the dictionary key [mCurrencyRates setObject:l_rate forKey:l_currency]; } } // Because this method will execute on a // background thread we need to invoke // another method, on the main thread, to // update the GUI [self performSelectorOnMainThread:@selector(populateGUIOnMainThread:) withObject:nil waitUntilDone:NO]; } - (void) populateGUIOnMainThread: (id) a_object { // If the last update time is nil its because // the XML file could not be retrieved or // parsed so there is nothing to // display if (mLastUpdateTime) { // Display the time in the GUI [mLastUpdateTimeTextField setStringValue:mLastUpdateTime]; // Remove everything from the Pop Up Menu [mCurrencyToConvertPopUp removeAllItems]; // Add an item "Select Currency" to the Pop Up menu [mCurrencyToConvertPopUp addItemWithTitle:D_SELECT_CURRENCY]; // The currency rates returned in the XML // file are not sorted. Create a new NSArray // sorted in alphabetical order NSArray *l_sortedKeys = [[mCurrencyRates allKeys] sortedArrayUsingSelector:@selector(compare:)]; // Add the currencies to the Pop Up Menu [mCurrencyToConvertPopUp addItemsWithTitles: l_sortedKeys]; } // Stop animating the progress indicator. // Animation was started just before the // background thread to download the XML file // was invoked [mLastUpdateTimeProgressIndicator stopAnimation:self]; } // This method will be invoked whenever a currency // code is selected from the Pop Up Menu - (IBAction)selectToCurrency:(id)a_sender { //NSBeep(); // Send a message to the table view telling it that // it needs to reload its data [mConvertedCurrencyTableView reloadData]; } @end
NSDictionary делает себе личную копию ключа, поэтому объект используемый в качестве ключа должен поддерживать копирование (NSString). Если нужно обойти это ограничение, то надо использовать NSMapTable. Чтобы получить список всех ключей в NSArray надо послать сообщение allKeys объекту NSDictionary (или NSMutableDictionary).
Главный поток управляет обработкой событий (mouse, keyboard, и т. д.) и GUI.
Если главный поток занят какой-нибудь длинной задачей это останавливает обработку событий, и Mac OS покажет spinning multicolored cursor и приложение будет помечено как не отвечаемое. Фоновый поток убивается когда закрывается приложение.
5. ПКМ перетягиваем наш App Delegate object в MainMenu.xib на компоненты интерфейса.
6. ПКМ перетягиваем pop-up menu на App Delegate и выбираем selectToCurrency:.
7. Добавим новый класс BTS_GCTableViewDelegate с Subclass равным NSObject
BTS_GCTableViewDelegate.h
#import <Foundation/Foundation.h> #import "AGAppDelegate.h" #import <Foundation/Foundation.h> // Create defines that will be used // to identify the column in the // table view #define kBTSGCCurrency @"currency" #define kBTSGCValue @"value" @interface BTS_GCTableViewDelegate : NSObject @end
BTS_GCTableViewDelegate.m
#import "BTS_GCTableViewDelegate.h" @implementation BTS_GCTableViewDelegate { // Create a reference to the AppDelegate so // that we only need to look it up one time AGAppDelegate *mAppDelegate; } /* This method is invoked automatically when the object instance is revived from the .xib file This is where we do any initialization needed by the .xib object */ -(void) awakeFromNib { // Get a reference to our AppDelegate // object and save it for later use mAppDelegate = [NSApp delegate]; } /* This method is invoked automatically when the table view GUI element needs to know how many rows it has to display All dataSource objects must implement this method. */ - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView { // Ask the AppDelegate to lookup the mCurrencyRates // member variable and then ask it for the number // of objects it contains. That is the number of // rows in the table view return mAppDelegate.mCurrencyRates.count; } - (id)tableView:(NSTableView *)a_tableView objectValueForTableColumn:(NSTableColumn *)a_tableColumn row:(NSInteger)a_rowIndex { // Retrive the identifier for the row. These are // set in the .xib file NSString *l_identifier = [a_tableColumn identifier]; // If the desired column is the currency, then // return the value for the currency code if ([kBTSGCCurrency isEqual: l_identifier]) { return [mAppDelegate.mCurrencyRates.allKeys objectAtIndex:a_rowIndex]; } // If the desired column is the value, then // return the value for the value for that currency code if ([kBTSGCValue isEqual: l_identifier]) { // Get the value to convert from // the AppDelegate double l_valueToConvert = [mAppDelegate.mValueToConvertTextField doubleValue]; // Get the currency code of the value // to convert from the AppDelegate NSString * l_selectedCurrency = [mAppDelegate.mCurrencyToConvertPopUp titleOfSelectedItem]; // Get the exchange rate to Euros for // the value to convert from the AppDelegate double l_rateFrom = [[mAppDelegate.mCurrencyRates objectForKey:l_selectedCurrency] doubleValue]; // Get the currency codes from the AppDelegate // and look up the currency code for the // requested table row NSString *l_toCurrency = [mAppDelegate.mCurrencyRates.allKeys objectAtIndex:a_rowIndex]; // Get the exchange rate to Euros for // the row from the AppDelegate double l_rateTo = [[mAppDelegate.mCurrencyRates objectForKey:l_toCurrency] doubleValue]; // Calculate the converted value. // First by converting the from currency to // Euros, then by converting the result // from Euors to the desired currency double l_euroValue = l_valueToConvert / l_rateFrom; double l_finalValue = l_euroValue * l_rateTo; // Return the result as an NSString return [NSString stringWithFormat:@"%f",l_finalValue]; } return nil; } @end
- nil (all lower-case) is a null pointer to an Objective-C object.
- Nil (capitalized) is a null pointer to an Objective-C class.
- NULL (all caps) is a null pointer to anything else.
8. В MainMenu.xib перетягиваем компонент Object на панель объектов и в Custom Class меняем для него Class на BTS_GCTableViewDelegate
9. Три раза щелкаем на таблицы чтобы выделить NSTableView (первый раз выделяется NSScrollView). И ПКМ перетягиваем его на объект BTS_GCTableViewDelegate. В появившемся окне выбираем dataSource. А затем еще раз, только выбираем delegate.
Объект delegate реализует методы протокола NSTableViewDelegate, которые вызываются когда table view нужно выполнить такие дейтсвия как информирование delegate о том что выделение было изменено или спросить у delegate должен ли столбец быть выделен.
Объект dataSource реализует методы протокола NSTableViewDataSource, которые вызываются автоматически, когда table view необходимо отобразить данные.
10. Три раза щелкаем на таблицы чтобы выделить NSTableView (первый раз выделяется NSScrollView). И в Identity Inspector, для первого столбца ставим Identifier значение currency, а для второго столбца value