Een foto-app verbeteren met GPUImage & iCarousel

In deze zelfstudie leert u hoe u GPUImage kunt gebruiken om afbeeldingsfilters in realtime toe te passen terwijl de camerafeed van het apparaat wordt weergegeven. Onderweg leer je hoe je automatisch afbeeldingen kunt vullen in een carrouselcontroller en hoe je het formaat van afbeeldingen kunt wijzigen met UIImage + Categories.


Projectoverzicht


Tutorial Vereisten

Deze tutorial bouwt voort op een vorig bericht getiteld "Build a Photo App with GPUImage". De vorige les liet zien hoe te gebruiken UIImagePickerController om foto's uit het fotoalbum of de camera van het apparaat te selecteren, hoe u de GPUImage bibliotheek voor uw project en het gebruik van de GPUImageFilter klasse om fotocamera-frames te verbeteren. Als je al bekend bent met UIImagePickerController en kan uitzoeken hoe toe te voegen GPUImage in je eigen project, zou je moeten kunnen oppikken van waar de laatste tutorial goed is gebleven.


Stap 1: importeer iCarousel

Dit project zal uitgebreid gebruik maken van een open-sourceproject genaamd iCarousel om een ​​stijlvolle weergave van geselecteerde foto's toe te voegen.

Om iCarousel in uw project op te nemen, gaat u naar de officiële GitHub-pagina en downloadt u de broncode als een zipbestand. Pak de code uit het ZIP-bestand uit en sleep de map met de naam "iCarousel" naar de Xcode Project Navigator. Deze map moet beide bevatten iCarousel.h en iCarousel.m. Zorg ervoor dat u "Groepen maken voor toegevoegde mappen" selecteert en vink het vakje aan naast "Items naar de doelmap kopiëren (indien nodig)" en het vakje naast de doelnaam van uw project in het gebied "Toevoegen aan doelen".

Ga vervolgens naar ViewController.m en voeg een import statement toe voor iCarousel:

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

Stap 2: UIImage + categorieën importeren

Voordat we onze afbeeldingen met iCarousel weergeven, moeten we ze verkleinen tot een acceptabele grootte. In plaats van alle code te schrijven om dit met de hand te doen, maken we gebruik van het uitstekende UIImage + Categories-project, dat basisfunctionaliteit biedt voor het wijzigen van het formaat van afbeeldingen, evenals een paar andere trucs voor beeldmanipulatie.

Tip: U kunt ook het MGImageUtilities-project voor deze taak gebruiken. Hoewel de implementatiedetails enigszins verschillen, biedt het ook uitstekende ondersteuning voor UIImage-schaling.

Download de UIImage + Categorieën code van GitHub en maak dan een nieuwe groep met dezelfde naam binnen Xcode. Sleep zowel de implementatie- als de headerbestanden naar UIImage + Alpha, UIImage + Resize, en UIImage + RoundedCorner in uw project. Zorg ervoor dat u "Groepen maken voor toegevoegde mappen" selecteert en vink het vakje aan naast "Items naar de doelmap kopiëren (indien nodig)" en het vakje naast de doelnaam van uw project in het gebied "Toevoegen aan doelen".

Binnen de ViewController.m bestand, importeer de afbeeldingscategorieën met de volgende coderegel:

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

Stap 3: Voeg de iCarousel View toe aan IB

Met de iCarousel-code die in ons project is geïmporteerd, schakelt u over naar de MainStoryboard.storyboard bestand om onze interface opnieuw te bewerken.

Selecteer eerst de stroom UIImageView verbonden met de selectedImageView IBOutlet en verwijder het. Schakel terug naar ViewController.m en wijzig de projectcode als volgt:

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

Vervang op lijn 1 hierboven de selectedImageView stopcontact met een iCarousel stopcontact genoemd photoCarousel. Wissel ook de variabelen in de syntheseverklaring uit op regel 14 hierboven.

Ga terug naar Interface Builder en sleep een nieuw UIView op de weergavecontroller. Met het nieuwe UIView geselecteerd, gaat u naar het tabblad "Identiteitscontrole" in het deelvenster Functies en stelt u de waarde voor het veld "Klasse" in op "iCarousel". Dit vertelt Interface Builder dat het UIView we toegevoegd aan het project moeten worden geïnstantieerd als een instantie van de iCarousel klasse.

