niedziela, 11 stycznia 2015

iOS, a co to jest - odcinek 12: Core Data

Tym razem nieco informacji o Core Data, który jest takim uniwersalnym ORM mogącym współpracować z różnymi źródłami danych. Przy okazji dodam, że takie są założenia budowanego Entity Framework w wersji 7. Muszę przyznać, że API w Core Data wygląda całkiem przyjemnie, dodatkowo przewidziano współpracę np. z UITableView w zakresie grupowania (no, tu już API nie jest takie przyjemne, ale idea słuszna) czy stronicowania. Należy zauważyć możliwość szczegółowego profilowania operacji na danych. Brawo!

 

Intro

Core Data

  • framework
  • zarządzanie cyklem życia obiektów
  • zarządzanie grafem obiektów
  • persystencja

Stos

  • NSManagedObject, NSManagedObjectContext
  • NSPersistentStoreCoordinator (+ NSManagedObjectModel)
  • NSPersistentStore (SQLite, XML, pamięć, iCloud, …)

 

Model

Elementy

  • encje (odpowiednik tabel)
  • atrybuty (odpowiednik kolumn; typ, walidacja: min, max, default, właściwości)
  • relacje (to-one, to-many, opcje: m.in typ, opcjonalność, reguła usuwania)
  • dziedziczenie
  • designer (plik *.xcdatamodelId)

Zaawansowane opcje

  • szablony zapytań
  • wersjonowanie
  • dodatkowe informacje w postaci słowników (user info dictionaries)
  • konfiguracje

 

Prosty odczyt i zapis

XCode: projekt z opcją Core Data z wygenerowanym kodem

AppDelegate

@interface AppDelegate: UIResponder<UIApplicationDelegate>

…

@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;

@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;

@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

- (void) saveContext;

- (NSURL *) applicationDocumentsDirectory;

@end

@implementation AppDelegate

- (NSManagedObjectModel *)managedObjectModel {
    if (_managedObjectModel != nil) {
        return _managedObjectModel;
    }
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreDataDemo" withExtension:@"momd"];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

   //inna możliwość:   [NSManagedObjectModel mergedModelFromBundles: nil]
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }    
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataDemo.sqlite"];
    NSError *error = nil;
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
    }
   
    return _persistentStoreCoordinator;
}

- (NSManagedObjectContext *)managedObjectContext {
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }    
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (!coordinator) {
        return nil;
    }
    _managedObjectContext = [[NSManagedObjectContext alloc] init];
    [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    return _managedObjectContext;
}

- (void)saveContext {  
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil) {
        NSError *error = nil;
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
        }
    }
}

@end

Opis encji:

NSEntityDescription *desc;

desc = [NSEntityDescription entityForName: @”Album”  inManagedObjectContext: context];

Wstawianie obiektu:

NSManagedObject *obj;

obj = [NSEntityDescription insertNewObjectForEntityForName: @”Album”  inManagedObjectContext: context];

 

[obj setValue: @”Kazik”  forKey: @”artist”];

[obj setValue: @”Melassa”  forKey: @”title”];

 

NSString *title = [obj valueForKey: @”title”];

Pobieranie obiektów:

NSFetchRequest *req;

req = [NSFetchRequest fetchRequestWithEntityName: @”Album”];

NSError *error;

NSArray *allAlbums = [context executeFetchRequest: req  error: &error];

 

Odczytywanie i zapisywanie - bardziej złożone scenariusze

Własne klasy encji:

dziedziczymy po NSManagedObject

@interface Album: NSManagedObject

@property (nonatomic, retain) NSString* artist;

@property (nonatomic, retain) NSString* title;

@end

 

@interface Album (CoreDataGeneratedAccessors)

…

@end

 

@implementation Album

@dynamic artist;  //implementacja dostarczana w runtime

@dynamic title;

- (void) awakeFromInsert {

        [self setTitle: @”No title”];   //w przypadku niedostarczenia danych

}

@end

nie należy nadpisywać:

  • primitiveValueForKey:
  • setPrimitiveValue: forKey:
  • isEqual:
  • hash
  • superclass
  • class
  • self
  • zone
  • isProxy
  • managedObjectContext
  • entity
  • description

implementujemy:

  • awakeFromInsert
  • awakeFromFetch

kreator w XCode: nowy plik –> Core Data –> NSManagedObject subclass

używanie wygenerowanych klas:

Album *album;

album = [NSEntityDescription insertNewObjectForEntityForName: @”Album”  inManagedObjectContext: context];

[album setTitle: @”Hurra”];

NSString *title = [album title];

Relacje:

NSManagedObject *category = [item valueForKey: @”category”];

MusicCategory *category = [item category];  //przy własnych klasach encji

 

NSMutableSet *songs = [item mutableSetValueForKey: @”songs”];

[songs addObject: song];

[item addSongsObject: song];  //wygenerowana metoda we własnej klasie encji

Zapytania:

Zwracają obiekty NSManagedObject albo nasze encje. Encje powiązane są ładowane.

NSPredicate:

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: @”Album”];

NSPredicate *pred = [NSPredicate predicateWithFormat: @”title CONTAINS %@”, searchString];

NSPredicate *pred = [NSPredicate predicateWithFormat: @”%K CONTAINS %@”, attr, searchString];

[request setPredicate: pred];

porównanie napisu:

  • CONTAINS
  • BEGINSWITH
  • ENDSWITH
  • LIKE
  • MATCHES

