Forbedre en bildeapp med GPUImage & iCarousel

Denne opplæringen vil lære deg hvordan du bruker GPUImage til å bruke bildefiltre i sanntid, ettersom enhetens kamerainnføring vises. Underveis lærer du hvordan du automatisk fyller bilder i en karusellkontroller og hvordan du endrer størrelsen på bilder med UIImage + Kategorier.


prosjekt oversikt


Opplæringsforutsetninger

Denne opplæringen bygger på et tidligere innlegg med tittelen "Bygg et bildeapp med GPUImage". Den forrige leksjonen viste hvordan man skal bruke UIImagePickerController for å velge bilder fra enhetens fotoalbum eller kamera, hvordan du legger til GPUImage bibliotek til prosjektet ditt, og hvordan du bruker GPUImageFilter klassen for å forbedre stillbilder. Hvis du allerede er kjent med UIImagePickerController og kan finne ut hvordan man legger til GPUImage til prosjektet ditt på egenhånd, bør du kunne plukke opp fra hvor den siste opplæringen slått seg helt greit ut.


Trinn 1: Importer iCarousel

Dette prosjektet vil gjøre omfattende bruk av et åpen kildekodeprosjekt kalt iCarousel for å legge til stilig visning av utvalgte bilder.

For å inkludere iCarousel i prosjektet, gå til den offisielle GitHub siden og last ned kildekoden som en zip-fil. Trekk ut koden fra ZIP-filen og dra og slipp deretter mappen med tittelen "iCarousel" i Xcode Project Navigator. Denne mappen skal inneholde begge iCarousel.h og iCarousel.m. Sørg for å velge "Opprett grupper for eventuelle tilføyte mapper" og merk av i boksen ved siden av "Kopier elementer i destinasjonsgruppens mappe (om nødvendig)" i tillegg til boksen ved siden av prosjektets målnavn i området "Legg til mål".

Neste gå til ViewController.m og legg til en importerklæring for iCarousel:

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

Trinn 2: Importer UIImage + kategorier

Før vi viser bildene våre med iCarousel, må vi skalere dem ned til en akseptabel størrelse. I stedet for å skrive all koden for å gjøre dette for hånd, vil vi gjøre bruk av det utmerkede UIImage + Kategorien-prosjektet, som gir grunnleggende funksjonalitet for å endre størrelsen på bildene, samt noen andre bilde manipulasjons triks.

Tips: Du kan alternativt bruke MGImageUtilities-prosjektet for denne oppgaven. Selv om implementeringsdetaljer vil avvike noe, gir det også utmerket støtte til UIImage-skalering.

Last ned UIImage + Kategorier kode fra GitHub og deretter opprette en ny gruppe med samme navn i Xcode. Dra både implementerings- og toppfiler for UIImage + Alpha, UIImage + Resize, og UIImage + RoundedCorner inn i prosjektet ditt. Sørg for å velge "Opprett grupper for eventuelle tilføyte mapper" og merk av i boksen ved siden av "Kopier elementer i destinasjonsgruppens mappe (om nødvendig)" i tillegg til boksen ved siden av prosjektets målnavn i området "Legg til mål".

Innen ViewController.m fil, importer bildekategoriene med følgende linje kode:

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

Trinn 3: Legg til iCarousel View i IB

Med iCarousel-koden importert til prosjektet, bytt til MainStoryboard.storyboard fil for å omarbeide grensesnittet vårt.

Velg først gjeldende UIImageView koblet til selectedImageView IBOutlet og slett den. Bytt tilbake til ViewController.m og endre prosjektkoden for å lese som følger:

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

På linje 1 over, erstatt selectedImageView uttak med a iCarousel uttak kalt photoCarousel. Bytt ut variablene i syntetiseringsoppgaven på linje 14 ovenfor også.

Gå tilbake til Interface Builder og dra en ny UIView på visningsregulatoren. Med den nye UIView valgt, gå til "Identitetsinspektør" -fanen i Verktøy-panelet og sett verdien for "Klasse" -feltet til "iCarousel". Dette forteller Interface Builder at UIView Vi legger til prosjektet bør være instantiated som en forekomst av iCarousel klasse.

Gjør nå en forbindelse mellom photoCarousel uttak bare erklært og UIView bare lagt til som et undervisning.