Maak nu een verbinding tussen de photoCarousel outlet zojuist aangegeven en de UIView gewoon toegevoegd als een subweergave.

We moeten zowel de gegevensbron als de gedelegeerde instellen photoCarousel ook, en we kunnen dit bereiken vanuit Interface Builder. Ga eerst naar ViewController.h en verklaren dat deze view controller voldoet aan de juiste protocollen:

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

Op regel 2 importeren we iCarousel en op regel 4 verklaren we conformiteit aan zowel de gedelegeerde als de gegevensbron.

Terug in het storyboard-bestand kunt u nu zowel de gegevensbron als de gedelegeerde toewijzen aan de view-controller.

Alvorens verder te gaan, ga je gang en verander je de achtergrondkleur van de iCarousel zicht op zwart.

Oké, nog een ding. We willen dat de iCarousel-weergave onder de. Verschijnt UIToolbar in de weergavehiërarchie. U kunt dit visueel doen door ze eenvoudig naar de juiste volgorde te slepen in Interface Builder:

Merk op hoe de iCarousel-weergave nu voor de werkbalk wordt weergegeven.

Sla uw werk op in Interface Builder.


Stap 4: Implementeer de iCarousel-protocollen

iCarousel gebruikt een ontwerppatroon dat lijkt op UITableView doordat een gegevensbron wordt gebruikt om invoer in de besturing te voeden en een deelnemer wordt gebruikt om de interactie met de besturing af te handelen.

Voor ons project zal de gegevensbron eenvoudig zijn NSMutableArray genaamd "displayImages". Voeg dit toe aan de klas-extensie in ViewController.m nu:

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

Vervolgens willen we geheugen toewijzen voor de array in de klasse 'designated initializer'. In ons geval wordt de view-controller vanaf een storyboard geïnstantieerd, dus de juiste initializer is initWithCoder:. Als de klasse echter programmatisch zou worden geïnstantieerd vanuit een XIB, zou de juiste initializer zijn initWithNibName: bundel:. Om tegemoet te komen aan beide initialisatiestijl, schrijven we onze eigen aangepaste initialisatieprogramma en bellen we deze van beide, zoals:

 - (void) customSetup displayImages = [[NSMutableArray alloc] init];  - (id) initWithNibName: (NSString *) nibNameOrNil-bundel: (NSBundle *) nibBundleOrNil if ((self = [super initWithBibnaam: nibNameOrNil-bundel: nibBundleOrNil])) [self customSetup];  terugkeer zelf;  - (id) initWithCoder: (NSCoder *) aDecoder if ((self = [super initWithCoder: aDecoder])) [self customSetup];  terugkeer zelf; 

Nu kunnen we de gegevensbron en delegatie implementeren. Begin met de gegevensbronmethode numberOfItemsInCarousel:, zoals zo:

 #pragma mark #pragma mark iCarousel DataSource / Delegate / Custom - (NSUInteger) numberOfItemsInCarousel: (iCarousel *) carrousel return [aantal vertoningsafbeeldingen]; 

Dit zal iCarousel vertellen hoeveel afbeeldingen moeten worden weergegeven door te kijken naar het aantal afbeeldingen dat is opgeslagen in de gegevensbronarray.

Schrijf vervolgens de methode die daadwerkelijk een weergave genereert voor elk beeld dat in de carrousel wordt weergegeven:

 - (UIView *) carrousel: (iCarousel *) carrousel viewForItemAtIndex: (NSUInteger) index hergebruikenView: (UIView *) weergave // Maak een nieuwe weergave als er geen weergave beschikbaar is voor recycling if (view == nihil) view = [[UIImageView toewijzen] initWithFrame: CGRectMake (0, 0, 300.0f, 300.0f)]; view.contentMode = UIViewContentModeCenter;  ((UIImageView *) weergave) .image = [displayImages objectAtIndex: index]; terugkeer bekijken; 