proste porównanie:

  • =, ==
  • >=, =>
  • <=, =<
  • <
  • >
  • !=, <>
  • BETWEEN

agregacje:

  • ANY, SOME
  • ALL
  • NONE
  • IN

NSSortDescriptor:

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: @”Album”];

NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey: @”title” ascending: YES];

NSArray *sortArray = [NSArray arrayWithObject: sort];

[request setSortDescriptors: sortArray];

Szablony zapytań

możliwość definiowania i trzymania w diagramie

NSFetchRequest *request = [model fetchRequestTemplateForName: @”allRockAlbums”];

 

NSFetchedResultsController

Prezentowanie danych w sekcjach UITableView:

sekcje: pierwszy deskryptor w kolekcji z obiektami NSSortDescriptor w NSFetchRequest

sortowanie w sekcjach: drugi deskryptor

 

@interface ViewController: UITableViewController <NSFetchedResultsControllerDelegate>

@property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;

@end

@implementation ViewController

…

- (void) viewDidLoad  {

     …

     NSError *error;

      if (![[self fetchedResultsController]  performFetch: &error]) {

      }

}

- (NSFetchedResultsController *) fetchedResultsController  {

       …

       //przygotowanie requestu z deskryptorami do sortowania, pierwszy z “category.name”

       NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest: request

                             managedObjectContext: context  sectionNameKeyPath: @”category.name”  cacheName: nil ];

       [self setFetchedResultsController: frc];

       [[self  fetchedResultsController] setDelegate: self];

       return _fetchedResultsController;

}

- (NSInteger) numberOfSectionsInTableView: (UITableView *) tableView  {

       NSArray *sections = [[self fetchedResultsController] sections];

       return [sections count];

}

- (NSInteger) tableView: (UITableView *) tableView

numberOfRowsInSection: (NSInteger) section {

         NSArray *sections = [[self fetchedResultsController] sections];

         id<NSFetchedResultsSectionInfo> curSec;

         curSec = [sections objectAtIndex: section];

         return [curSec numberOfObjects];

}

- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath  {

          …

          Album *item = [[self fetchedResultsController] objectAtIndexPath: indexPath];

         …

}

- (NSString *) tableView: (UITableView *) tableView titleForHeaderInSection: (NSInteger) section {

         NSArray *sections = [[self fetchedResultsController] sections];

         id<NSFetchedResultsSectionInfo> curSec = [sections objectAtIndex: section];

         return [curSec name];

}

//nasłuchiwanie zmian danych

- (void) controllerWillChangeContent: (NSFetchedResultsController *) controller {

        [[self tableView] beginUpdates];

}

- (void) controller: (NSFetchedResultsController *) controller

    didChangeSection: (id<NSFetchedResultsSectionInfo>) sectionInfo

                        atIndex:  (NSUInteger) sectionIndex

         forChangeType:  (NSFetchedResultsChangeType) type {

         switch (type) {

                  case NSFetchedResultsChangeInsert:

                           [[self tableView] insertSections: [NSIndexSet indexSetWithIndex: sectionIndex]

                                                              withRowAnimation: UITableViewRowAnimationFade];

                           break;

                  case NSFetchedResultsChangeDelete:

                           [[self tableView] deleteSections: [NSIndexSet indexSetWithIndex: sectionIndex]

                                                              withRowAnimation: UITableViewRowAnimationFade];

                           break;

         }

}

- (void) controller: (NSFetchedResultsController *) controller

    didChangeObject: (id) anObject

    atIndexPath: (NSIndexPath *) indexPath

    forChangeType: (NSFetchedResultsChangeType) type

    newIndexPath: (NSIndexPath *) newIndexPath {

     switch (type) {

              case NSFetchedResultsChangeInsert:

                      [[self tableView] insertRowsAtIndexPaths: [NSArray arrayWithObject: newIndexPath]

                                                         withRowAnimation: UITableViewRowAnimationFade];

                      break;

              case NSFetchedResultsChangeDelete:

                      [[self tableView] deleteRowsAtIndexPaths: [NSArray arrayWithObject: indexPath]

                                                         withRowAnimation: UITableViewRowAnimationFade];

                      break;

              case NSFetchedResultsChangeUpdate:

              {

                       UITableViewCell *cell = [[self tableView] cellForRowAtIndexPath: indexPath];

                       Album *item = [[self fetchedResultsController]  objectAtIndexPath: indexPath];

                       [[cell textLabel] setText: [item title];

                       break;

              }

              case NSFetchedResultsChangeMove:

                       [[self tableView] deleteRowsAtIndexPaths: [NSArray arrayWithObject: indexPath]

                                                         withRowAnimation: UITableViewRowAnimationFade];

                       [[self tableView] insertRowsAtIndexPaths: [NSArray arrayWithObject: newIndexPath]

                                                         withRowAnimation: UITableViewRowAnimationFade];

                       break;

      }

}

- (NSString *) controller: (NSFetchedResultsController *) controller

    sectionIndexTitleForSectionName: (NSString *) sectionName {

       return sectionName;

}

- (void) controllerDidChangeContent: (NSFetchedResultsController *) controller {

       [[self tableView] endUpdates]; 

}

@end

 

Wersjonowanie modelu

po zmianie modelu trzeba zmigrować dane w repozytorium

migracje

  • dodawanie, usuwanie, zmiana nazw atrybutów, encji, relacji
  • tworzenie encji rodziców i dzieci
  • przesuwanie atrybutów w górę i w dół hierarchii encji

konfiguracja automatycznych migracji:

NSMutableDictionary *options = [NSMutableDictionary dictionary];

[options setValue: [NSNumber numberWithBool: YES]

                      forKey: NSMigratePersistentStoresAutomaticallyOption];

[options setValue: [NSNumber numberWithBool: YES]

                      forKey: NSInferMappingModelAutomaticallyOption];

NSError *error = nil;

[persistentStoreCoordinator addPersistentStoreWithType: NSSQLiteStoreType

                                                                                 configuration: nil

                                                                                                   URL: dataStoreURL

                                                                                           options: options

                                                                                                error: &error];

tworzymy nową wersję modelu:  Editor -> Add Model Version…

ustawiamy nowy model jako bieżący: wybieramy w ustawieniach katalogu z modelami

wykonujemy zmiany na nowej wersji, w ustawieniach elementu uzupełniamy w sekcji Versioning np. poprzednią nazwę

poprzednio wygenerowane klasy encji albo ręcznie edytujemy albo generujemy jeszcze raz

bardziej zaawansowane scenariusze: tworzenie modeli mapujących pomiędzy wskazanymi wersjami modelu

 

Wydajność

Model

  • denormalizacja (zamiast relacji atrybut z obiektem tego rodzaju)
  • dodanie indeksu na atrybucie (opcja Indexed)
  • tworzenie indeksu na wielu kolumnach (w encji sekcja Indexes)
  • przechowywanie w postaci binarnej atrybutu (np. zdjęcia, typ: Binary Data, opcja: Allows External Storage)

Refaulting:

[context refreshObject: item mergesChanges: NO];

[context reset];

Prefetching:

NSEntityDescription *entity = [NSEntityDescription entityForName: @”Album”  inManagedObjectContext: context];

NSFetchRequest *request = [[NSFetchRequest alloc] init];

[request setEntity: entity];

 

NSMutableArray *prefetchKeys = [NSMutableArray array];

[prefetchKeys addObject: @”category”];

 

[request setRelationshipKeyPathsForPrefetching: prefetchKeys];  //ładowanie od razu encji we wskazanych miejscach

Ograniczenie pobieranych danych:

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: @”Album”];

[request setFetchBatchSize: 10]; //doładowywanie na żądanie, jeśli będzie używane, współpraca z UITableView

Wydajne tekstowe zapytania:

np. < przed CONTAINS w koniunkcji

Rodzaje współbieżności:

  • NSConfinementConcurrencyType
  • NSMainQueueConcurrencyType
  • NSPrivateQueueConcurrencyType

NSUInteger type = NSPrivateQueueConcurrencyType;

_context = [[NSManagedObjectContext alloc] initWithConcurrencyType: type];

[_context performBlock: ^{

}];

Debugowanie:

w ustawieniach projektu w sekcji Run na zakładce Arguments dodajemy argument:  -com.apple.CoreData.SQLDebug 1

Profilowanie:

XCode: Product –> Profile –> Core Data

 

Uzupełnienia

UIPickerView - odpowiednik comba

@interface ViewController: UIViewController <UIPickerViewDelegate, UIPickerViewDataSource>

@property (weak, nonatomic) IBOutlet UIPickerView *picker;

@property (nonatomic, strong) NSArray *allCategories;

@property (nonatomic, strong) MusicCategory *selectedCategory;

@end

 

@implementation ViewController

- (void) viewDidLoad {

         …

         [[self picker] setDelegate: self];

         [[self picker] setDataSource: self];

 

        [[self picker]  selectRow: 0 inComponent: 0 animated: NO];

        [self setSelectedCategory: [[self allCategories]  objectAtIndex: 0]];

}

…

//UIPickerViewDataSource

- (NSInteger) numberOfComponentsInPickerView: (UIPickerView *) pickerView {

        return 1;

}

- (NSInteger) pickerView: (UIPickerView *) pickerView numberOfRowsInComponent: (NSInteger) component {

       return [[self allCategories] count];

}

//UIPickerViewDelegate

- (NSString *) pickerView: (UIPickerView *) pickerView

                           titleForRow: (NSInteger) row

                       forComponent: (NSInteger) component  {

         MusicCategory *category = [[self allCategories] objectAtIndex: row];

         return [category categoryName];

}

- (void) pickerView: (UIPickerView *) pickerView

            didSelectRow:  (NSInteger) row

            inComponent:  (NSInteger) component  {

         MusicCategory *selected = [[self allCategories] objectAtIndex: row];

         [self setSelectedCategory: selected];

}

@end

Programowe wyłączenie klawiatury na polu tekstowym

[textField resignFirstResponder];

Programowa zmiana zakładki w UITabBarController z poziomu jednej z zakładek

[[self tabBarController]  setSelectedIndex: 0];

sobota, 10 stycznia 2015

iOS, a co to jest - odcinek 11: grafika i animacje

Przegląd możliwości tworzenia grafiki i animacji w iOS.  Wydaje się, że większość czego potrzebujemy jest. Funkcjonalnymi - niekiedy bardziej niskopoziomowymi - odpowiednikami XAML są UIKit i Core Graphics, w tym Quartz 2D.  Odpowiednikiem DirectX jest OpenGL ES. Kilka - tym razem - drobnych uwag:

  • duża cześć bibliotek  m.in Core Graphics i OpenGL ES oferuje API w C, aczkolwiek nie wydaje się to dużym problemem
  • trochę przekombinowane rysowanie podstawowych kształtów za pomocą API do krzywych Beziera w UIKit
  • ciekawostką jest API w UIKit pozwalające szybko zrobić animację z gotowej sekwencji obrazków
  • API w UIKit do animacji nie wydaje się tak uniwersalne, panuje pewien śmietnik we flagach, brakuje uniwersalnej obsługi easing functions
  • Quartz 2D wydaje się takim nieco szerszym odpowiednikiem canvas w HTML5, w tej niższej warstwie znajdziemy też dużo rzeczy znanych z XAML np. gradienty, clipping, transformacje
  • aplikacje uniwersalne w iOS siłą rzeczy kojarzą mi się z aplikacjami uniwersalnymi na Windows i Windows Phone, przy czym iOS jest jeden

 