Vi må sette både datakilden og delegere for photoCarousel også, og vi kan oppnå dette fra Interface Builder. Gå først til ViewController.h og erklærer at denne kontrolleren vil overholde de aktuelle protokollene:

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

På linje 2 importerer vi iCarousel, og på linje 4 erklærer vi deretter samsvar med både delegat og datakilde.

Tilbake i storyboard-filen, kan du nå kartlegge både datakilden og delegaten til visningsregulatoren.

Før du fortsetter, gå videre og endre bakgrunnsfargen til iCarousel vis til svart.

Ok, bare en ting til. Vi vil at iCarousel-visningen skal vises under UIToolbar i visningshierarkiet. Du kan gjøre dette visuelt ved å bare dra dem til riktig rekkefølge i Interface Builder:

Legg merke til hvordan iCarousel-visningen nå vises før verktøylinjen.

Lagre arbeidet ditt i Interface Builder.


Trinn 4: Implementer iCarousel-protokollene

iCarousel bruker et mønster som ligner på UITableView ved at en datakilde brukes til å mate innspill til kontrollen og en delegat brukes til å håndtere samhandling med kontrollen.

For vårt prosjekt vil datakilden være enkel NSMutableArray kalt "displayImages". Legg dette til klassen utvidelsen i ViewController.m nå:

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

Deretter ønsker vi å tildele minne for matrisen i klassens utpekte initialiserer. I vårt tilfelle vil visningscontrolleren bli instantiated fra et Storyboard, så den riktige initialiseringen er initWithCoder:. Men hvis klassen skulle bli instansert programmatisk fra en XIB, ville den riktige initialiseringen være initWithNibName: bundle:. For å imøtekomme enten initialiseringsstil, skriver vi vår egen tilpassede initialiserer og ringer den fra begge, slik som:

 - (void) customSetup displayImages = [[NSMutableArray alloc] init];  - (id) initWithNibName: (NSString *) nibNameOrNil bundle: (NSBundle *) nibBundleOrNil hvis ((selv = [super initWithNibName: nibNameOrNil bundle: nibBundleOrNil])) [self customSetup];  returner selv;  - (id) initWithCoder: (NSCoder *) aDecoder if ((selv = [super initWithCoder: aDecoder])) [self customSetup];  returner selv; 

Nå kan vi implementere datakilden og delegere. Start med datakildemetoden numberOfItemsInCarousel:, som så:

 #pragma mark #pragma markere iCarousel DataSource / Delegate / Custom - (NSUInteger) numberOfItemsInCarousel: (iCarousel *) karusell return [displayImages count]; 

Dette vil fortelle iCarousel hvor mange bilder som skal vises ved å se på antall bilder som er lagret i datakildesammenstillingen.

Deretter skriver du metoden som faktisk genererer en visning for hvert bilde som vises i karusellen:

 - (UIView *) karusell: (iCarousel *) karusell viewForItemAtIndex: (NSUInteger) indeks reusingView: (UIView *) visning // Opprett ny visning hvis ingen visning er tilgjengelig for resirkulering hvis (se == nil) view = [[UIImageView alloc] initWithFrame: CGRectMake (0, 0, 300.0f, 300.0f)]; view.contentMode = UIViewContentModeCenter;  ((UIImageView *) visning) .image = [displayImages objectAtIndex: index]; returnere visning; 

Dette er en god start, men det er et svært viktig problem med det ovennevnte: Bildene skal skaleres ned før de leveres til iCarousel. Legg til følgende kodelinjer for å oppdatere metoden:

 - (UIView *) karusell: (iCarousel *) karusell viewForItemAtIndex: (NSUInteger) indeks reusingView: (UIView *) visning // Opprett ny visning hvis ingen visning er tilgjengelig for resirkulering hvis (se == nil) view = [[UIImageView alloc] initWithFrame: CGRectMake (0, 0, 300.0f, 300.0f)]; view.contentMode = UIViewContentModeCenter;  // Intelligent skala ned til maksimalt 250px i bredde eller høyde UIImage * originalImage = [displayImages objectAtIndex: index]; CGSize maxSize = CGSizeMake (250,0f, 250,0f); CGSize targetSize; // Hvis bildet er landskap, sett bredden til 250px og dynamisk regne ut høyden hvis (originalImage.size.width> = originalImage.size.height) float newHeightMultiplier = maxSize.width / originalImage.size.width; targetSize = CGSizeMake (maxSize.width, round (originalImage.size.height * newHeightMultiplier));  // Hvis bildet er portrett, sett høyde til 250px og dynamisk finne ut bredde annet float newWidthMultiplier = maxSize.height / originalImage.size.height; targetSize = CGSizeMake (runde (newWidthMultiplier * originalImage.size.width), maxSize.height);  // Endre størrelsen på kildebildet for å passe pent i iCarousel ((UIImageView *) visning) .image = [[displayImages objectAtIndex: index] resizedImage: targetSize interpolationQuality: kCGInterpolationHigh]; returnere visning; 
Pro Tips: Bruk denne metoden i en produksjonsapp? Vurder å forbedre koden for ytelse ved å gjøre størrelsen på bildet på en bakgrunnstråd og holde en egen NSMutableArray som caches de nedskalerte bildeversjonene. OPPDATERING 9/27/2012: Nick Lockwood (forfatter av iCarousel) har gitt ut et prosjekt som heter FXImageView som automatisk håndterer bildebelastning på en bakgrunnstråd. Det kommer også med andre nyttige bjeller og fløyter som drop shadows og avrundede hjørner, så sjekk det ut!

Ovenfor angir vi en maksimal størrelse på 250px for enten Bredden eller høyden på bildet, og deretter skaler vi motsatt attributten ned for å matche. Dette begrenser forholdet mellom bildet og ser mye bedre ut enn å bare skalere ned til en 250 px med 250 px firkant.

Ovennevnte to metoder er alt iCarousel trenger å begynne å vise bilder.

Med delegatene og datakildemetoder konfigurert, er det nå en god tid å sette opp iCarousel-objektet i viewDidLoad metode også:

 - (void) viewDidLoad [super viewDidLoad]; // iCarousel Configuration self.photoCarousel.type = iCarouselTypeCoverFlow2; self.photoCarousel.bounces = NO; 

Med bare noen få flere tilpasninger, vil prosjektet kunne vise bilder innenfor en karusell!


Trinn 5: Bytt til den nye datomodellen

Tidligere i denne opplæringen, erstattet vi selectedImageView eiendom med photoCarousel eiendom, oppdaterte Storyboard-grensesnittet for å matche, og opprettet en NSMutableArray å fungere som iCarousel-datamodellen. Det er imidlertid noen få metoder i ViewController.m Bruk fortsatt den gamle datamodellen som forhindrer prosjektet i å kompilere, så la oss fikse de nå. Oppdater saveImageToAlbum metode slik:

 - (IBAction) saveImageToAlbum UIImage * selectedImage = [displayImages objectAtIndex: self.photoCarousel.currentItemIndex]; UIImageWriteToSavedPhotosAlbum (selectedImage, self, @selector (image: didFinishSavingWithError: contextInfo :), null); 

Linje 3 velger UIImage fra datamodellen som samsvarer med gjeldende iCarousel-indeksen. Linje 4 utfører den faktiske diskskriften med bildet.

Deretter går du til UIImagePickerController delegere metode og endre koden:

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

På linje 6 over legger vi det valgte bildet til den nye modellen og på linje 8 trenger vi en fornyelse av karusellen.

Bare en ekstra endring å gjøre. Gå til handlingsarkets clickedButtonAtIndex: metode og modifiser koden som følger:

 #pragma mark - #pragma mark UIActionSheetDelegate - (void) actionSheet: (UIActionSheet *) actionSheet klikketButtonAtIndex: (NSInteger) buttonIndex if (buttonIndex == actionSheet.cancelButtonIndex) return;  GPUImageFilter * selectedFilter; bytt (buttonIndex) case 0: selectedFilter = [[GPUImageGrayscaleFilter alloc] init]; gå i stykker; tilfelle 1: selectedFilter = [[GPUImageSepiaFilter alloc] init]; gå i stykker; tilfelle 2: selectedFilter = [[GPUImageSketchFilter alloker] init]; gå i stykker; tilfelle 3: selectedFilter = [[GPUImagePixellateFilter alloc] init]; gå i stykker; tilfelle 4: selectedFilter = [[GPUImageColorInvertFilter alloc] init]; gå i stykker; sak 5: selectedFilter = [[GPUImageToonFilter alloker] init]; gå i stykker; tilfelle 6: selectedFilter = [[GPUImagePinchDistortionFilter alloc] init]; gå i stykker; sak 7: selectedFilter = [[GPUImageFilter alloker] init]; gå i stykker; standard: break;  Ullmage * filteredImage = [selectedFilter imageByFilteringImage: [displayImages objectAtIndex: self.photoCarousel.currentItemIndex]]; [displayImages replaceObjectAtIndex: self.photoCarousel.currentItemIndex withObject: filteredImage]; [self.photoCarousel reloadData]; 