Dit is een goed begin, maar er is één zeer belangrijk probleem met het bovenstaande: de afbeeldingen moeten worden verkleind voordat ze aan iCarousel worden geleverd. Voeg de volgende coderegels toe om de methode te updaten:

 - (UIView *) carrousel: (iCarousel *) carrousel viewForItemAtIndex: (NSUInteger) index hergebruikenView: (UIView *) weergave // Maak een nieuwe weergave als er geen weergave beschikbaar is voor recycling if (view == nihil) view = [[UIImageView toewijzen] initWithFrame: CGRectMake (0, 0, 300.0f, 300.0f)]; view.contentMode = UIViewContentModeCenter;  // Intelligent schalen naar een maximum van 250px in breedte of hoogte UIImage * originalImage = [displayImages objectAtIndex: index]; CGSize maxSize = CGSizeMake (250.0f, 250.0f); CGSize targetSize; // Als het beeld liggend is, stel dan de breedte in op 250px en bepaal dynamisch de hoogte als (originalImage.size.width> = originalImage.size.height) float newHeightMultiplier = maxSize.width / originalImage.size.width; targetSize = CGSizeMake (maxSize.width, round (originalImage.size.height * newHeight Multiplier));  // Als het beeld een portret is, stel dan de hoogte in op 250px en bepaal dynamisch de breedte else float newWidthMultiplier = maxSize.height / originalImage.size.height; targetSize = CGSizeMake (round (newWidthMultiplier * originalImage.size.width), maxSize.height);  // Pas het formaat van de bronafbeelding aan zodat deze mooi past in de iCarousel-weergave ((UIImageView *)) .image = [[displayImages objectAtIndex: index] resizedImage: targetSize interpolationQuality: kCGInterpolationHigh]; terugkeer bekijken; 
Pro-tip: Gebruik je deze methode in een productie-app? Overweeg de code voor prestaties te verbeteren door het formaat van afbeeldingen te wijzigen op een achtergrondthread en een afzonderlijke NSMutableArray te behouden die de verkleinde afbeeldingsversies in de cache opslaat. UPDATE 9/27/2012: Nick Lockwood (auteur van iCarousel) heeft een project vrijgegeven genaamd FXImageView dat het laden van afbeeldingen automatisch verwerkt op een achtergrondthread. Het komt ook met andere handige toeters en bellen, zoals slagschaduwen en afgeronde hoeken, dus probeer het eens!

Hierboven hebben we een maximale grootte van 250 px ingesteld voor een van beide de breedte of hoogte van het beeld, en dan schalen we het tegenovergestelde attribuut naar beneden om te matchen. Dit beperkt de verhoudingen van het beeld en ziet er veel leuker uit dan eenvoudigweg verkleinen tot een vierkant van 250px bij 250px.

De bovenstaande twee methoden zijn allemaal iCarousel moet beginnen met het weergeven van afbeeldingen.

Met de gedelegeerde en gegevensbronmethoden geconfigureerd, is het nu een goed moment om het iCarousel-object in te stellen in de viewDidLoad methode ook:

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

Met slechts een paar aanpassingen kan het project afbeeldingen weergeven in een carrousel!


Stap 5: Schakel over naar het nieuwe datamodel

Eerder in deze tutorial hebben we de. Vervangen selectedImageView eigendom met de photoCarousel property, heeft de Storyboard-interface bijgewerkt om overeen te komen en een NSMutableArray om op te treden als het iCarousel-gegevensmodel. Er zijn echter een paar methoden in ViewController.m nog steeds met behulp van het oude datamodel dat zal voorkomen dat het project compileert, dus laten we dat nu oplossen. Werk het saveImageToAlbum methode zoals zo:

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

Regel 3 selecteert de UIImage van het gegevensmodel dat overeenkomt met de huidige iCarousel-index. Regel 4 voert de eigenlijke schrijf van de schijf uit met die afbeelding.

Ga vervolgens naar de UIImagePickerController delegeer methode en pas de code aan:

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

Op regel 6 hierboven voegen we de geselecteerde foto toe aan het nieuwe model en op regel 8 forceren we een vernieuwing van de carrousel.

Nog een verandering om te maken. Ga naar de actielijst's clickedButtonAtIndex: methode en wijzig de code als volgt:

 #pragma mark - #pragma mark UIActionSheetDelegate - (void) actionSheet: (UIActionSheet *) actionSheet clickedButtonAtIndex: (NSInteger) buttonIndex if (buttonIndex == actionSheet.cancelButtonIndex) return;  GPUImageFilter * selectedFilter; switch (buttonIndex) case 0: selectedFilter = [[GPUImageGrayscaleFilter alloc] init]; breken; case 1: selectedFilter = [[GPUImageSepiaFilter alloc] init]; breken; geval 2: selectedFilter = [[GPUImageSketchFilter alloc] init]; breken; case 3: selectedFilter = [[GPUImagePixellateFilter alloc] init]; breken; case 4: selectedFilter = [[GPUImageColorInvertFilter alloc] init]; breken; case 5: selectedFilter = [[GPUImageToonFilter alloc] init]; breken; case 6: selectedFilter = [[GPUImagePinchDistortionFilter alloc] init]; breken; case 7: selectedFilter = [[GPUImageFilter alloc] init]; breken; standaard: pauze;  UIImage * filteredImage = [selectedFilter imageByFilteringImage: [displayImages objectAtIndex: self.photoCarousel.currentItemIndex]]; [displayImages replaceObjectAtIndex: self.photoCarousel.currentItemIndex withObject: filteredImage]; [self.photoCarousel reloadData]; 

De laatste drie regels van deze methode filteren het datamodelbeeld dat overeenkomt met de huidige carrouselindex, vervangen het carrouselbeeld met dat beeld en verversen daarna de carrousel.

Als alles goed is gegaan, zou je nu in staat moeten zijn om het project te compileren en uit te voeren! Als u dit doet, kunt u uw afbeeldingen in de carrousel bekijken in plaats van alleen in een afbeeldingsweergave.


Stap 6: een gebaar toevoegen voor het verwijderen van afbeeldingen

De app ziet er tot nu toe goed uit, maar het zou fijn zijn als de gebruiker een foto uit de carrousel kon verwijderen nadat hij deze aan het scherm had toegevoegd. Geen probleem! We kunnen elk selecteren UIGestureRecognizer subklasse om dit mogelijk te maken. Voor deze zelfstudie heb ik ervoor gekozen om tweemaal met twee vingers te tikken. Dit gebaar is misschien niet meteen intuïtief, maar het is eenvoudig uit te voeren en de extra complexiteit helpt voorkomen dat afbeeldingen per ongeluk worden verwijderd.

Binnen de ViewController.m bestand, ga naar de carrousel: viewForItemAtIndex: reusingView: methode en voeg de volgende regels toe net voor het einde van de methode:

 // Pas het formaat van de bronafbeelding aan zodat deze mooi past in iCarousel (weergave (UIImageView *)) .image = [[displayImages objectAtIndex: index] resizedImage: targetSize interpolationQuality: kCGInterpolationHigh]; // Dubbelklik twee keer om een ​​afbeelding te verwijderen UITapGestureRecognizer * gesture = [[UITapGestureRecognizer alloc] initWithTarget: self action: @selector (removeImageFromCarousel :)]; gesture.numberOfTouchesRequired = 2; gesture.numberOfTapsRequired = 2; view.gestureRecognizers = [NSArray arrayWithObject: gesture]; terugkeer bekijken;

Regel 4 - 8 verklaart een nieuwe UITapGestureRecognizer object, stel het aantal aanrakingen (dat wil zeggen vingers) in dat nodig is om de beweging naar 2 te activeren en stel ook het aantal benodigde tikken in op 2. Eindelijk, vlak voordat we het uitzicht weer terugsturen naar het iCarousel-object, zetten we de gestureRecognizers eigendom met de nieuw gevormde herkenner.

Merk op dat wanneer deze wordt geactiveerd, deze gebaarherkenner de selector zal activeren removeImageFromCarousel:. Laten we het volgende implementeren:

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

Lijn 3 verwijdert de beweging van het huidige doel om te voorkomen dat meerdere bewegingen worden geactiveerd tijdens de verwerking. De resterende twee regels zijn op dit moment niet nieuw.

Bouw de app opnieuw en voer deze uit. Je zou nu in staat moeten zijn om items dynamisch uit de carrousel te verwijderen!


Stap 7: Maak MTCameraViewController

De rest van deze tutorial zal zich concentreren op het gebruik GPUImageStillCamera om een ​​aangepast camerakiezerbesturingselement te maken dat in realtime filters op de inkomende videostream kan toepassen. GPUImageStillCamera werkt nauw samen met een klasse genaamd GPUImageView. Cameraframes gegenereerd door GPUImageStillCamera worden verzonden naar een toegewezen GPUImageView object voor weergave aan de gebruiker. Dit alles wordt bereikt met de onderliggende functionaliteit geboden door de AVFoundation framework, dat programmatische toegang tot cameraframegegevens biedt.

Omdat GPUImageView is een kindklasse van UIView, we kunnen de volledige camera-weergave in onze eigen stijl integreren UIViewController klasse.

Voeg een nieuwe toe UIViewController subklasse aan het project door met de rechtermuisknop op "PhotoFX" in de projectnavigator te klikken en vervolgens te selecteren Nieuw bestand> Objective-C-klasse. Noem de klasse "MTCameraViewController" en voer "UIViewController" in voor het veld "subklasse van".

Klik op "Volgende" en vervolgens op "Maken" om het proces te voltooien.

Ga naar de MTCameraViewController.m bestand en importeer GPUImage:

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

Maak vervolgens een klasse-extensie met de nodige GPUImage-gegevensleden:

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

Ga tenslotte naar de viewDidLoad: methode en voeg de code toe om de camera-opname te starten:

 - (void) viewDidLoad [super viewDidLoad]; // Stel het eerste filterfilter van de camera in = [[GPUImageFilter alloc] init]; [filter prepareForImageCapture]; GPUImageView * filterView = (GPUImageView *) self.view; [filter addTarget: filterView]; // Maak aangepaste GPUImage-camera stillCamera = [[GPUImageStillCamera-toewijzing] init]; stillCamera.outputImageOrientation = UIInterfaceOrientationPortrait; [stillCamera addTarget: filter]; // Begin met het weergeven van de videocamerastream [stillCamera startCameraCapture]; 

Regel 5 - 9 maak een nieuw GPUImageView voor het weergeven van de camerafeed en een standaardinstelling GPUImageFilter bijvoorbeeld voor het toepassen van een speciaal effect op de weergave. We hadden net zo gemakkelijk een van de GPUImageFilter subklassen, zoals GPUImageSketchFilter, maar we zullen in plaats daarvan beginnen met het standaardfilter (d.w.z. geen manipulaties) en de gebruiker later dynamisch een filter laten selecteren.

Regel 11 - 17 instantiëren de GPU-camera-instantie en passen het eerder gemaakte filter toe op de camera voordat de opname wordt gestart.


Stap 8: voeg de aangepaste camera toe aan IB

Voordat de code uit stap 8 werkt, moeten we de aangepaste toevoegen MTCameraViewController klas zojuist gemaakt naar het Storyboard van het project.

Open de MainStoryboard.storyboard bestand en sleep een nieuwe View Controller uit de Objectbibliotheek. Als dit object is geselecteerd, gaat u naar het tabblad Identiteitscontrole en stelt u de veldwaarde "Klasse" in op "MTCameraViewController".

Sleep vervolgens een UIToolbar op het scherm en stel de stijleigenschap in op "Black Opaque" in het Attributes-inspector. Voeg vervolgens twee items in de balkknop met de flexibele breedte toe aan de werkbalk met een "Foto maken" UIBarButtonItem in het midden.

Om deze weergavecontroller aan te sluiten op de toepassingsstroom, klikt u met de rechtermuisknop op de knop "camera" in de hoofdweergavecontroller en sleept u de geactiveerde segmentenuitgang naar de nieuwe weergavecontroller:

Selecteer "Push" als u hierom wordt gevraagd als de seguestijl.

Met het nieuw toegevoegde segue-object nog steeds geselecteerd, ga naar "Attributes inspector" en stel de identifier in op "pushMTCamera". Ga je gang en zorg ervoor dat "Push" is geselecteerd in de vervolgkeuzelijst "Stijl".

Zorg er met de gemaakte segue voor dat de UIImagePicker wordt niet langer weergegeven wanneer de gebruiker op de cameraknop tikt op het eerste app-scherm door de verbinding te verbreken IBAction stopcontact van de photoFromCamera methode.

Selecteer ten slotte de primaire weergave van de nieuw gemaakte MTCameraViewController. Ga naar de Identiteitscontrole en stel de klassenwaarde in op "GPUImageView".

Hoewel het nog niet perfect is, zou je kunnen pushen als je de app nu bouwt en uitvoert MTCameraViewController op de weergavehiërarchie en kijken GPUImageView toon de beelden van de camera in realtime!


Stap 9: Realtime filterselectie toevoegen