Wprowadzenie

UIKit

  • aplikacje biznesowe
  • UIImage
  • UIImageView
  • UIBezierPath
  • UIColor

UIImage *img = [UIImage imageNamed: @”plik.png”]

UIImageView *iv = [ [UIImageView alloc]  initWithImage:  img];

iv.frame = CGRectMake(50, 25, iv.frame.size.width * 2, iv.frame.size.height * 2);   //przesunięcie i skalowanie

Core Graphics

  • w C (nie w Objective-C)
  • CGContextRef - opakowanie wywołania kodu w C
  • CGImageRef  - operacje na pikselach obrazka
  • CGAffineTransform - transformaty obrazków (skalowanie, skrzywianie, obrót)
  • funkcje do tworzenia dokumentów PDF

// klasa dziedzicząca po UIView:

- (id) initWithFrame: (CGRect) frame {

         self = [super initWithFrame: frame];

         if (self) {                

         }

         return self;

}

- (void) drawRect: (CGRect) rect {

         CGContextRef context = UIGraphicsGetCurrentContext();

         CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);

         CGContextFillRect(context, CGRectMake(40, 40, 100, 200));

}

Quartz 2D

  • używa większość obiektów z Core Graphics np. CGContextRef
  • CGContextBeginPath(…)
  • CGContextMoveToPoint(…)
  • CGContextAddLineToPath(…), CGContextAddCurveToPoint(…)
  • CGContextFillPath(…)
  • CGContextStrokePath(…)

Core Animation

  • UIKit jest zbudowany na Core Animation
  • warstwy
  • wydajne biblioteki animacji

OpenGL ES

  • najszybsze renderowanie 2d i 3d
  • EAGLContext
  • frameworki 
    • Sprite Kit
    • Unity 3D
    • open source

Sprite Kit

  • klocki
  • iOS 7
  • engine gier 2D
  • akceleracja sprzętowa
  • Objective-C

 

UIKit

Testowe załadowanie obrazka z sieci:

NSURL *url = [NSURL URLWithString: @”http://…”];

NSData *data = [NSData dataWithContentsOfURL: url];

UIImage *img = [[UIImage alloc] initWithData: data];

//produkcyjnie typowy asynchroniczny request

Krzywe i ścieżki Beziera:

UIBezierPath *path = [UIBezierPath bezierPathWithRect: CGRectMake(10, 10, 100, 100)];  //prostokąt

UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(50, 50, 100, 100)]; //okrąg

UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(10, 10, 100, 100)

             cornerRadius: 20.0]  // prostokąt z zaokrąglonymi rogami

 

UIBezierPath *path = [UIBezierPath bezierPath];  //ścieżka Beziera definiowana w oparciu o punkty

 

[[UIColor redColor] setFill];

[[UIColor greenColor] setStroke];

path.lineWidth = 4;

 

[path moveToPoint: CGPointMake(100, 100)];  //trójkąt

[path addLineToPoint: CGPointMake(200, 100)];

[path addLineToPoint: CGPointMake(200, 300)];

 

//dodanie krzywej

//[path addCurveToPoint: CGPointMake(0, 300)  controlPoint1: CGPointMake(150, 400)

//        controlPoint2: CGPointMake(50, 200)];

 

[path addQuadCurveToPoint: CGPointMake(0, 300)  controlPoint: CGPointMake(100, 400)];

 

[path closePath];

 

[path fill];

[path stroke];

Animacja ramkowa:

//animacja z wielu obrazków

UIImageView *iv = [ [UIImageView alloc]  initWithFrame:  CGRectMake(40, 40, 95, 70)];

…

iv.animationImages = array;  //tablica obrazków UIImage

iv.animationDuration = 1.0 / 2;

 

[self.view  addSubview:  iv];

[iv startAnimating];

Animacja proceduralna:

//zmiana propercji

iv.alpha = 0;

[self.view  addSubview:  iv];

[UIView animateWithDuration: 5.0

                                        animations:  ^ {

                                                  iv.alpha = 1.0;

                                                  iv.frame = CGRectMake(100, 100, 95*2, 70*2);

                                                  iv.transform = CGAffineTransformMakeRotation(M_PI);

                                        }];

Więcej parametrów dla animacji:

animateWithDuration: delay: options: animations: completion:

enum UIViewAnimationOptions – m.in pozwolenie po interakcji użytkownika, powtarzanie, autorewers, niektóre funkcje przebiegu, pewne obrócenia

[UIView animateWithDuration: 5.0

                                        delay: 0

                                        options: UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat

                                        animations:  ^ {

                                                  iv.alpha = 1.0;

                                                  iv.frame = CGRectMake(100, 100, 95*2, 70*2);

                                                  iv.transform = CGAffineTransformMakeRotation(M_PI);

                                        },

       completion: ^(BOOL finished) {

                //np. można ustawić coś po zakończeniu lub odpalić inną animację

       }

];

 

Quartz 2D

Rysowanie kształtów:

CGContextRef context = UIGraphicsGetCurrentContext();

 

CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);

CGContextSetStrokeColorWithColor(context, [UIColor greenColor].CGColor);

CGContextSetLineWidth(context, 4);

 

CGContextFillRect(context, CGRectMake(40, 40, 100, 200));  //prostokąt

CGContextStrokeRect(context, CGRectMake(30, 30, 100, 300));

 

CGContextFillEllipseInRect(context, CGRectMake(30, 30, 100, 300));  //elipsa

CGContextStrokeEllipseInRect(context, CGRectMake(30, 30, 100, 300));

 

CGContextBeginPath(context);  //inne

CGContextMoveToPoint(context, 30, 30);

CGContextAddLineToPoint(context, 300, 30);

CGContextAddLineToPoint(context, 300, 300);

CGContextAddCurveToPoint(context, 250, 350, 50, 250, 30, 300);  //2 punkty kontrolne i punkt docelowy

CGContextClosePath(context);

 

CGContextDrawPath(context, kCGPathFillStroke /*  kCGPathStroke  */);

 

//wycinek koła

CGContextBeginPath(context); 

CGContextMoveToPoint(context, x,  y);

CGContextAddArc(context, x, y, rad, startAngle, endAngle, 0);

CGContextClosePath(context);

Kolory:

predefiniowane:

[UIColor redColor]

RGB:      

[UIColor colorWithRed: 55.0/255.0  green: 0.5  blue: 0.5  alpha: 1.0]

 

CGFloat components[] = {0, 0, 1, 1};

CGColorRef c = CGColorCreate (CGColorSpaceCreateDeviceRGB(), components);

CGContextSetFillColorWithColor(context, c);

…

CGColorRelease(c);

Zapisanie kontekstu przed zmianą i przywrócenie po wykonaniu czynności:

CGContextSaveCGState(context);

CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor);

CGContextFillEllipseInRect(context, CGRectMake(30, 30, 100, 300)); 

CGContextRestoreGState(context);

Gradient liniowy:

CGFloat colors[] = {

         1.0, 0.0, 0.0, 1.0,

         0.0, 0.0, 1.0, 1.0

};

CGGradientRef gradient = CGGradientCreateWithColorComponents(CGColorSpaceCreateDeviceRGB(), colors, NULL, 2);

CGPoint startPoint = CGPointMake(0, CGRectGetMinY(rect));

CGPoint endPoint = CGPointMake(0, CGRectGetMaxY(rect));

CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0); 

// kCGGradientDrawsAterEndLocation | kCGGradientDrawsBeforeStartLocation

CGGradientRelease(gradient);

  Clipping:

CGRect r = CGRectMake(50, 50, 200, 200);

CGContextClipToRect(context, r);             

Memory leaks:

Project –> Analyze

 

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, colors, NULL, 2);

CGColorSpaceRelease(colorSpace);

Gradient radialny:

CGPoint gradCenter = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));

float gradRadius = rect.size.width/2;

CGContextDrawRadialGradient(context, gradient, gradCenter, 0 /* wycięta w środku przestrzeń*/, gradCenter, gradRadius,

      kCGGradientDrawsAfterEndLocation);

Tekst:

CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);

NSString *text = @”xxx”;

[text drawAtPoint: CGPointMake(40, 40) withFont: [UIFont fontWithName: @”Helvetica” size: 40]];

 

//bardziej pracochłonna alternatywa

CGAffineTransform xform = CGAffineTransformMake(1.0, 0.0, 0.0, –1.0, 0.0, 0.0);

CGContextSetTextMatrix(context, xform);

CGContextSelectFont(context, “Helvetica”, 40, kCGEncodingMacRoman);

//CGContextSetTextDrawingMode(context, kCGTextFill);

CGContextShowTextAtPoint(context, 0, 30, “xxx” /* ASCII */, 7);

Bitmapy:

UIImage *img = [UIImage imageNamed: @”xxx”];

 

[img drawAtPoint: CGPointMake(40, 40)];

 

//bardziej pracochłonna alternatywa

CGContextTranslateCTM(context, 0, img.size.height + 80);

CGContextScaleCTM(context, 1,0, -1.0);

CGContextDrawImage(context, CGRectMake(40, 40, 95, 70), img.CGImage); 

Transformacje:

CGContextTranslate(context, 320/2, 480/2);

CGContextScaleCTM(context, 3, 3);

CGContextRotateCTM(context, M_PI/4);

 

Różne ekrany i urządzenia

Aplikacje uniwersalne – na iPhone i iPad

Różnicowanie zasobów

  • MyImage-ipad.png
  • MyImage-iphone.png

https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/IconMatrix.html

 

Uzupełnienia

Animacja z timerem:

NSTimer *timer;

…

timer = [NSTimer  scheduledTimerWithTimeInterval: 1/30.0   target: self   selector: @selector(nextFrame)

                  userInfo: NULL  repeats: YES];

- (void) nextFrame {

       //zmiana stanu, może być sterowana przez easing function

}

Wymuszenie przerysowania:

[myView setNeedsDisplay];

Easing functions:

Znalezienie punktu dotyku w obiekcie wizualnym:

- (void) touchesBegan: (NSSet *) touches  withEvent: (UIEvent *)event  {

         UITouch *t = [touches anyObject];

         CGPoint p = [t locationInView: myView];

}

Funkcja trygonometryczna:

atan2(deltaY, deltaX);

Czas:

double time = CACurrentMediaTime();

piątek, 9 stycznia 2015

iOS, a co to jest - odcinek dziesiąty: iOS 7

Post zbierający wprowadzone funkcjonalności w wersji siódmej iOS (2013 rok). Ogólnie - jak dla mnie - szału nie było poza pewnymi wyjątkami, niektóre rzeczy wydają się wprowadzone nawet dość późno na tle Windows Phone /Windows (np. transfer plików w tle był już w Windows Phone w 2010 roku) lub wydają się mniej uniwersalne (np. ukrywanie klawiatury przy scrollowaniu czy kolorowanie pasków).  Do czego chciałbym się ustosunkować ? Znajdzie się kilka zagadnień. Oto one:

  • lekkie echa z Modern UI: uproszczenie wyglądu, największy nacisk na content, chrome w odwrocie (do Modern UI nie pasują jednak przezroczystości i pół-przezroczystości, efekty 3d, pickery wyświetlane inline)
  • ciekawostką wydaje się tryb renderowania obrazków na podstawie warstw przezroczystości, z pominięciem kolorystyki obrazka i z użyciem koloru tint, tak by dopasować się do reszty UI
  • możliwość implementacji funkcjonalnego odpowiednika zachowania kontrolki SemanticZoom z Windows 8.x w oparciu o animowane zmiany w layout CollectionView
  • obsługa ruchu urządzenia (w Windows bardziej ogólne API do sensorów związanych z ruchem też jest)
  • Dynamics - zbiór fizycznych zachowań np. grawitacji, kolizji, z pewnością ułatwi to tworzenie prostych gier (tutaj kojarzy mi się rok 2009, kiedy wprowadzono zachowania w Blend i zrobiono demo budowy prostej gry w deklaratywny sposób z przygotowanych wcześniej zachowań, jednak nic takiego nie trafiło do standardowego API platformy Windows)
  • praca w tle - trochę dziwne, że zdecydowano się uzależnić w iOS 7 pracę w tle od tego, czy aplikacja jest w pamięci (za wyjątkiem transferu w tle), ładowanie danych w tle ideowo kojarzy mi się trochę z prefetchingiem w Windows 8.1, choć mamy inny nieco sposób realizacji (interesujące jest też automatyczne odświeżanie screenshotu z aplikacji w oknie przełączania między aplikacjami). Zdalne notyfikacje przypominają nieco znane mi do tej pory zdalne notyfikacje (przy zachowaniu pewnych różnic np. flagi do uruchomienia aplikacji przed wyświetleniem notyfikacji w Windows nie ma). Transfer w tle z uniwersalną infrastrukturą będącą alternatywą także dla zwykłej komunikacji sieciowej wydaje mi się trochę przekombinowany, ale ujdzie.
  • “Assets Catalog” w XCode na zasoby graficzne wydaje się niezłym pomysłem (chociaż używanie konwencji nazw w plikach nie wydaje mi się piekłem)
  • interesująca jest możliwość podglądu widoku w wybranej wersji systemu operacyjnego

 

iOS 7

Zasadnicze zmiany

  • pierwszy duży redesign iOS
  • redesign wbudowanych aplikacji (Mail, Safari, Photos, itd.)
  • mnóstwo udogodnień (w multitaskingu, komunikacji sieciowej, itd.)

Właściwości UI

  • chrome w odwrocie, najważniejsza zawartość, najwięcej miejsca dla niej (np. w TableView)
  • przejrzystość i łatwość użycia
  • głębia
    • rozmycia, przezroczystość, dynamiczne efekty 3d

Widoki

  • aplikacja zajmuje pełny ekran
  • pasek statusu jest rysowany nad powierzchnią widoku
    • większa kontrola z poziomu UIViewController
      • możliwość nadpisania propercji preferredStatusBarStyle
      • metoda setNeedsStatusBarAppearanceUpdate (przy wywołaniu w bloku animacji zmiany będą animowane)
      • prefersStatusBarHidden (domyślnie NO, powiązanie z preferredStatusBarStyle)

- (void) viewDidLoad  {

         [self setNeedsStatusBarAppearanceUpdate];

         [super viewDidLoad];

}

- (UIStatusBarStyle) preferredStatusBarStyle {

          return UIStatusBarStyleLightContent;   //białe napisy

}

//jak umieścimy te metody w klasie rozszerzającej navigation controller, to będzie to dziedziczone na obsługiwanych przez niego kontrolerach

W pblist projektu dodajemy wpis:  View controller-based status bar = YES

  • paski nawigacji i zakładek
    • są przezroczyste / półprzezroczyste
    • zmiana tła za pomocą barTintColor (tintColor nie daje takiego efektu jak kiedyś)
    • inne rozmiary
  • pasek nawigacji
    • półprzezroczysty
    • możliwość zmiany backIndicatorImage
      • backIndicatorTransitionMaskImage używany podczas tranzycji push i pop
    • displaysSearchBarInNavigationBar - umieszcza pasek wyszukiwarki w pasku nawigacji zamiast nad TableView
  • toolbar (przy opcji Shows Toolbar)
    • także półprzezroczysty i rysowany nad widokiem
  • sterowanie wjeżdżaniem zawartości ekranu po paski
    • opcje Extended Edges: Under Top Bars, Under Bottom Bars, Under Opaque Bars
  • kolory są wskazówką dla użytkownika
    • tintColor w klasie bazowej UIView
      • wcześniej tylko na paskach
      • na interaktywnych elementach (co jest klikalne, dotykalne itp.)
      • każda aplikacja może mieć własny
      • domyślnym kolorem w iOS 7 jest niebieski
    • automatycznie dziedziczony przez podwidoki
    • action sheets eksponują kontrolki

Zmiany w kontrolkach

  • modyfikacja rozmiarów większości kontrolek
  • szybka migracja z iOS6, jeśli używany był autolayout
  • przyciski nie mają obramowań ani krawędzi
    • nowy domyślny typ przycisku UIButtonTypeSystem
    • TintColor używany do ustawienia kolorów normal i selected
  • TableView
    • w trybie grupowania komórki są rozciągane do krawędzi (większe odstępy między sekcjami, ciemniejsze tło)
    • podświetlane komórki - szary zamiast niebieskiego, selectedBackgroundView
    • separatory są wstawiane
    • możliwość podania szacowanej wysokości przed załadowaniem komórek
  • UIScrollView
    • propercja keyboardDismiss
      • UIScrollViewKeyboardDismissModeOnDrag - przy formularzu w scroll view przewijanie wyłącza klawiaturę
  • wszystkie pickery wyświetlane są inline

UIImage

  • sposób renderowania obrazków obsługiwany przez UIKit
    • metoda imageWithRenderingMode
      • UIImageRenderingModeAlwaysTemplate  - obrazek traktowany jak szablon (kolory są ignorowane, uwzględniane są kanały przezroczystości, tintColor)
      • UIImageRenderingModeAlwaysOriginal
      • UIImageRenderingModeAutomatic (domyślnie) - użycie szablonu zależy od kontekstu

UIActivity

  • nowe typy aktywności
    • UIActivityTypeAddToReadingList
    • UIActivityTypePostToFlickr
    • UIActivityTypePostToVimeo
    • UIActivityTypePostToTencentWeibo
    • UIActivityTypeAirDrop
  • nowy enum: UIActivityCategory
    • UIActivityCategoryShare - Facebook, Twitter, itp.
    • UIActivityCategoryAction - AirDrop

Typografia

  • większa dynamika czcionek
  • style tekstu (w tym obsługa transformacji)
  • wysoka dostępność (zmiana wielkości czcionki przez użytkownika, wysoki kontrast)
  • Text Kit - nowy framework, bazuje na OSX Cocoa Text

Animacje

  • dużo nowych animacji dla tranzycji widoków
  • wymowne, nie gratisowe
    • odzwierciedlają przepływ w aplikacji (np. photos i calendar)
  • otwarcie w API kontroli nad animacjami podczas tranzycji między kontrolerami
    • nowe metody “animate with” umożliwiają używanie zarówno wbudowanych animacji, jak i własnych
    • własne tranzycje - implementacja UIViewControllerTransitioningDelegate
  • możliwość wyłączenia potencjalnych animacji
    • [UIView performWithoutAnimation] - przyjmuje blok, który gwarantuje wykonywanie bez animacji
  • CollectionViews
    • możliwość animowania zmian layoutu wewnątrz CollectionView
      • nowa metoda ma blok completion
    • możliwość customizacji tranzycji pomiędzy layoutami za pomocą podklasy UICollectionViewTransitionLayout
    • możliwość koordynacji zmian w layout przez kontroler nawigacji
      • useLayoutToLayoutNavigationTransitions musi być ustawione na NO w bieżącym kontrolerze kolekcji
      • useLayoutToLayoutNavigationTransitions musi być ustawione na YES na otwieranym kontrolerze kolekcji

zoom-in, zoom-out na kolekcji:

…

zoomedInLayout = [[UICollectionViewFlowLayout alloc] init];

zoomedInLayout.itemSize = CGSizeMake(120, 120);

zoomedInLayout.sectionInset = UIEdgeInsetsMake(10, 5, 0, 5);

[self.collectionView addGestureRecognizer: gestuer];

…  //obsługa gestów pinch i stretch

[self.collectionView reloadData];

[self.collectionView setCollectionViewLayout: zoomedInLayout  animated: YES  completion: ^(BOOL finished) {

}];

… //obsługa CollectionView  przypominająca trochę obsługę TableView

Ruch

  • system jest świadomy umiejscowienia i przemieszczania urządzenia w przestrzeni
    • Springboard - efekt paralaksy
  • alerty się przemieszczają wraz ze zmianą położenia
    • Safari - wielostronicowe widoki
  • UIMotionBehavior - główna klasa w nowym API
  • efekty ruchu
    • klasa bazowa UIMotionEffect
      • tworzenie własnych efektów w klasach pochodnych
      • przekazujemy UIOffset - wertykalny/horyzontalny offset ruchu urządzenia
    • UIInterpolatingMotionEffect - zamiast dziedziczenia
      • dodawanie do każdego UIView - addMotionEffect
      • nowa propercja motionEffects - jakie efekty są zaaplikowane do widoku
      • removeMotionEffect - usuwanie widoku
    • UIMotionEffectGroup - kolekcja efektów
      • [UIView addMotionEffect] - możliwość dodania

przykładowe dodanie ruchu:

UIInterpolatingMotionEffect* xmotion = [[UIInterpolatingMotionEffect alloc]

        initWithKeyPath: @”center.x”  type:

        UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];

xmotion.maximumRelativeValue = [NSNumber numberWithFloat: 20.0];

xmotion.minimumRelativeValue = [NSNumber numberWithFloat: –20.0];

//podobnie ymotion