De tre siste linjene i denne metoden vil filtrere datamodellbildet som tilsvarer gjeldende karusellindeks, erstatte karuselldisplayet med bildet og deretter oppdatere karusellen.

Hvis alt gikk bra, bør du nå kunne kompilere og kjøre prosjektet! Hvis du gjør det, kan du se bildene dine i karusellen i stedet for bare innenfor en bildevisning.


Trinn 6: Legg til et gest for å slette bilder

Appen ser bra ut så langt, men det ville være fint hvis brukeren kunne fjerne et bilde fra karusellen etter å ha lagt det til skjermen. Ikke noe problem! Vi kan velge noen UIGestureRecognizer underklasse for å få dette til å skje. For denne opplæringen har jeg valgt å bruke en to-fingers dobbelttrykk. Denne bevegelsen kan ikke være umiddelbart intuitiv, men det er lett å utføre og den ekstra kompleksiteten vil bidra til å forhindre fjerning av bilder ved et uhell.

Innen ViewController.m fil, gå til karusellen: viewForItemAtIndex: reusingView: metode og legg til følgende linjer like før slutten av metoden:

 // Endre størrelsen på kildebildet for å passe pent i iCarousel ((UIImageView *) visning) .image = [[displayImages objectAtIndex: index] resizedImage: targetSize interpolationQuality: kCGInterpolationHigh]; // Tofinger dobbelttrykk vil slette et bilde UITapGestureRecognizer * gest = = [[UITapGestureRecognizer alloc] initWithTarget: selvhandling: @selector (removeImageFromCarousel :)]; gesture.numberOfTouchesRequired = 2; gesture.numberOfTapsRequired = 2; view.gestureRecognizers = [NSArray arrayWithObject: gest]; returnere visning;

Linjer 4-8 erklære en ny UITapGestureRecognizer objekt, angi antall berøringer (dvs. fingre) som kreves for å utløse gesten til 2, og angi antall kraner som kreves til 2 også. Til slutt, før vi sender visningen tilbake til iCarousel-objektet, setter vi inn gestureRecognizers eiendom med den nylig dannede gjenkjenneren.

Merk at når denne utløses, vil denne gjenkjenningsmaskinen brenne väljeren removeImageFromCarousel:. La oss implementere det neste:

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

Linje 3 fjerner bevegelsen fra det nåværende målet for å forhindre at flere bevegelser utløses under behandlingen. De resterende to linjene er ikke noe nytt på dette punktet.

Bygg og kjør appen på nytt. Du bør nå kunne fjerne elementer fra karusellen dynamisk!


Trinn 7: Lag MTCameraViewController

Resten av denne opplæringen vil fokusere på å bruke GPUImageStillCamera å bygge en egendefinert kameravalgstyring som kan bruke filtre til den innkommende videostrømmen i sanntid. GPUImageStillCamera jobber tett med en klasse som kalles GPUImageView. Kamerarammer generert av GPUImageStillCamera blir sendt til en tildelt GPUImageView objekt for visning til brukeren. Alt dette oppnås med den underliggende funksjonaliteten som tilbys av Foundation- rammeverk, som gir programmatisk tilgang til kameramodelldata.

Fordi GPUImageView er et barn klasse av UIView, Vi kan legge inn hele kameravisningen til vår egen brukervennlighet UIViewController klasse.

