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();

Brak komentarzy: