niedziela, 4 stycznia 2015

iOS, a co to jest - odcinek dziewiąty: podstawy iOS cz.4 (ostatnia)

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)

@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

//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: