Îmbunătățirea aplicației foto cu GPUImage & iCarousel

Acest tutorial vă va învăța cum să utilizați GPUImage pentru a aplica filtre de imagine în timp real pe măsură ce este afișat feed-ul aparatului foto. Pe parcurs, veți învăța cum să populați automat imaginile într-un controler carusel și cum să redimensionați imaginile cu UIImage + Categorii.


rezumatul proiectului


Tutorial Precondiții

Acest tutorial se bazează pe un post anterior intitulat "Creați o aplicație foto cu GPUImage". Lecția precedentă a demonstrat modul de utilizare UIImagePickerController pentru a selecta fotografii din albumul foto sau camera foto a aparatului foto, cum se adaugă GPUImage bibliotecă pentru proiectul dvs. și cum să utilizați GPUImageFilter pentru a îmbunătăți cadrele aparatului foto. Dacă sunteți deja familiarizat cu UIImagePickerController și pot afla cum să adăugați GPUImage la proiectul dvs. pe cont propriu, ar trebui să puteți să vă ridicați de unde sa terminat ultimul tutorial.


Pasul 1: Importați iCarousel

Acest proiect va utiliza în mare măsură un proiect open-source numit iCarousel pentru a adăuga o prezentare elegantă a fotografiilor selectate.

Pentru a include iCarousel în proiect, accesați pagina oficială GitHub și descărcați codul sursă ca fișier zip. Extrageți codul din fișierul ZIP și apoi trageți și plasați folderul intitulat "iCarousel" în Xcode Project Navigator. Acest dosar trebuie să conțină atât iCarousel.h și iCarousel.m. Asigurați-vă că ați selectat "Creați grupuri pentru toate dosarele adăugate" și bifați caseta de lângă "Copiați articolele în dosarul grupului de destinație (dacă este necesar)", precum și caseta de lângă numele țintă al proiectului în zona "Adăugați la ținte".

Urmărește-te ViewController.m și adăugați o declarație de import pentru iCarousel:

 #import "ViewController.h" #import "GPUImage.h" #import "iCarousel / iCarousel.h"

Pasul 2: Importați categoriile UIImage +

Înainte de a ne afișa imaginile cu iCarousel, va trebui să le diminuăm până la o dimensiune acceptabilă. Mai degrabă decât să scrieți tot codul pentru a face acest lucru manual, vom face uz de proiectul excelent UIImage + Categories, care oferă funcționalități de bază pentru redimensionarea imaginilor, precum și câteva alte trucuri de manipulare a imaginilor.

Bacsis: Alternativ, puteți utiliza proiectul MGImageUtilities pentru această sarcină. În timp ce detaliile implementării vor diferi ușor, acesta oferă, de asemenea, un sprijin excelent pentru scalarea UIImage.

Descărcați UIImage + Categorii cod de la GitHub și apoi creați un nou grup cu același nume în Xcode. Trageți atât fișierele de implementare, cât și cele de antet pentru UIImage + Alpha, UIImage + Resize, și UIImage + RoundedCorner în proiectul dvs. Asigurați-vă că ați selectat "Creați grupuri pentru toate dosarele adăugate" și bifați caseta de lângă "Copiați articolele în dosarul grupului de destinație (dacă este necesar)", precum și caseta de lângă numele țintă al proiectului în zona "Adăugați la ținte".

În cadrul ViewController.m fișier, importați categoriile de imagini cu următorul rând de cod:

 #import "ViewController.h" #import "GPUImage.h" #import "iCarousel.h" #import "UIImage + Resize.h"

Pasul 3: Adăugați vizualizarea iCarousel în IB

Cu codul iCarousel importat în proiectul nostru, treceți la MainStoryboard.storyboard fișier pentru a reface interfața noastră.

Mai întâi, selectați actul curent UIImageView conectat la selectedImageView IBOutlet și ștergeți-l. Reveniți la ViewController.m și modificați codul proiectului astfel:

 @property (nonatomic, slab) IBOutlet iCarousel * photoCarousel; @property (nonatomic, slab) IBOutlet UIBarButtonItem * filterButton; @property (nonatomic, slab) IBOutlet UIBarButtonItem * saveButton; - (IBAction) photoFromAlbum; - (IBAction) photoFromCamera; - (IBAction) saveImageToAlbum; - (IBAction) applyImageFilter: (id) expeditor; @end @implementation ViewController @synthesize photoCarousel, filterButton, saveButton;

Pe linia 1 de mai sus, înlocuiți selectedImageView ieșire cu a iCarousel numit photoCarousel. Schimbați și variabilele din instrucțiunea de sinteză a liniei 14 de mai sus.

Reveniți la Interface Builder și trageți un nou UIView pe controlerul de vizualizare. Cu noul UIView selectați, accesați fila "Inspector de identitate" din panoul Utilities și setați valoarea câmpului "Class" la "iCarousel". Acest lucru spune Interface Builder că UIView pe care am adăugat la proiect trebuie să fie instanțiată ca o instanță a iCarousel clasă.

Acum faceți o conexiune între photoCarousel ieșire doar declarată și UIView adăugat doar ca un subvior.

Trebuie să setăm atât sursa de date, cât și să o delegem photoCarousel precum și putem realiza acest lucru din cadrul interfeței Builder. Mai întâi, du-te la ViewController.h și să declare că acest controler de vizualizare se va conforma protocoalelor corespunzătoare:

 #import  #import "iCarousel / iCarousel.h" @interface ViewController: UIViewController 

Pe linia 2 importăm iCarousel, iar pe linia 4 declarăm conformitatea atât cu delegatul, cât și cu sursa de date.

Înapoi în fișierul storyboard, puteți acum să cartografiați atât sursa de date cât și delegatul la controlerul de vizualizare.

Înainte de a continua, treceți și schimbați culoarea de fundal a imaginii iCarousel vedeți la negru.

Bine, doar un singur lucru. Vrem ca vizualizarea iCarousel să apară sub UIToolbar în ierarhia vizuală. Puteți face acest lucru vizual prin simpla tragere a acestora în ordinea corectă în Interface Builder:

Rețineți cum arată acum imaginea iCarousel înaintea barei de instrumente Google.

Salvați-vă munca în Interface Builder.


Pasul 4: Implementați protocoalele iCarousel

iCarousel folosește un model de design asemănător cu UITableView în care o sursă de date este folosită pentru a alimenta intrarea în control și un delegat este folosit pentru a manipula interacțiunea cu controlul.

Pentru proiectul nostru, sursa de date va fi un simplu NSMutableArray numite "displayImages". Adăugați aceasta la extensia de clasă din ViewController.m acum:

 #import "UIImage + Resize.h" @interface ViewController () NSMutableArray * displayImages;  @property (nonatomic, slab) IBOutlet iCarousel * photoCarousel;

Apoi, vrem să alocăm memoria pentru matrice în clasa inițializatorului desemnat. În cazul nostru, controlerul de vizualizare va fi instanțiat dintr-un Storyboard, deci inițializatorul propriu-zis este initWithCoder:. Cu toate acestea, dacă clasa ar trebui să fie instanțiată programabil dintr-un XIB, ar fi inițializatorul corespunzător initWithNibName: pachet:. Pentru a acomoda fie stilul de inițializare, vom scrie propriul inițializator personalizat și îl sunăm din ambele, după cum urmează:

 - (void) customSetup displayImages = [[NSMutableArray aloca] init];  - (id) initWithNibName: (NSString *) pachet nibNameOrNil: (NSBundle *) nibBundleOrNil if (self = [super initWithNibName: nibNameOrNil bundle: nibBundleOrNil])) [custom customSetup];  întoarce-te;  - (id) initWithCoder: (NSCoder *) aDecoder if ((self = [super initWithCoder: aDecoder])) [custom customSetup];  întoarce-te; 

Acum putem implementa sursa de date și delega. Începeți cu metoda sursei de date numberOfItemsInCarousel:, ca astfel:

 #pragma marcă #pragma marcă iCarousel DataSource / Delegat / Custom - (NSUInteger) numberOfItemsInCarousel: (iCarousel *) carusel return [countImages count]; 

Acest lucru îi va spune iCarousel numărul de imagini care se afișează prin vizualizarea numărului de imagini stocate în matricea sursei de date.

Apoi, scrieți metoda care va genera de fapt o vizualizare pentru fiecare imagine afișată în carusel:

 - (UIView *) carusel: (iCarousel *) vizualizarea caruseluluiForItemAtIndex: (NSUInteger) index reuseView: (UIView *) vizualizare // Crearea unei noi vizualizări dacă nu este disponibilă nici o vizualizare pentru reciclare dacă view == nil) view = [[UIImageView alin] initWithFrame: CGRectMake (0, 0, 300.0f, 300.0f)]; view.contentMode = UIViewContentModeCenter;  (Vizualizare (UIImageView *)) .image = [displayImages objectAtIndex: index]; retur; 

Acesta este un început bun, dar există o problemă foarte importantă cu cele de mai sus: imaginile ar trebui să fie reduse înainte de a fi furnizate iCarousel. Adăugați următoarele rânduri de cod pentru actualizarea metodei:

 - (UIView *) carusel: (iCarousel *) vizualizarea caruseluluiForItemAtIndex: (NSUInteger) index reuseView: (UIView *) vizualizare // Crearea unei noi vizualizări dacă nu este disponibilă nici o vizualizare pentru reciclare dacă view == nil) view = [[UIImageView alin] initWithFrame: CGRectMake (0, 0, 300.0f, 300.0f)]; view.contentMode = UIViewContentModeCenter;  // Scala inteligentă până la o lățime sau înălțime maximă de 250px UIImage * originalImage = [displayImages objectAtIndex: index]; CGSize maxSize = CGSizeMake (250.0f, 250.0f); CGSize targetSize; // Dacă imaginea este peisaj, setați la 250px și dați dinamic înălțimea dacă (originalImage.size.width> = originalImage.size.height) float newHeightMultiplier = maxSize.width / originalImage.size.width; targetSize = CGSizeMake (maxSize.width, rotund (originalImage.size.height * newHeightMultiplier));  // Dacă imaginea este portret, setați înălțimea la 250px și dați dinamic altă lățime float newWidthMultiplier = maxSize.height / originalImage.size.height; targetSize = CGSizeMake (rotund (newWidthMultiplier * originalImage.size.width), maxSize.height);  // Redimensionați imaginea sursă în jos pentru a se încadra frumos în vizualizarea iCarousel ((UIImageView *)) .image = [[objectImages objectAtIndex: index] resizedImage: targetSize interpolareQuality: kCGInterpolationHigh]; retur; 
Pro Tip: Utilizarea acestei metode într-o aplicație de producție? Luați în considerare îmbunătățirea codului de performanță prin redimensionarea imaginii pe un fir de fundal și păstrarea unui NSMutableArray separat care stochează imaginile reduse. UPDATE 9/27/2012: Nick Lockwood (autorul iCarousel) a lansat un proiect numit FXImageView care va prelua automat încărcarea imaginii pe un fir de fundal. De asemenea, vine cu alte clopoței utile, cum ar fi umbrele picăturilor și colțurile rotunjite, așa că verificați-l!

Deasupra, am setat o dimensiune maximă de 250px pentru fie lățimea sau înălțimea imaginii, apoi scarăm atributul opus în jos pentru a se potrivi. Acest lucru constrânge proporțiile imaginii și arată mult mai plăcut decît pur și simplu scalarea până la un pătrat de 250 × 250 px.

Cele două metode de mai sus sunt toate pentru care iCarousel trebuie să înceapă să afișeze imagini.

Odată cu configurarea metodelor delegate și sursă de date, acum este momentul potrivit pentru configurarea obiectului iCarousel în viewDidLoad și metoda:

 - (vid) viewDidLoad [super viewDidLoad]; // Configurația iCarousel self.photoCarousel.type = iCarouselTypeCoverFlow2; self.photoCarousel.bounces = NU; 