UIMotionEffectGroup* group = [[UIMotionEffectGroup alloc] init];

group.motionEffects = @[xmotion, ymotion];

[self.collectionView addMotionEffect: group];

Dynamics

  • możliwość odsłonięcia kamery na lock screen
    • zetknięcie się z dołem ekranu
    • symulowana waga
  • przewijanie wiadomości pod palcem
  • implementacja za pomocą UIDynamics
    • fizyczny engine wbudowany w UIKit
  • tworzenie zachowania
    • konfiguracja z elementami do animowania
    • dodanie tego do widoku
  • wbudowane zachowania
    • UIAttachmentBehavior - łączy dwa widoki ze sobą
    • UICollisionBehavior - modeluje kolizję między widokami lub granicami
    • UIGravityBehavior - modeluje grawitację: 1000 punktów/s2 zamiast 9.8 m/s2
    • UIPushBehavior - aplikuje siły do widoków
    • UISnapBehavior - snapuje do punktu
    • UIDynamicItemBehavior - dostęp do niskopoziomowych propercji elementu w Dynamics

UIDynamicAnimator* animator;

…

//opadnięcie na dół ekranu i lekkie odbicia przed ostatecznym zatrzymaniem

animator = [[UIDynamicAnimator alloc] initWithReferenceView: self.view];

UIGravityBehavior* gravity = [UIGravityBehavior alloc] initWithItems:  @[labelView];

UICollisionBehavior* collision = [[UICollisionBehavior alloc] initWithItems: @[labelView]];

collision.translatesReferenceBoundsIntoBoundary = YES;

UIDynamicItemBehavior* dynamic = [[UIDynamicItemBehavior alloc] initWithItems: @[labelView]];

dynamic.elasticity = .7;

[animator addBehavior: dynamic];

[animator addBehavior: gravity];

[animator addBehavior: collision];

Przywracanie stanu

  • możliwość rejestracji obiektów do bycia częścią przywracanego stanu
    • [UIApplication registerObjectForStateRestoration:  restorationIdentifier: ]
      • implementacja protokołu UIStateRestoration
  • przechwytywanie snaphotów
    • iOS 7 używa snapshotu przy uruchamianiu
    • ignoreSnapshotOnNextApplicationLaunch

Adaptatywny multitasking

  • nowe UI prezentujące uruchomione aplikacje
  • aplikacja może aktualizować UI w tle dzięki nowym właściwościom notyfikacji i transferu w tle
    • w nowym UI system robi wizualny snaphot aplikacji
  • wrażenie ciągłego wykonywania się aplikacji
    • background fetch
    • remote notifications
    • background transfer service

iCloud

  • największe do tej pory ulepszenie niezawodności i wydajności
  • testowanie w symulatorze
  • integracja z narzędziami debugującymi w XCode

Objective-C

  • moduły - bardziej efektywny sposób importu frameworków
    • @import (binarnie) zamiast #import (tekstowo)
    • automatyczny import innych modułów w razie potrzeby
  • instancetype - weryfikacja przez kompilator zwracanych wartości
  • jawnie typowane enum-y
  • poprawa szybkości ARC

XCode

  • “Asset Catalog” np. Images.xcassets
    • utrzymuje listę obrazków, w tym ikonę aplikacji i splash-screeny
    • dla każdego obrazka pokazywane są wszystkie rozdzielczości
    • nazwy nie są już istotne (do tej pory używano w nazwach plików konwencji nazewniczych dla retiny - np. Default@2x.png i różnych rozdzielczości np. Default-586@2x.png)
  • tylko LLVM (gcc już niedostępny)
  • podgląd .xib w różnych wersjach iOS i orientacjach ekranu
  • XCTest zamiast OCTest
  • …

Rozszerzenia frameworku

  • Store Kit - weryfikacja in-app purchases na urządzeniu lub serwerze
  • bezpieczeństwo - dostęp do iCloud keychain
  • Pass Kit - wiele przejść za jednym razem, lepsza kontrola
  • OpenGL ES - rozszerzenia
  • Message UI - dodawanie załączników
  • Map Kit - wsparcie dla map 3d i innych rozszerzeń
  • Audio Unit - miksowanie audio między aplikacjami

Nowe właściwości i frameworki

  • AirDrop - transfer plików pomiędzy urządzeniami w bliskiej odległości (NFC)
  • komunikacja peer-to-peer
  • framework JavaScript Core - dostęp do maszyny JavaScript z poziomu Objective-C
  • media - kontrola zamkniętych opisów
  • Safari Services - dodawanie url-i do listy odczytu
  • Sprite Kit - engine gier 2d
  • Game Controllers - abstrakcja używania kontrolerów do gier

Rzeczy usunięte

  • adresy UUID i MAC (deprecated), zamiast tego UIDevice.identifierForVendor

Wspieranie iOS6

  • możliwość obsługi iOS 6 i iOS7
  • zalecana jest stylizacja z iOS 7 (mimo innych kontrolek)

 

Ogólne uzupełnienia

przykładowe sharowanie:

UIActivityViewController* activity = [[UIActivityViewController alloc]

         initWithActivityItems: @[@”xxx”]  applicationActivities: nil];

[self presentViewController: activity animated: YES  completion: ^{

}];

proste odtworzenie dźwięku *.aif:

SystemSoundID soundID;

AudioServicesCreateSystemSoundID ((CFURLRef) CFBridgingRetain([NSURL fileURLWithPath:

         soundPath]), &soundID);

AudioServicesPlaySystemSound (soundID);

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