Legg til en ny UIViewController underklasse til prosjektet ved å høyreklikke "PhotoFX" i prosjektnavigatoren, og velg deretter Ny fil> Mål-C klasse. Navngi klassen "MTCameraViewController" og skriv inn "UIViewController" i for "underklasse" -feltet.

Klikk "Next" og deretter "Create" for å fullføre prosessen.

Gå til MTCameraViewController.m fil og importer GPUImage:

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

Deretter oppretter du en klasseutvidelse med de nødvendige GPUImage-datamedlemmene:

 @interface MTCameraViewController ()  GPUImageStillCamera * stillCamera; GPUImageFilter * filter;  @slutt

Til slutt, gå til viewDidLoad: metode og legg til koden for å starte kameraopptaket:

 - (void) viewDidLoad [super viewDidLoad]; // Oppsett for startfilterfilterfilter = [[GPUImageFilter alloc] init]; [filter prepareForImageCapture]; GPUImageView * filterView = (GPUImageView *) self.view; [filter addTarget: filterView]; // Opprett egendefinert GPUImage-kamera stillCamera = [[GPUImageStillCamera alloc] init]; stillCamera.outputImageOrientation = UIInterfaceOrientationPortrait; [stillCamera addTarget: filter]; // Begynn å vise videokamera stream [stillCamera startCameraCapture]; 

Linjer 5 - 9 oppretter en ny GPUImageView for visning av kameramat og en standard GPUImageFilter eksempel for å bruke en spesiell effekt på visningen. Vi kunne like like brukt en av GPUImageFilter underklasser, for eksempel GPUImageSketchFilter, men vi vil i stedet starte med standardfilteret (det vil si ingen manipulasjoner) og la brukeren dynamisk velge et filter senere.

Linjer 11 - 17 instanserer GPU-kameraeksemplet og bruker filteret som ble opprettet tidligere til kameraet før opptaket startet.


Trinn 8: Legg til det egendefinerte kameraet i IB

Før koden fra trinn 8 vil fungere, må vi legge til tilpasset MTCameraViewController Klassen er nettopp opprettet til prosjektets Storyboard.

Åpne MainStoryboard.storyboard fil og dra ut en ny View Controller fra Objekt-biblioteket. Med dette objektet valgt, gå til fanen Identitetsinspektør og sett feltverdien "Klasse" til "MTCameraViewController".

Deretter drar du a UIToolbar på skjermen og setter stilegenskapen til "Svart Opaque" i Attributtsinspektøren. Deretter legger du til to fleksible strekkknappelementer til verktøylinjen med "Ta bilde" UIBarButtonItem i midten.

For å koble denne visningskontrolleren til programflyten, høyreklikk på "kamera" -knappen fra hovedvisningskontrollen og dra det utløste seguesuttaket til den nye visningskontrollen:

Når du blir bedt om det, velg "Push" som segue-stilen.

Med det nylig tilsatte segueobjektet som er valgt, går du til "Attributtsinspektøren" og angir identifikatoren til "pushMTCamera". Gå videre og sørg for at "Push" er valgt fra rullegardinmenyen "Style".

Med seggen opprettet, sørg for at UIImagePicker vil ikke lenger vises når brukeren tapper kameraknappen på den første appskjermen ved å koble fra IBAction uttak fra photoFromCamera metode.

Til slutt velger du hovedvisningen til den nyopprettede MTCameraViewController. Gå til Identitetsinspektøren og sett klasseverdien til "GPUImageView".

Selv om det ikke er perfekt ennå, hvis du bygger og kjører appen nå, bør du kunne presse MTCameraViewController på visningshierarkiet og se på GPUImageView vis bildene fra kameraet i sanntid!


Trinn 9: Legg til realtidsfiltervalg

Vi kan nå legge til logikken som er nødvendig for å kontrollere filteret som er brukt på kameraskjermen. Først, gå til viewDidLoad: metode innenfor MTCameraViewController.m fil og legg til koden som vil opprette en "Filter" -knapp øverst til høyre i visningsregulatoren:

 - (void) viewDidLoad [super viewDidLoad]; // Legg Filter-knappen til grensesnittet UIBarButtonItem * filterButton = [[UIBarButtonItem alloc] initWithTitle: @ "Filter" -stil: UIBarButtonItemStylePlain mål: self action: @selector (applyImageFilter :)]; self.navigationItem.rightBarButtonItem = filterButton;