We kunnen nu de logica toevoegen die nodig is om het filter te besturen dat op het camerascherm is toegepast. Ga eerst naar de viewDidLoad: methode binnen de MTCameraViewController.m bestand en voeg de code toe die een knop 'Filter' in de rechterbovenhoek van de view controller maakt:

 - (void) viewDidLoad [super viewDidLoad]; // Filter-knop toevoegen aan interface UIBarButtonItem * filterButton = [[UIBarButtonItem alloc] initWithTitle: @ "Filter" -stijl: UIBarButtonItemStylePlain-doel: zelfactie: @selector (applyImageFilter :)]; self.navigationItem.rightBarButtonItem = filterButton;

Op regel 6 hierboven maken we een aangepast UIBarButtonItem dat zal activeren applyImageFilter: wanneer geselecteerd.

Maak nu de selector-methode:

 - (IBAction) applyImageFilter: (id) afzender UIActionSheet * filterActionSheet = [[UIActionSheet alloc] initWithTitle: @ "Selecteer Filter" delegate: self cancelButtonTitle: @ "Cancel" destructiveButtonTitle: nil otherButtonTitles: @ "Grayscale", @ "Sepia", @ "Sketch", @ "Pixellate", @ "Color Invert", @ "Toon", @ "Pinch Distort", @ "None", nihil]; [filterActionSheet showFromBarButtonItem: afzender geanimeerd: YES]; 

Nadat u het bovenstaande hebt toegevoegd, ziet u een compilerwaarschuwing waarin staat dat de huidige view controller niet voldoet aan de UIActionSheetDelegate protocol. Los dit probleem nu op door naar MTCameraViewController.h en de klasseverklaring als volgt te wijzigen:

 #importeren  @interface MTCameraViewController: UIViewController  @einde

Voltooi de cirkel door terug te gaan naar de MTCameraViewController.m bestand en het toevoegen van de logica die zal reageren op de UIActionSheet gepresenteerd:

 - (ongeldig) actionSheet: (UIActionSheet *) actionSheet clickedButtonAtIndex: (NSInteger) buttonIndex // Bail als de cancel-knop werd aangeraakt als (actionSheet.cancelButtonIndex == buttonIndex) return;  GPUImageFilter * selectedFilter; [stillCamera removeAllTargets]; [filter removeAllTargets]; switch (buttonIndex) case 0: selectedFilter = [[GPUImageGrayscaleFilter alloc] init]; breken; case 1: selectedFilter = [[GPUImageSepiaFilter alloc] init]; breken; geval 2: selectedFilter = [[GPUImageSketchFilter alloc] init]; breken; case 3: selectedFilter = [[GPUImagePixellateFilter alloc] init]; breken; case 4: selectedFilter = [[GPUImageColorInvertFilter alloc] init]; breken; case 5: selectedFilter = [[GPUImageToonFilter alloc] init]; breken; case 6: selectedFilter = [[GPUImagePinchDistortionFilter alloc] init]; breken; case 7: selectedFilter = [[GPUImageFilter alloc] init]; breken; standaard: pauze;  filter = selectedFilter; GPUImageView * filterView = (GPUImageView *) self.view; [filter addTarget: filterView]; [stillCamera addTarget: filter]; 

Regels 11-12 worden gebruikt om het momenteel geselecteerde filter opnieuw in te stellen.

De regels 15 - 42 hierboven moeten bekend voorkomen in de logica ViewController.m; we schakelen gewoon de geselecteerde knop in om een ​​exemplaar van het correlatiefilter te maken.

Regel 44 - 47 nemen het nieuw gemaakte filter en passen dit toe op de GPUImage-camera.

Als u nu het project bouwt en uitvoert, moet u zien dat de nieuw gemaakte filterknop de gebruiker in staat stelt GPUImage-filters in realtime uit te proberen!


Stap 10: Maak een Camera Delegate Protocol

Nu de live-feedfilters werken, is de laatste grote stap in de zelfstudie dat de gebruiker snapshots kan maken met de GPUImage-camera en deze vervolgens weer kan weergeven in de fotocarrousel van de hoofdviewcontroller.

Om dit te bereiken, zullen we berichten doorgeven tussen view controllers met behulp van het designpatroon van de delegatie. In het bijzonder maken we ons eigen aangepaste formele deelnemersprotocol in MTCameraViewController en configureer dan de main ViewController klasse om aan dat protocol te voldoen om delegatieberichten te ontvangen.

Ga naar om te beginnen MTViewController.h en verander de code als volgt:

 #importeren  @protocol MTCameraViewControllerDelegate - (void) didSelectStillImage: (NSData *) image withError: (NSError *) fout; @end @interface MTCameraViewController: UIViewController @property (weak, nonatomic) id delegate; @einde