Cu doar câteva modificări, proiectul va putea afișa imagini într-un carusel!


Pasul 5: Comutați la noul model de date

Mai devreme în acest tutorial, am înlocuit selectedImageView proprietate cu photoCarousel proprietate, a actualizat interfața Storyboard pentru a se potrivi și a creat un NSMutableArray pentru a acționa ca model de date iCarousel. Cu toate acestea, există câteva metode în ViewController.m folosind încă vechiul model de date care va împiedica compilarea proiectului, așa că să le rezolvăm acum. Actualizați saveImageToAlbum astfel:

 - (IBAction) saveImageToAlbum UIImage * selectImage = [afișareImagini objectAtIndex: self.photoCarousel.currentItemIndex]; UIImageWriteToSavedPhotosAlbum (selectatImage, auto, @selector (imagine: didFinishSavingWithError: contextInfo :), nil); 

Linia 3 selectează UIImage din modelul de date care se potrivește cu indicele iCarousel curent. Linia 4 realizează scrierea reală a discului cu acea imagine.

Apoi, mergeți la UIImagePickerController delegați metoda și modificați codul:

 - (void) imagePickerController: (UIImagePickerController *) fotoPicker didFinishPickingMediaWithInfo: (NSDictionary *) info self.saveButton.enabled = DA; self.filterButton.enabled = DA; [displayImages addObject: [info valueForKey: UIImagePickerControllerOriginalImage]]; [self.photoCarousel reloadData]; [photoPicker dismissViewControllerAnimated: DA terminat: NULL]; 

În linia 6 de mai sus, adăugăm fotografia selectată la noul model, iar pe linia 8 forțăm o reîmprospătare a caruselului.

Doar o altă schimbare de făcut. Mergeți la foaia de acțiune clickedButtonAtIndex: și modificați codul după cum urmează:

 #pragma marcă - #pragma marca UIActionSheetDelegate - (void) actionSheet: (UIActionSheet *) acțiuneSheet clickedButtonAtIndex: (NSInteger) buttonIndex if (buttonIndex == actionSheet.cancelButtonIndex) return;  GPUImageFilter * selectFilter; comutator (butonIndex) caz 0: selectFilter = [[GPUImageGrayscaleFilter alocare] init]; pauză; cazul 1: selectFilter = [[GPUImageSepiaFilter alloc] init]; pauză; cazul 2: selectFilter = [[GPUImageSketchFilter alloc] init]; pauză; cazul 3: selectFilter = [[GPUImagePixellateFilter alloc] init]; pauză; cazul 4: selectFilter = [[GPUImageColorInvertFilter alocare] init]; pauză; cazul 5: selectedFilter = [[GPUImageToonFilter alloc] init]; pauză; cazul 6: selectFilter = [[GPUImagePinchDistortionFilter alloc]]; pauză; cazul 7: selectFilter = [[GPUImageFilter alloc] init]; pauză; prestabilit: pauză;  UIImage * filteredImage = [selectatFilter imageByFilteringImage: [displayImages objectAtIndex: self.photoCarousel.currentItemIndex]]; [displayImages replaceObjectAtIndex: auto.photoCarousel.currentItemIndex cuObject: filteredImage]; [self.photoCarousel reloadData]; 

Cele trei linii finale ale acestei metode vor filtra imaginea modelului de date care corespunde indexului caruselului actual, vor înlocui afișajul carusel cu acea imagine și apoi vor reîmprospăta caruselul.

Dacă totul a mers bine, acum ar trebui să puteți compila și rula proiectul! Acest lucru vă va permite să vizualizați imaginile în carusel, în loc de a pur și simplu în cadrul unei imagini.


Pasul 6: Adăugați un gest pentru ștergerea imaginilor

Aplicația arată bine până acum, însă ar fi frumos ca utilizatorul să poată elimina o fotografie din carusel după adăugarea acesteia pe ecran. Nici o problema! Putem selecta orice UIGestureRecognizer subclasa pentru a face acest lucru să se întâmple. Pentru acest tutorial, am ales să folosesc dublu atingere cu două degete. Acest gest nu poate fi imediat intuitiv, dar este ușor de realizat, iar complexitatea adăugată va ajuta la prevenirea eliminării accidentale a imaginilor.