På linje 6 ovenfor lager vi en egendefinert UIBarButtonItem som vil utløse applyImageFilter: når valgt.

Opprett nå velgermetoden:

 - (IBAction) applyImageFilter: (id) sender UIActionSheet * filterActionSheet = [[UIActionSheet alloc] initWithTitle: @ "Velg filter" delegere: self cancelButtonTitle: @ "Cancel" destructiveButtonTitle: ingen andreButtonTitles: @ "Gråskala", @ "Sepia" @ "Sketch", @ "Pixellate", @ "Color Invert", @ "Toon", @ "Pinch Distort", @ "None", null); [filterActionSheet showFromBarButtonItem: sender animert: YES]; 

Etter at du har lagt til det ovenfor, vil du se en kompilervarsel som angir at den nåværende visningskontrolleren ikke samsvarer med UIActionSheetDelegate protokoll. Løs problemet ved å gå til MTCameraViewController.h og endre klassedeklarasjonen som sådan:

 #importere  @interface MTCameraViewController: UIViewController  @slutt

Fullfør sirkelen ved å gå tilbake til MTCameraViewController.m fil og legge til logikken som vil svare på UIActionSheet presenteres:

 - (void) actionSheet: (UIActionSheet *) actionSheet klikketButtonAtIndex: (NSInteger) buttonIndex // Kreditt hvis avbryt-knappen ble tappet hvis (actionSheet.cancelButtonIndex == buttonIndex) return;  GPUImageFilter * selectedFilter; [stillCamera removeAllTargets]; [filter removeAllTargets]; bytt (buttonIndex) case 0: selectedFilter = [[GPUImageGrayscaleFilter alloc] init]; gå i stykker; tilfelle 1: selectedFilter = [[GPUImageSepiaFilter alloc] init]; gå i stykker; tilfelle 2: selectedFilter = [[GPUImageSketchFilter alloker] init]; gå i stykker; tilfelle 3: selectedFilter = [[GPUImagePixellateFilter alloc] init]; gå i stykker; tilfelle 4: selectedFilter = [[GPUImageColorInvertFilter alloc] init]; gå i stykker; sak 5: selectedFilter = [[GPUImageToonFilter alloker] init]; gå i stykker; tilfelle 6: selectedFilter = [[GPUImagePinchDistortionFilter alloc] init]; gå i stykker; sak 7: selectedFilter = [[GPUImageFilter alloker] init]; gå i stykker; standard: break;  filter = selectedFilter; GPUImageView * filterView = (GPUImageView *) self.view; [filter addTarget: filterView]; [stillCamera addTarget: filter]; 

Linjer 11-12 brukes til å tilbakestille det valgte filteret.

Linjene 15 - 42 ovenfor bør se kjent med logikken i ViewController.m; vi slår bare på den valgte knappen for å opprette en forekomst av korrelerende filter.

Linjene 44 - 47 tar det nyopprettede filteret og bruker det til GPUImage-kameraet.

Hvis du bygger og kjører prosjektet nå, bør du se at den nyopprettede filterknappen lar brukeren prøve GPUImage-filtre i sanntid!


Trinn 10: Opprett en kameratelegatprotokoll

Nå som vi har livefiltre som fungerer, er det siste store trinnet i opplæringen at brukeren kan ta stillbilder med GPUImage-kameraet og deretter vise dem tilbake i hovedbildekontrollens fotocelle.

For å oppnå dette, vil vi sende meldinger mellom visningskontrollere ved hjelp av delegasjonsdesignmønsteret. Spesielt lager vi vår egen tilpassede formelle delegatprotokoll i MTCameraViewController og konfigurer deretter hovedmenyen ViewController klassen for å overholde denne protokollen for å motta delegasjonsmeldinger.

For å komme i gang, gå til MTViewController.h og endre koden som følger:

 #importere  @protocol MTCameraViewControllerDelegate - (void) didSelectStillImage: (NSData *) bilde medError: (NSError *) feil; @end @interface MTCameraViewController: UIViewController @property (svak, ikkeatomisk) ID-delegat; @slutt

Ovennevnte kode erklærer et formelt delegatmønster som heter MTCameraViewControllerDelegate på linjene 3-7, og oppretter deretter et delegatobjekt på linje 11.

Neste bytt til MTCameraViewController.m og syntetiser delegatets eiendom:

 @implementation MTCameraViewController @synthesize delegate;

Med protokollen erklært, må vi nå implementere den i hovedsak ViewController klasse. Gå til ViewController.h og legg til følgende linjer:

 #importere  #import "iCarousel.h" #import "MTCameraViewController.h" @interface ViewController: UIViewController  @slutt

Åpne nå ViewController.m fil. Vi ønsker å tilordne delegategenskapen når visningscontrolleren er instansiert. Fordi vi bruker Storyboards, er riktig sted å gjøre dette i prepareForSegue: avsender: metode som vil bli kalt like før den nye visningsregulatoren skyves på skjermen:

 - (void) prepareForSegue: (UIStoryboardSegue *) segue sender: (id) avsender if ([segue.identifier isEqualToString: @ "pushMTCamera"]) // Still inn delegatene slik at denne kontrolleren kan motta snakkede bilder MTCameraViewController * cameraViewController = (MTCameraViewController *) segue.destinationViewController; cameraViewController.delegate = self; 

Deretter må vi implementere didSelectStillImage: withError: metode som kreves av MTCameraViewControllerDelegate protokoll:

 #pragma mark - #pragma mark MTCameraViewController // Denne delegerte metoden kalles etter at vår egendefinerte kameraklasse tar et bilde - (void) didSelectStillImage: (NSData *) imageData withError: (NSError *) feil if (! error) UIImage * image = [[UIImage allokering] initWithData: imageData]; [displayImages addObject: image]; [self.photoCarousel reloadData]; self.filterButton.enabled = YES; self.saveButton.enabled = YES;  ellers UIAlertView * alert = [[UIAlertView alloc] initWithTitle: @ "Capture Error" melding: @ "Kan ikke fange bilde." delegere: null avbrytButtonTitle: @ "OK" otherButtonTitles: null]; [varslingsutstilling]; 

Ovennevnte kode vil konvertere NSData objekt levert til metoden til a UIImage og last deretter på nytt bildekarusellen.

Til slutt må vi pakke opp ting ved å returnere til MTCameraViewController.m og legger til i passende delegatemetodeanrop. Først sett opp en IBAction metode som vil utløse et kamera snap:

 GPUImageFilter * filter;  - (IBAction) captureImage: (id) avsender; @slutt

Før du fortsetter, Koble denne metoden til "Ta bilde" -knappen i MainStoryboard.storyboard fil.

Til slutt legg til metoden implementering:

 -(IBAction) captureImage: (id) avsender // Deaktiver for å forhindre flere kraner mens du behandler UIButton * captureButton = (UIButton *) avsender; captureButton.enabled = NO; // Snap Bilde fra GPU kamera, send tilbake til hovedvisning kontroller [stillCamera capturePhotoAsJPEGProcessedUpToFilter: filter withCompletionHandler: ^ (NSData * processedJPEG, NSError * feil) if ([delegate respondsToSelector: @selector (didSelectStillImage: withError :)] self.delegate didSelectStillImage: processedJPEG withError: error];  else NSLog (@ "Delegate reagerte ikke på meldingen");  runOnMainQueueWithoutDeadlocking (^ [self.navigationController popToRootViewControllerAnimated: YES];); ]; 

Linjene 3-5 ovenfor deaktiverer "Ta bilde" -knappen for å forhindre flere presser under behandling.

Linjer 7 - 22 bruker GPUImage-metoden capturePhotoAsJPEGProcessedUpToFilter: withCompletionHandler: For å faktisk lagre et JPEG-bilde, sjekk for å se om en delegat er satt, og send deretter bildedataene til delegaten hvis den er satt.

Linje 19 pops den nåværende visningsregulatoren, men gjør det på hovedprogramtråden.


Wrap Up

Gratulerer! Hvis du har fulgt opplæringen så langt, bør du ha et fullt funksjonelt, avansert fotograferingsprogram! Hvis du har spørsmål eller tilbakemelding, er du velkommen til å legge den inn i kommentarfeltet nedenfor, eller send dem direkte til meg via Twitter (@markhammonds).

Takk for at du leste!