De bovenstaande code verklaart een formeel gedelegeerd patroon genaamd MTCameraViewControllerDelegate op regels 3-7 en maakt vervolgens een machtigingsobject op regel 11.

Ga vervolgens naar MTCameraViewController.m en de eigenschap van de gedelegeerde samenstellen:

 @implementation MTCameraViewController @ delegate delegeren;

Nu het protocol is gedeclareerd, moeten we het nu voornamelijk implementeren ViewController klasse. Ga naar ViewController.h en voeg de volgende regels toe:

 #importeren  #import "iCarousel.h" #import "MTCameraViewController.h" @interface ViewController: UIViewController  @einde

Open nu de ViewController.m het dossier. We willen de gedelegeerde eigenschap toewijzen wanneer de view-controller wordt geïnstantieerd. Omdat we Storyboards gebruiken, is de juiste plaats om dit te doen in de prepareForSegue: afzender: methode, die wordt aangeroepen net voordat de nieuwe view-controller op het scherm wordt gedrukt:

 - (void) prepareForSegue: (UIStoryboardSegue *) segue afzender: (id) afzender if ([segue.identifier isEqualToString: @ "pushMTCamera"]) // Stel de afgevaardigde in zodat deze controller vastgelegde foto's kan ontvangen MTCameraViewController * cameraViewController = (MTCameraViewController *) segue.destinationViewController; cameraViewController.delegate = zelf; 

Vervolgens moeten we het didSelectStillImage: withError: methode vereist door de MTCameraViewControllerDelegate protocol:

 #pragma mark - #pragma mark MTCameraViewController // Deze deelnemersmethode wordt aangeroepen nadat onze aangepaste cameraklasse een foto maakt - (void) didSelectStillImage: (NSData *) imageData withError: (NSError *) error if (! error) UIImage * image = [[UIImage alloc] initWithData: imageData]; [displayImages addObject: image]; [self.photoCarousel reloadData]; self.filterButton.enabled = YES; self.saveButton.enabled = YES;  else UIAlertView * alert = [[UIAlertView alloc] initWithTitle: @ "Capture Error" -bericht: @ "Kan foto niet maken." delegate: nil cancelButtonTitle: @ "OK" otherButtonTitles: nil]; [alarm show]; 

De bovenstaande code converteert de NSData object overhandigd aan de methode om een UIImage en laad vervolgens de fotocarrousel opnieuw.

Ten slotte moeten we de zaken afronden door terug te keren naar MTCameraViewController.m en het toevoegen van de juiste gedelegeerde methodeaanroep. Stel eerst een IBAction methode die een camerabestand activeert:

 GPUImageFilter * filter;  - (IBAction) captureImage: (id) afzender; @einde

Voordat u doorgaat, verbind deze methode met de "Foto nemen" knop in de MainStoryboard.storyboard het dossier.

Voeg ten slotte de methode-implementatie toe:

 -(IBAction) captureImage: (id) afzender // Uitschakelen om meerdere tikken te voorkomen tijdens het verwerken van UIButton * captureButton = (UIButton *) afzender; captureButton.enabled = NO; // Maak een afbeelding van de GPU-camera, stuur deze terug naar de hoofdweergavecontroller [stillCamera capturePhotoAsJPEGProcessedUpToFilter: filter withCompletionHandler: ^ (NSData * processedJPEG, NSError * error) if ([respondent reageertToSelector: @selector (didSelectStillImage: withError :)]) [ self.delegate didSelectStillImage: processedJPEG withError: error];  else NSLog (@ "Delegate reageerde niet op bericht");  runOnMainQueueWithoutDeadlocking (^ [self.navigationController popToRootViewControllerAnimated: YES];); ]; 

Lijnen 3-5 hierboven schakelen de knop 'Foto maken' uit om te voorkomen dat er meerdere keren wordt ingedrukt tijdens de verwerking.

Regel 7 - 22 gebruikt de GPUImage-methode capturePhotoAsJPEGProcessedUpToFilter: withCompletionHandler: om een ​​JPEG-afbeelding daadwerkelijk op te slaan, controleer om te zien of een gedelegeerde is ingesteld en verzend de afbeeldingsgegevens vervolgens naar de afgevaardigde als deze is ingesteld.

Regel 19 geeft de huidige view-controller weer, maar doet dit o