În cadrul ViewController.m fișier, mergeți la carusel: viewForItemAtIndex: reusingView: și adăugați următoarele linii chiar înainte de sfârșitul metodei:

 // Redimensionați imaginea sursă în jos pentru a se încadra frumos în vizualizarea iCarousel ((UIImageView *)) .image = [[objectImages objectAtIndex: index] resizedImage: targetSize interpolareQuality: kCGInterpolationHigh]; // Două atingeri cu două degete vor șterge o imagine UITapGestureRecognizer * gesture = [[UITapGestureRecognizer alocare] initWithTarget: acțiune automată: @selector (removeImageFromCarousel :)]; gesture.numberOfTouchesRequired = 2; gesture.numberOfTapsRequired = 2; view.gestureRecognizers = [NSArray arrayWithObject: gest]; retur;

Liniile 4-8 declară o nouă UITapGestureRecognizer obiect, setați numărul de atingeri (adică degetele) necesare pentru declanșarea gestului la 2 și setați și numărul de robinete necesare la 2. În cele din urmă, chiar înainte de a trece viziunea înapoi la obiectul iCarousel, setăm gestureRecognizers proprietate cu recunoscutul nou format.

Rețineți că atunci când este declanșat, acest recunoaștere a gestului va declanșa selectorul removeImageFromCarousel:. Să implementăm următorul lucru:

 - (void) removeImageFromCarousel: (UIGestureRecognizer *) gest gesture removeTarget: actiune auto: @selector (removeImageFromCarousel :)]; [displayImages removeObjectAtIndex: auto.photoCarousel.currentItemIndex]; [self.photoCarousel reloadData]; 

Linia 3 elimină gestul din ținta curentă pentru a preveni declanșarea mai multor gesturi în timpul procesării. Celelalte două linii nu sunt nimic nou în acest moment.

Construiți și rulați aplicația din nou. Acum ar trebui să eliminați dinamic articolele din carusel!


Pasul 7: Creați MTCameraViewController

Restul acestui tutorial se va concentra pe utilizarea GPUImageStillCamera pentru a construi un control personalizat de selectare a camerelor, care să poată aplica filtre în fluxul video în timp real. GPUImageStillCamera lucrează îndeaproape cu o clasă numită GPUImageView. Ramele camerelor generate de GPUImageStillCamera sunt trimise către un asociat GPUImageView obiect pentru afișarea către utilizator. Toate acestea se realizează cu funcționalitatea de bază furnizată de platformei AV Foundation cadru, care oferă acces programatic la datele cadrului camerei.

pentru că GPUImageView este o clasă pentru copii UIView, putem încorpora întregul ecran al camerei în propriul nostru personalizat UIViewController clasă.

Adăugați un mesaj nou UIViewController subclasa la proiect făcând clic dreapta pe "PhotoFX" din navigatorul de proiect și apoi selectând Fișier nou> Clasă obiectiv-C. Denumiți clasa "MTCameraViewController" și introduceți "UIViewController" în câmpul "subclasa".

Dați clic pe "Următorul" și apoi pe "Creați" pentru a finaliza procesul.

Mergeți la MTCameraViewController.m fișier și import GPUImage:

 #import "MTCameraViewController.h" #import "GPUImage.h"

Apoi, creați o extensie de clasă cu membrii de date GPUImage necesari:

 @interface MTCameraViewController ()  GPUImageStillCamera * stillCamera; Filtru GPUImageFilter *;  @Sfârșit

În cele din urmă, du-te la viewDidLoad: și adăugați codul pentru a porni captura de cameră:

 - (vid) viewDidLoad [super viewDidLoad]; // Filtru filtru filtru inițial filtru = [[GPUImageFilter alloc] init]; [filtru prepareForImageCapture]; GPUImageView * filterView = (GPUImageView *) auto-vizualizare; [filtru addTarget: filterView]; // Creați camera personalizată GPUImage stillCamera = [[GPUImageStillCamera alloc] init]; stillCamera.outputImageOrientation = UIInterfaceOrientationPortrait; [stillCamera addTarget: filtru]; // începe afișarea fluxului de camere video [stillCamera startCameraCapture]; 

Liniile 5 - 9 creează un nou GPUImageView pentru afișarea fluxului camerei foto și implicit GPUImageFilter exemplu pentru aplicarea unui efect special asupra vederii. Am putea folosi la fel de ușor și unul dintre ele GPUImageFilter subclase, cum ar fi GPUImageSketchFilter, dar vom începe cu filtrul implicit (adică fără manipulări) și lăsăm utilizatorului să aleagă în mod dinamic un filtru ulterior.

Linile 11-17 instanțiate instanța camerei GPU și aplicați filtrul creat anterior camerei înainte de a începe captura.


Pasul 8: Adăugați camera personalizată în IB

Înainte ca codul de la pasul 8 să funcționeze, trebuie să adăugăm codul personalizat MTCameraViewController clasa creată doar în Storyboard-ul proiectului.

Deschide MainStoryboard.storyboard fișier și trageți un nou controler de vizualizare din biblioteca de obiecte. Cu acest obiect selectat, mergeți la fila Inspector de identitate și setați valoarea câmpului "Class" la "MTCameraViewController".

Apoi trageți a UIToolbar pe ecran și setați proprietatea stilului la "Negru opac" din inspectorul Atribute. Apoi adăugați două elemente de buton pentru bare de lățime flexibile în bara de instrumente, cu o funcție "Take Photo" UIBarButtonItem in centru.

Pentru a conecta acest controler de vizualizare la fluxul de aplicații, faceți clic dreapta pe butonul "cameră" de la controlerul principal de vizualizare și trageți priza de semnal declanșată la noul controller de vizualizare:

Când vi se solicită, selectați "Push" ca stil de siguranță.

Cu noul obiect segmente nou adăugat selectat, mergeți la "Inspectorul atributelor" și setați identificatorul la "pushMTCamera". Mergeți mai departe și asigurați-vă că este selectată opțiunea "Push" din meniul derulant "Stil".

Odată cu crearea de segmente, asigurați-vă că UIImagePicker nu va mai fi afișată atunci când utilizatorul pune butonul camerei pe primul ecran al aplicației deconectând IBAction ieșire din photoFromCamera metodă.

În cele din urmă, selectați vizualizarea primară a MTCameraViewController nou creat. Mergeți la Inspectorul de identitate și setați valoarea de clasă la "GPUImageView".

În timp ce nu este perfect încă, dacă construiți și rulați aplicația acum, ar trebui să puteți să împingeți MTCameraViewController pe ierarhia vizuală și ceas GPUImageView afișați cadrele camerei în timp real!


Pasul 9: Adăugați selectarea filtrului în timp real

Acum putem adăuga logica necesară pentru a controla filtrul aplicat pe afișajul camerei. Mai întâi, du-te la viewDidLoad: metoda în cadrul MTCameraViewController.m fișier și adăugați codul care va crea un buton "Filtru" în partea din dreapta sus a controlerului de vizualizare:

 - (vid) viewDidLoad [super viewDidLoad]; // Adaugă butonul filtru pentru interfață UIBarButtonItem * filterButton = [[UIBarButtonItem alin] initWithTitle: @ Stilul "Filtru": UIBarButtonItemStylePlain țintă: auto acțiune: @selector (applyImageFilter :); auto.navigationItem.rightBarButtonItem = filterButton;

Pe linia 6 de mai sus, creăm un obicei UIBarButtonItem care va declanșa applyImageFilter: când este selectat.

Acum creați metoda selectorului:

 - (IBAction) applyImageFilter: (id) expeditor UIActionSheet * filterActionSheet = [[UIActionSheet alocare] initWithTitle: @ Delegat "Select Filter": self cancelButtonTitle: @ "Cancel" destructiveButtonTitle: nil otherButtonTitles: @ "Grayscale" @ "Sketch", @ "Pixellate", @ "Color Invert", @ "Toon", @ "Pinch Distort", @ "None", nul]; [filterActionSheet showFromBarButtonItem: expeditor animat: DA]; 

După adăugarea celor de mai sus, veți vedea un avertisment de compilator prin care se afirmă că actualul controler de vizualizare nu este în conformitate cu UIActionSheetDelegate protocol. Fixați problema acum prin a merge la MTCameraViewController.h și modificarea declarației clasei așa:

 #import  @ interfață MTCameraViewController: UIViewController  @Sfârșit

Completați cercul, revenind la MTCameraViewController.m fișier și adăugarea logicii care va răspunde la UIActionSheet a prezentat:

 - (void) actionSheet: (UIActionSheet *) actionSheet clickedButtonAtIndex: (NSInteger) buttonIndex // Bail daca a fost lovit butonul cancel if (actionSheet.cancelButtonIndex == buttonIndex) retur;  GPUImageFilter * selectFilter; [stillCamera removeAllTargets]; [filter removeAllTargets]; comutator (butonIndex) caz 0: selectFilter = [[GPUImageGrayscaleFilter alocare] init]; pauză; cazul 1: selectFilter = [[GPUImageSepiaFilter alloc] init]; pauză; cazul 2: selectFilter = [[GPUImageSketchFilter alloc] init]; pauză; cazul 3: selectFilter = [[GPUImagePixellateFilter alloc] init]; pauză; cazul 4: selectFilter = [[GPUImageColorInvertFilter alocare] init]; pauză; cazul 5: selectedFilter = [[GPUImageToonFilter alloc] init]; pauză; cazul 6: selectFilter = [[GPUImagePinchDistortionFilter alloc]]; pauză; cazul 7: selectFilter = [[GPUImageFilter alloc] init]; pauză; prestabilit: pauză;  filter = selectFilter; GPUImageView * filterView = (GPUImageView *) auto-vizualizare; [filtru addTarget: filterView]; [stillCamera addTarget: filtru]; 

Linile 11-12 sunt utilizate pentru a reseta filtrul selectat curent.

Linile 15 - 42 de mai sus ar trebui să pară familiarizate cu logica ViewController.m; doar pornim butonul selectat pentru a crea o instanță a filtrului de corelare.

Liniile 44 - 47 iau noul filtru creat și îl aplicați camerei GPUImage.

Dacă construiți și rulați proiectul acum, ar trebui să vedeți că butonul filtru nou creat permite utilizatorului să încerce filtre GPUImage în timp real!


Pasul 10: Creați un protocol de delegat al camerei

Acum, când avem filtre live feed, ultima etapă importantă a tutorialului este de a permite utilizatorului să efectueze instantanee cu camera GPUImage și apoi să le afișeze înapoi în caruselul foto al controlerului principal.

Pentru a realiza acest lucru, vom transmite mesaje între controlorii de vizualizare utilizând modelul de design al delegării. În mod specific, vom crea propriul nostru protocol delegat oficial delegat în MTCameraViewController și apoi configurați principalele ViewController să se conformeze acelui protocol pentru a primi mesaje de delegație.

Pentru a începe, mergeți la MTViewController.h și modificați codul după cum urmează:

 #import  @protocol MTCameraViewControllerDelegate - (void) didSelectStillImage: (NSData *) imagine cuError: (NSError *) eroare; @end @interface MTCameraViewController: UIViewController @property (slab, nonatomic) delegat id; @Sfârșit

Codul de mai sus declară un model formal delegat numit MTCameraViewControllerDelegate pe liniile 3-7 și apoi creează un obiect delegat pe linia 11.

Apoi treceți la MTCameraViewController.m și sintetizează proprietatea delegată:

 @implementation MTCameraViewController @synthesize delegate;

Cu protocolul declarat, acum trebuie să îl implementăm în principal ViewController clasă. Mergi la ViewController.h și adăugați următoarele rânduri:

 #import  #import "iCarousel.h" #import "MTCameraViewController.h" @interface ViewController: UIViewController  @Sfârșit

Deschide acum ViewController.m fişier. Vrem să atribuim proprietatea delegat atunci când controlerul de vizualizare este instanțiat. Pentru că folosim Storyboards, locul potrivit pentru a face acest lucru este în prepareForSegue: expeditor: , care va fi apelată chiar înainte ca noul controler de vizualizare să fie împins pe ecran:

 - (id) expeditor if ([segue.identifier isEqualToString: @ "pushMTCamera"]) // Setați delegatul astfel încât acest controler poate primi fotografii snapped MTCameraViewController * cameraViewController = (MTCameraViewController *) segue.destinationViewController; cameraViewController.delegate = auto; 

Apoi trebuie să punem în aplicare didSelectStillImage: withError: metoda cerută de MTCameraViewControllerDelegate protocol:

 #pragma mark - #pragma mark MTCameraViewController // Această metodă delegată se numește după ce clasa noastră de camere personalizate are o fotografie (void) didSelectStillImage: (NSData *) imageData withError: (NSError *) eroare if (! eroare) UIImage * image = [[UIImage alocare] initWithData: imageData]; [displayImages addObject: imagine]; [self.photoCarousel reloadData]; self.filterButton.enabled = DA; self.saveButton.enabled = DA;  altceva UIAlertView * alert = [[UIAlertView alloc] initWithTitle: @ "Eroare de captură": @ "Imposibil de capturat fotografie". delegat: nil cancelButtonTitle: @ "OK" altButtonTitle: nil]; [alertă spectacol]; 

Codul de mai sus va converti NSData obiect predat metodei la a UIImage și apoi reîncărcați caruselul foto.

În cele din urmă, trebuie să încheiem lucrurile prin revenirea la MTCameraViewController.m și adăugarea în apelul corespunzător al metodei delegatului. Mai întâi, configurați un IBAction metoda care va declanșa un snap de cameră:

 Filtru GPUImageFilter *;  - (IBAction) captureImage: (id) expeditor; @Sfârșit

Înainte de a continua, conectați această metodă la butonul "Faceți fotografie" din MainStoryboard.storyboard fişier.

În cele din urmă, adăugați implementarea metodei:

 -(IBAction) captureImage: (id) expeditor // Dezactivează pentru a preveni mai multe robinete în timp ce procesează UIButton * captureButton = (UIButton *) expeditor; captureButton.enabled = NO; // Snap Imagine de la aparatul de fotografiat GPU, trimite înapoi la controlerul principal de vizualizare [stillCamera capturePhotoAsJPEGProcessedUpToFilter: filtru cuCompletionHandler: ^ (NSData * processedJPEG, NSError * eroare) if ([delegate respondsToSelector: @selector (didSelectStillImage: withError :)] self.delegate didSelectStillImage: procesateJPEG cu eroare: eroare];  altceva NSLog (@ "Delegatul nu a răspuns la mesaj");  runOnMainQueueWithoutDeadlocking (^ [auto.navigationController popToRootViewControllerAnimated: YES];); ]; 

Liniile 3-5 de mai sus dezactivează butonul "Faceți fotografie" pentru a împiedica apăsările multiple în timpul procesării.

Liniile 7 - 22 utilizează metoda GPUImage capturePhotoAsJPEGProcessedUpToFilter: withCompletionHandler: pentru a salva efectiv o imagine JPEG, verificați dacă un delegat a fost setat și apoi trimiteți datele de imagine către delegat dacă este setat.

Linia 19 afișează controlerul de vizualizare curent, dar face acest lucru pe firul principal al aplicației.


Învelire

Felicitări! Dacă ați urmat tutorialul în acest moment, atunci ar trebui să aveți o aplicație complet funcțională, avansată de fotografie! Dacă aveți întrebări sau feedback, nu ezitați să o lăsați în secțiunea de comentarii de mai jos sau să le trimiteți direct peste Twitter (@markhammonds).

Vă mulțumim pentru lectură!

Cod