Kolejny poziom know-how z kanonu podstaw iOS rozjaśnił mi w tym systemie następujące kwestie:
- w XCode można niemalże zupełnie przez designer podpinać dane dla UITableView (w tym definiować komórki z niestandardową zawartością, którą można wiązać z kodem), a także definiować nawigacje z UINavigationController i UITabBarController. W rodzinie Windows mamy kontrolki nawigacyjne oraz ogólnodostępną nawigację, mimo czasami nieco mniejszej deklaratywności wydają mi się prostsze w użyciu
- praca w tle realizuje podobne scenariusze jak w Windows, aplikację w tle użytkownik może sobie ubić, możemy: zlecić odświeżenie danych w tle (UI jest aktualizowane!), odebrać zdalną notyfikację oraz zlecić transfer plików w tle. Nie dziwią limity czasowe czy możliwość wyłączenia przez użytkownika pracy w tle. Pewnym odstępstwem są domyślne ograniczenia w dostępie do np. do plików z poziomu tła.
- publikacja także jest w jakimś stopniu podobna do ekosystemu Windows, mamy także tryb Enterprise
- XCode pozwala na łączenie elementów wizualnych z kodem także przy pomocy prawego przycisku myszki, widzimy wtedy wszystkie możliwości
- chowanie programowej klawiatury przy dotknięciu miejsca poza jej obszarem wymaga napisania kawałka kodu (na szczęście niedużego, ale jednak). W Windows takie zachowanie mamy automatycznie.
- informację o zmianie ustawień aplikacji możemy odebrać w iOS przez centrum notyfikacyjne, w Windows notyfikacje powiązane z ustawieniami nie są jakimś standardowym mechanizmem. W Windows Phone / Windows ustawienia aplikacji firm trzecich nie są dostępne z poziomu ogólnych ustawień, w Windows mamy koncepcję panelu Settings, dostępnego z poziomu już danej aplikacji.
Podsumowując po raz kolejny koncepcyjnie nic nowego i różnice w szczegółach. Designer w XCode wydaje się nieco trudniejszy i obsługuje nieraz złożone rzeczy, co odchudza i upraszcza nasz kod w kontrolerach. Możemy korzystanie ze storyboard i designera ograniczyć, ale wtedy nie będziemy pisali zgodnie z aktualnie tendencjami, a nasz kod będzie dłuższy i mniej odseparowany od elementów wizualnych (choć w każdym przypadku i tak nie będziemy mieć pełnej separacji między logiką a UI). Praca w tle wykorzystuje znane z Windows pojęcia i koncepcje, pewne różnice są w szczegółach.
UITableView
Storyboard - przeciągamy UITableView do View
Mode:
- Static Cells - komórki edytowane w designerze, możemy ustawić m.in ich zawartość przeciągając do nich odpowiednie elementy np. Text View, Image View, Label
- Dynamic Prototypes - przeciągamy Table View Cell do Table View i edytujemy zawartość, szablon dla dynamicznie tworzonych komórek, na komórce możemy ustawić Identifier
Trzymając CTRL łączymy w obrębie drzewa wizualnego Table View z View Controller i wybieramy data source. Możemy też w panelu ustawień dla Table View wybrać zakładkę oznaczoną –> (Connection Inspector), zaznaczyć tam połączenie z dataSource i przeciągnąć je do View Controller w drzewie wizualnym.
Łączenie stworzonej w designerze zawartości komórki (Label) z kodem. Tworzymy klasę dziedziczącą po UITableViewCell. Usuwamy inicjalizację wg. stylu. Dodajemy w rozszerzeniu klasy propercję:
@property (nonatomic, strong) IBOutlet UILabel* mainLabel;
Dla komórki w designerze w ustawieniu Custom Class wpisujemy nazwę stworzonej przez nas klasy. W Connection Inspector pojawia się zdefiniowana przez nas propercja, wybieramy ją i przeciągamy do labelki w designerze.
Jeśli używamy designera storyboard, to w kodzie kontrolera w viewDidLoad nie tworzymy ręcznie UITableView, ani nie rejestrujemy identyfikatora dla komórki. W interfejsie naszej klasy możemy udostępnić propercję, w której setterze będziemy ustawiać zawartość tekstową labelki za pomocą utworzonej wcześniej wewnętrznej propercji.
Możemy też stworzyć własną klasę dla data source. W designerze storyboard możemy przeciągnąć z narzędzi Object do głównego poziomu drzewa wizualnego. W ustawieniach Custom Class wpisujemy nazwę utworzonej przez nas klasy. Łączymy data source z Connection Inspector z dodanym obiektem w drzewie wizualnym. Następnie łączymy z CTRL obiekt z drzewa wizualnego z interfejsem kontrolera w kodzie. Potem inicjalizujemy data source w viewDidLoad tablicą.
self.theDataSource.data = data;
Ustawienie nagłówków / stopek sekcji
Ustawienie obrazka dla komórki (deprecated):
cell.imageView.image = [UIImage imageNamed: @”photo.png”];
Ustawienie checków / przycisków na komórkach:
cell.accessoryType = UITableViewCellAccessoryCheckmark; //UITableViewCellAccessoryDetailDisclosureButton;
Nawigacja
UINavigationController
Storyboard - przeciągamy na designer drugi View Controller oraz Navigation Controller (usuwamy z niego domyślny Table View). Przeciągamy strzałkę wejścia z dotychczasowego kontrolera na Navigation Controller. Trzymając CTRL łączymy go z pierwszym kontrolerem i wybieramy root view. Przeciągamy przycisk na widok w pierwszym kontrolerze. Łączamy przycisk z wizualnego drzewa z drugim kontrolerem i wybieramy push (modal: zastąpienie, custom: kod)
UITabBarController
Storyboard - przeciągamy na designer drugi View Controller i Tab Bar Controller (usuwamy z niego domyślne dwa kontrolery). Przeciągamy strzałkę wejścia na Tab Bar Controller. Trzymając CTRL łączymy Tab Bar Controller z pierwszym kontrolerem i wybieramy view controllers. To samo robimy z drugim kontrolerem. W pierwszym kontrolerze wpisujemy Title w sekcji Bar Item. Podobnie w drugim.
Master-detail
W zdarzeniu zaznaczenia wiersza w UITableView otwieramy widok ze szczegółami. Mając numer wiersza wyciągamy odpowiedni element z tablicy, przekazujemy go do propercji kontrolera ze szczegółami (na tej podstawie w jego wnętrzu ustawimy dane dla odpowiedniego elementu wizualnego np. w viewDidLoad).
Storyboard - nawigacja z komórki UITableView: w drzewie wizualnym designera zaznaczamy naszą komórkę i przeciągamy na drugi kontroler. Wybieramy push. Zamiast obsługiwać przez delegat zaznaczenie wiersza posłużymy się funkcjonalnością storyboard:
@implementation ViewController
- (void) prepareForSegue: (UIStoryboardSegue *)segue sender: (id) sender {
MyTableViewCell* cell = sender;
NSIndexPath* indexPath = [self.tableView indexPathForCell:cell];
DetailsViewController* details = segue.destinationViewController;
NSString* model = self.data[indexPath.row];
details.model = model;
}
Łączenie kontrolera w designerze z klasą - wpisujemy nazwę w polu Class w sekcji Custom Class
XCode storyboard - navigation controller możemy zaznaczyć i wybrać: Editor –> Embed In –> Tab Bar Controller
Komunikacja sieciowa
Wpierane protokoły:
- http://
- https://
- ftp://
- file:///
- data://
Składanie url:
NSURL *root = …;
NSURL *URLWithPath = [NSURL URLWithString: path relativeToURL: root];
Wysłanie JSON-a POST-em:
zamieniamy encje na obiekty tj. tablice, słowniki, następnie:
NSData *jsonData;
if ([NSJSONSerialization isValidJSONObject: newAlbum) {
NSError* jsonError;
jsonData = [NSJSONSerialization dataWithJSONObject: newAlbum options: NSJSONWritingPrettyPrinted error: &jsonError];
}
NSURL *url = [NSURL URLWithString: @”…”];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL: url];
[request setHTTPMethod: @”POST”];
[request setHTTPBody: jsonData];
[NSURLConnection sendAsynchronousRequest: request queue: [[NSOperationQueue alloc] init] completionHandler:^
(NSURLResponse *response, NSData *data, NSError *error) {
}];
Autentykacja:
- cookies
- NSHTTPCookie
- NSHTTPCookieStorage
-
- autentykacja http
- rodzaje: basic, digest
- NSURLCredential
- NSURLCredentialStorage
-
- niestandardowe nagłówki
- przykłady: OAuth, xAuth
-
- metody
- NSURLAuthenticationMethodDefault
- NSURLAuthenticationMethodHTTPBasic
- NSURLAuthenticationMethodHTTPDigest
- NSURLAuthenticationMethodHTMLForm
- NSURLAuthenticationMethodNTLM
- NSURLAuthenticationMethodNegotiate
- NSURLAuthenticationMethodClientCertificate
- NSURLAuthenticationMethodServerTrust
-
wyłączenie domyślnej akceptacji cookies i wyczyszczenie storage:
[[NSHTTPCookiesStorage sharedHTTPCookiesStorage] setCookieAcceptPolicy:
NSHTTPCookieAcceptPolicyNever];
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookiesStorage];
for (NSHTTPCookie *cookie in [cookieStorage cookies]) {
[cookieStorage deleteCookie: cookie];
}
zapamiętanie użytkownika i hasła w storage:
NSURLCredential *credential = [NSURLCredential credentialWithUser: userName
password: password
persistence: NSURLCredentialPersistencePermanent];
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc]
initWithHost:
port:
protocol:
realm:
authenticationMethod: NSURLAuthenticationMethodHTTPBasic] ;
[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential: credential
forProtectionSpace: protectionSpace];
usunięcie danych uwierzytelniających:
NSURLCredential *credential = [ [NSURLCredentialStorage sharedCredentialStorage]
defaultCredentialForProtectionSpace: protectionSpace];
if (credential) {
[[NSURLCredentialStorage sharedCredentialStorage] removeCredential: credential
forProtectionSpace: protectionSpace];
}
polityki cache
Anulowanie requestu:
[connection cancel];
Pozbycie się cyklicznej referencji w callbacku wywołania asynchronicznego:
__weak ViewController* weakView = self;
dispatch_async(dispath_get_main_queue(), ^{ [weakView.webView … ]});
Definiowanie zmiennej przekazywanej do bloku w callbacku wywołania asynchronicznego:
__block NSURL* urlObject = …
Wewnętrzna kolejka w connection helperze:
static NSOperationQueue* queue;
- (NSOperationQueue*) internalQueue {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = [[NSOperationQueue alloc] init];
});
return queue;
}
Encja z obrazkiem
- propercje typu UIImage* i NSString* (uri)
- obrazki trzeba pobrać osobno na podstawie adresów uri:
…
__weak TrackHelper* weakSelf = self;
for (Track* track in tracks) {
NSString* imageURI = track.trackImageURI;
[connectionHelper getDataAsync: imageURI completion: ^(NSData *data, NSError *error) {
track.trackImage = [UIImage imageWithData: data];
[weakSelf.delegate trackImageLoaded: track];
}];
}
//przeładowanie komórki po pobraniu obrazka
@implementation TrackListViewController
- (void) trackImageLoaded: (Track *) track {
NSInteger index = [self.tracks indexOfObject: track];
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:index inSection:0];
TrackTableViewCell* cell = (TrackTableViewCell*) [self.tableView cellForRowAtIndexPath: indexPath];
if (cell) {
[self.tableView reloadRowsAtIndexPaths: @[indexPath] withRowAnimation: UITableViewRowAnimationFade];
}
}
…
@end
NSURLSession - nowy obiekt w iOS 7 przydatny do autentykacji
Lokalna persystencja
Pełne ścieżki do folderów:
NSString* home = NSHomeDirectory();
NSString* temp = NSTemporaryDirectory();
Ścieżka do pliku:
NSString* path = [temp stringByAppendingPathComponent: @”plik.txt”];
Ścieżki do plików:
- NSString
- NSURL (działa także z zasobami w sieci, preferowane przez NSFileManager)
NSFileManager
wypisanie ścieżek do folderów w folderze Home:
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *directories = [fm contentsOfDirectoryAtURL: [NSURL fileURLWithPath: home]
includingPropertiesForKeys: nil
options: NSDirectoryEnumerationSkipsHiddenFiles
error: nil];
for (NSURL *url in directories) {
NSLog(@”%@”, [url path]);
}
ścieżka do folderu Documents:
NSArray *directories = [fm URLsForDirectory: NSDocumentDirectory inDomains: NSUserDomainMask];
NSURL *documentPath = [directories objectAtIndex: 0];
ścieżka do pliku w folderze Documents:
[documentPath URLByAppendingPathComponent: fileName];
Zapisywanie drzewa obiektów do strumienia i odwrotnie
- klasa NSCoder
- obiekt musi implementować protokół NSCoding
- (void) encodeWithCoder: (NSCoder *) aCoder {
[aCoder encodeObject: _title forKey: @”title”];
[aCoder encodeInt: _year forKey: @”year”];
}
- (id) initWithCoder: (NSCoder *) aDecoder {
self = [super init];
if (self) {
[self setTitle: [aDecoder decodeObjectForKey: @”title”]];
[self setYear: [aDecoder decodeIntForKey: @”year”]];
}
return self;
}
podklasy NSCoder: NSKeyedArchiver i NSKeyedUnarchiver
[NSKeyedArchiver archiveRootObject: obj toFile: filePath]; //obj: np. tablica z encjami implementującymi NSCoding
[NSKeyedUnarchiver unarchiveObjectWithFile: filePath]; // może zwrócić np. NSMutableArray *, w tym pusty obiekt
Property Lists
- pliki .plist
- obiekty: kolekcje lub typy proste
- przechowywanie: XML lub binarnie
- tworzenie: XCode, edytor lub Objective-C
tworzenie w XCode:
- okno dla nowego pliku –> Resource –> Property List
- jako XML
Bezpośredni zapis obiektu property list (tj. NSArray, NSDictionary) do pliku (xml):
[dictionary writeToFile: path atomically:NO];
Deserializacja z pliku do obiektu property list: initWithContentsOfFile
NSArray *array1 = [NSArray arrayWithContentsOfFile: filepath]; //wszystko read-only
NSMutableArray *array2 = [NSMutableArray arrayWithContentsOfFile: filepath]; //tablica modyfikowalna, jej elementy read-only
NSPropertyListMutabilityOptions:
NSPropertyListImmutable //wszystko read-only NSPropertyListMutableContainers //kontener modyfikowalny, jego elementy read-only NSPropertyListMutableContainersAndLeaves //wszystko modyfikowalne
NSPropertyListSerialization
Serializacja obiektów property list (NSArray, NSDictionary, NSString, NSNumber, NSData, NSDate):
dataWithPropertyList: format: options: error: (NSData) writePropertyList: toStream: format: options: error: (NSStream)Deserializacja - odpowiednio:
propertyListWithData: options: format: error: propertyListWithStream: options: format: error:Walidacja: propertyList: isValidForFormat:
Drugi krok - zapis NSData do pliku (writeToFile: atomically:) lub odczyt (dataWithContentsOfFile:)
Przykładowy odczyt property list:
NSError *error;
NSData *data = [NSData dataWithContentsOfURL: fileUrl];
NSMutableDictionary *stats = [NSPropertyListSerialization propertyListWithData: data
options: NSPropertyListMutableContainersAndLeaves
format: NULL error: &error];
Przykładowy zapis property list:
NSError *error;
NSData *data = [NSPropertyListSerialization dataWithPropertyList: pList
format: NSPropertyListBinaryFormat_v1_0
options: 0 error: &error];
[data writeToURL: fileUrl atomically: YES];
Ustawienia NSUserDefaults
Jak property list i słownik. Może przechowywać tylko typy property list.
Jak plik użytkownika: plik przechowywany w /Library/Preferences, backup w iTunes
Wymuszenie zapisu (normalnie synchronizacja odbywa się okresowo):
[[NSUserDefaults standardUserDefaults] synchronize];
Keyboard Type - typ klawiatury dla pola
w AppDelegate w metodzie didFinishLaunchingWithOptions:
NSDictionary *appDefaults = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: 50] forKey: @”total”];
[[NSUserDefaults standardUserDefaults] registerDefaults: appDefaults];
odczyt:
NSInteger total = [[NSUserDefaults standardUserDefaults] integerForKey: @”total”];
odbiór przez aplikację systemowej notyfikacji o zmianie ustawienia:
@interface ViewController: UIViewController
…
- (void) totalChanged: (NSNotification *) notification;
@end
@implementation ViewController
…
-(void) viewDidLoad {
…
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver: self selector: @selector(totalChanged:) name: NSUserDefaultsDidChangeNotification object: nil];
}
-(void) totalChanged: (NSNotification *) notification {
NSUserDefaults *defaults = (NSUserDefaults *) [notification object];
//odczyt
}
@end
inappsettingskit.com - możliwość osadzenia ustawień z Settings wewnątrz aplikacji
Pliki aplikacji po wgraniu:
- PkgInfo
- plik wykonywalny
- obrazek startowy
- zasoby stringowe (w różnych językach)
- skompilowany storyboard
- skompilowane zasoby graficzne (Assets.car)
Storyboard XML jest trzymany w postaci skompilowanej jako zasób w osobnym pliku i ładowany w runtime.
W zasobach aplikacji (bundles) mogą być trzymane dowolne pliki np. html.
Wczytanie danych z pliku będącego zasobem aplikacji:
NSBundle* main = [NSBundle mainBundle];
NSURL* bundleURL = [main bundleURL]; //ścieżka w nieco innym formacie: file:// (escapowana)
NSURL* url = [main URLForResource: @”index” withExtension: @”html”];
NSData* data = [NSData dataWithContentsOfURL: url];
Praca w tle
Wypisanie wywołanej funkcji:
[self print: [NSString stringWithFormat: @”%s”, __PRETTY_FUNCTION__]];
Lista uruchomionych aplikacji:
- Shift + H (dwa razy Home)
- zabicie: przeciągnięcie (swipe up)
Taski w tle
- od iOS 4 aplikacje mogą poprosić o dodatkowy czas na wykonywanie po przejściu do tła
- 15 minut
- to API pozostało w iOS 7, ale można się bez niego obyć
- semantyka wywołania uległa zmianom
- przy lock screen w iOS 6, task w tle utrzymuje telefon w czuwaniu
- iOS 7 usypia aplikację, aplikacja dostaje okres czasu po obudzeniu telefonu
- nie ma gwarancji, że czas wykonania będzie ciągły
Nowy sposób przełączania aplikacji w iOS 7:
- jedna z głównych zmian w systemie
- użytkownik może zabić aplikację
- w przeciwieństwie do iOS 6, to zabija także wykonywanie w tle
- nowe API w iOS 7
- ładowanie w tle
- aktualizacja danych w tle
- jeśli aplikacja jest w tle, jest wiązana z przedziałem czasu
- jeśli aplikacja nie jest uruchomiona, startuje w tle i jest wiązana z przedziałem czasu
- moment i sposób wiązania aplikacji z przedziałem czasu są kontrolowane przez system (system się uczy na podstawie częstotliwości użytkowania aplikacji)
- API
- UIBackgroundModes na “fetch” w Info.plist (można też skorzystać z zakładki Capabilities w Xcode)
- zakres czasu ładowania w tle w obiekcie Application
- callback
- performFetchWithCompletionHandler
- po wykonaniu pracy wywołujemy completion handler
- po wywołaniu handlera, jeśli są nowe dane, jest tworzony nowy snapshot dla aplikacji
- aktualizacja UI dla App Switcher’a
- testowanie przez propercje debug: Background Fetch / Launch due to a background fetch event (także w menu Debug –> Simulate Background Fetch)
- aktualizacja danych w tle
- ładowanie w tle
@implementation AppDelegate
- (void) application: (UIApplication *) application
performFetchWithCompletionHandler: (void (^) (UIBackgroundFetchResult)) completionHandler {
//wywołanie metody odświeżającej na kontrolerze root z przekazaniem jej completionHandler do wywołania
// po udanym pobraniu danych
}
…
@end
@implementation ViewController
…
//metoda odświeżająca:
completionHandler(UIBackgroundFetchResultNewData);
…
@end
- zdalne notyfikacje
- od iOS 4 notyfikacje mogą być wysyłane za pomocą Apple Push Notification Service (APNS)
- często są to notyfikacje o nowych danych
- użytkownik jest informowany przez alert lub popup
- po kliknięciu w UI aplikacja jest przenoszona do foreground lub uruchamiana
- problem: czy aplikacja zdąży pobrać nowe dane przed swoim wyświetleniem?
- iOS 7 umożliwia wysyłanie notyfikacji z nową flagą
- content-available: 1
- gdy jest ustawiona, aplikacja jest uruchamiana zanim użytkownik otrzyma notyfikację
- wysłanie z tą flagą, ale bez alertu stworzy cichą notyfikację, która zostanie dostarczona do aplikacji w tle
- API
- UIBackgroundModes ustawiamy na “remote-notification”
- content-available: 1
- w aplikacji wywoływana jest metoda application:didReceiveRemoteNotification:fetchCompletionHandler
- wywołujemy completion handler, gdy skończymy
- ciche notyfikacje są limitowane (APNS nie chce przeciążyć urządzenia – opóźnienia, ale nie odrzucenia)
- transfer w tle
- przed iOS 7 można było chcieć pobierania lub wysyłania zasobu po przejściu aplikacji do tła
- Background Task API daje ustawienie przedziału czasu
- od iOS 7 Background Transfer Service (BTS)
- zarządza życiem baterii i danymi
- żądania są klasyfikowane jako uznawane i nieuznawane
- uznawane żądania zachodzą tylko przy włączonym Wi-Fi
- żądania z tła są automatycznie uznawane
- żądania z foreground mogą żądać bycia uznanymi
- NSURLSession
- nowy obiekt w iOS 7
- rozszerza funkcjonalność NSURLConnection
- API podobne do NSURLConnection
- dodatkowa funkcjonalność: przechowywanie danych uwierzytelniających, cookies, itp.)
- jeśli aplikacja przechodzi do tła lub zostanie zakończona po zrobieniu żądania NSURLSession, BTS dokończy żądanie
- aplikacja jest uruchamiana lub budzona, kiedy żądanie zostaje zakończone i mamy je szansę przetworzyć
- API
- do stworzenia NSURLSession potrzebujemy obiektu NSURLSessionConfiguration
- określa wykonywanie w foreground/background, czy request będzie używał tylko wi-fi, itp.
- dostaje identyfikator (string) do późniejszego pozyskiwania z BTS
- implementacja protokołów:
- NSURLSessionDelegate dla wysokopoziomowych zdarzeń
- NSURLSessionTaskDelegate dla zdarzeń związanych z danymi
- NSURLSessionTask zastępuje NSURLConnection
- metoda resume do uruchomienia taska
- metody kontrolujące: cancel, suspend, resume
- wiele podklas dla różnych funkcjonalności
- NSURLSession także ma metody z konwencją opartą na blokach
- do stworzenia NSURLSession potrzebujemy obiektu NSURLSessionConfiguration
- zarządza życiem baterii i danymi
//praca nie w tle, równoważnik NSURLConnection
@implementation ViewController
-(NSURLSession* ) getSession {
static NSURLSession* session = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
session = [NSURLSession sessionWithConfiguration: config];
});
return session;
}
-(void) refreshData {
NSURL* url = [NSURL URLWithString: @”…”];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL: url];
[request setValue: @”application/json” forHTTPHeaderField: @”Accept];
NSURLSessionDataTask* task = [self getSession] dataTaskWithRequest: request completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) { … }];
[task resume];
}
…
@end
//autentykacja
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyNever;
sessionConfig.HTTPCookieStorage = NULL;
sessionConfig.HTTPShouldSetCookies = NO;
sessionConfig.URLCredentialStorage = [NSURLCredentialStorage sharedCrendentialStorage];
NSURLSession *session = [NSURLSession sessionWithConfiguration: sessionConfig delegate: self
delegateQueue: [NSOperationQueue mainQueue]];
self implementuje protokół NSURLSessionTaskDelegate:
- (void) URLSession: (NSURLSession *) session
task: (NSURLSessionTask *) task
didReceiveChallenge: (NSURLAuthenticationChallenge *) challenge
completionHandler: ( void (^) (NSURLSessionAuthChallengeDisposition, NSURLCredential *) ) completionHandler {
if (challenge.previousFailureCount == 0) {
NSURLProtectionSpace *space = [challenge protectionSpace];
NSURLCredential *credential = [ [NSURLCredentialStorage sharedCredentialStorage] defaultCredential];
if (credential) {
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
return;
}
}
}
//praca w tle
@interface ViewController: UIViewController<NSURLSessionDownloadDelegate>
@end
@interface ViewController () {
}
@property (copy, nonatomic) void (^backgroundCompletionHandler)();
@end
@implementation ViewController
-(void) performBackgroundCleanup: (void (^)()) completionHandler {
self.backgroundCompletionHandler = completionHandler;
}
-(void) URLSession: (NSURLSession *) session
downloadTask: (NSURLSessionDownloadTask *) downloadTask
didFinishDownloadingToURL: (NSURL *) location {
NSData* data = [NSData dataWithContentsOfURL: location];
//obróbka danych i odświeżenie UI przez dispatch_async
[session getTasksWithCompletionHandler: ^(NSArray *dataTasks,
NSArray *uploadTasks, NSArray *downloadTasks) {
//policzyć łączną sumę tasków
if (count == 0) {
void (^temp)() = self.backgroundCompletionHandler;
self.backgroundCompletionHandler = nil;
temp();
}
}];
}
- (void) URLSession: (NSURLSession *) session
downloadTask: (NSURLSessionDownloadTask *) downloadTask
didResumeAtOffset: (int64_t) fileOffset
expectedTotalBytes: (int64_t) expectedTotalBytes {
}
- (void) URLSession: (NSURLSession *) session
downloadTask: (NSURLSessionDownloadTask *) downloadTask
didWriteData: (int64_t) bytesWritten
totalBytesWriten: (int64_t) totalBytesWritten
totalBytesExpectedToWrite: (int64_t) totalBytesExpectedToWrite {
}
-(NSURLSession* ) getBackgroundSession {
static NSURLSession* backgroundSession = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration* config = [NSURLSessionConfiguration
backgroundSessionConfiguration: @”pl.art.xxx” ];
backgroundSession = [NSURLSession sessionWithConfiguration: config
delegate: self delegateQueue: nil];
});
return backgroundSession;
}
-(void) refreshData {
NSURL* url = [NSURL URLWithString: @”…”];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL: url];
[request setValue: @”application/json” forHTTPHeaderField: @”Accept];
NSURLSessionDownloadTask* task = [self getBackgroundSession] downloadTaskWithRequest: request completionHandler];
[task resume];
}
…
@end
@implementation AppDelegate
…
- (void) application: (UIApplication *) application
handleEventsForBackgroundURLSession: (NSString *) identifier
completionHandler: (void (^)()) completionHandler {
//wywołanie odświeżającej metody z kontrolera z przekazaniem jej callbacku
[mvc performFetch: completionHandler];
}
…
@end
anulowanie
Przetwarzanie w tle:
- ograniczenie czas wykonywania (mniej niż 1 minuta)
- należy wykonywać completion handlers w mniej niż 1 minutę
- użytkownik może wyłączyć lub ograniczyć ładowanie w tle
- przy domyślnych poziomach zabezpieczeń danych, nie ma dostępu do plików, baz danych, kluczy z poziomu tła
- dla bezpiecznego dostępu do danych uwierzytelniających z poziomu tła można używać tokenów wygasających po okresie czasu
Publikacja
Ikony
XCode
- Product –> Archive (potrzebne prawdziwe urządzenie)
- archiwizacja, walidacja i wysłanie
iOS Provisioning Portal - certyfikat i profil
Enterprise Deployment
- iTunes na maszynie użytkownika
- iPhone Config Utility (for IT configuration)
- własna (wewnętrzna) strona internetowa
Rejestracja na portalu
- bundle id
- wspieranych funkcjonalności (możliwość późniejszej zmiany)
- sposobu dystrybucji (App Store lub bezpośrednio na ograniczoną liczbę urządzeń)
- wybór certyfikatu, app id i konkretnych urządzeń
- nazwa profilu
Pobieramy z sieci profil
Add to Library
XCode
- Organizer
- Product –> Archive
- Submit to the iOS App Store / Save for Enterprise or Ad Hoc Deployment
- wybór profilu
- opcja: Save for Enterprise Distribution (dane)
Pliki wynikowe
- *.ipa (pakiet)
- *.plist (opis + metadane)
Każde urządzenie
- plik profilu *.mobileprovision (z sieci) [zezwala na instalację *.ipa na konkretnym urządzeniu] (w mailu lub stronie Web)
- plik *.ipa (na stronie Web link typu itms-service://?action=download-manifest&url=http://127.0.0.1/xxx.plist)
Aktualizacje aplikacji
- musimy podnieść wersję
- automatyczne aktualizacja aplikacji z App Store w iOS 7
- konieczność corocznej aktualizacji aplikacji Enterprise (każdego roku wygasa certyfikat)
Inne uzupełnienia
Sortowanie tablicy
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey: @”title”
ascending: YES
selector: @selector(caseInsensitiveCompare: )];
NSArray *sortDescriptors = [NSArray arrayWithObject: sortDescriptor];
[array sortUsingDescriptors: sortDescriptors];
Unikalny identyfikator
[ [NSUUID UUID] UUIDString];
UISegmentedControl
- kontrolka przełączająca stan (np. wybór jednej z dwóch opcji)
- przyciski w jednym rzędzie
- zdarzenie: Value Changed
[[self segmentedControl] setSelectedSegmentIndex: 1];
[segmentedControl selectedSegmentIndex]; //pobranie
Outlets
XCode: można także zaznaczyć element prawym przyciskiem i zaznaczyć połączenie z sekcji Referencing Outlets
Akcje
XCode: można także zaznaczyć element prawym przyciskiem i wybrać pozycję z sekcji Sent Events (można i na interfejs)
Ukrywanie klawiatury po kliknięciu poza jej obszarem i pola tekstowego:
-(void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event {
[self.view endEditing: YES];
}
Layout
XCode:
- warunki także z przycisku na dole designera
- menu także na dole designera: Add missing constraints
- lista ustawionych warunków na layout w bocznej zakładce dla layoutu w sekcji Constraints (można usunąć nadmiarowe)
text.intValue
pole w interfejsie:
@interface ViewController: UIViewController {
int total;
NSMutableArray *items;
}
//propercje
//metody
@end
@implementation ViewController
-(id) initWithCoder: (NSCoder *) aDecoder {
self = [super initWithCoder: aDecoder];
if (self) {
items = [[NSMutableArray alloc] init];
}
return self;
}
…
@end
Segues
- ułatwiają nawigację między ekranami
- możemy zaznaczyć element prawym przyciskiem i zaznaczyć action z sekcji Triggered Segues
- powrót z ekranu: na ekranie docelowym definiujemy akcję, możemy zaznaczyć przycisk prawym przyciskiem i wybrać action z Triggered Segues, a potem przeciągnąć do przycisku powrotu na dole, podpowie się zdefiniowana wcześniej akcja, wybrać ją.
-(void) prepareForSegue: (UIStoryboardSegue *)segue sender: (id) sender {
if ([segue.identifier isEqualToString: @”xxx”]) { //w ustawieniach seque w designerze ustawiamy identyfikator
}
}
Wysłanie własnej notyfikacji przez centrum notyfikacyjne
[[NSNotificationCenter defaultCenter] postNotificationName: @”xxx” object: sender];
Alerty
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: @”…” message: @”…” delegate: nil
cancelButtonTitle: @”OK” otherButtonTitles: nil];
[alert show];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: @”…” message: @”…” delegate: self
cancelButtonTitle: @”No” otherButtonTitles: @”Yes”, nil];
…
// implementacja UIAlertViewDelegate
-(void) alertView: (UIAlertView *) alertView clickedButtonAtIndex: (NSInteger) buttonIndex {
if (buttonIndex == 0) { // No
}
else { // Yes
}
}
UIActionSheet - alternatywa, co najmniej dwie akcje (w alertach raczej do 2 przycisków), zwykle akcja ryzykowna np. usunięcie, deprecated w iOS 8
Biblioteka współdzielona w XCode
- tworzymy workspace
- tworzymy projekt aplikacji i projekt Cocoa Touch Static Library
- buildujemy całość
- przeciągamy produkt biblioteki do listy bibliotek w ustawieniach projektu aplikacji (Build Phases –> Link Binary With Libraries)
- w ustawieniach projektu aplikacji podajemy ścieżkę do szukania plików nagłówkowych (Build Settings –> Header Search Paths: …/NazwaBiblioteki + opcja recursive)
- w ustawieniach projektu biblioteki dodajemy nowy build step (Build Phases –> Add Build Phase –> Add Copy Headers: Projekt + i wybieramy plik nagłówkowy)
- w kodzie aplikacji importujemy plik nagłówkowy biblioteki tak samo jak inne pliki nagłówkowe
Pliki projektu w XCode
- Grupy - wirtualne foldery grupujące logicznie pliki, nie zmieniają nic w systemie plików, nie mają wpływu na referencje
- Obrazki - przy dodaniu do projektu opcja Copy items into destination group’s folder (if needed), po dodaniu dostępność z poziomu zakładki Media Library
Brak komentarzy:
Prześlij komentarz