Skip to content

Instantly share code, notes, and snippets.

@keicoder
Created February 27, 2014 03:20
Show Gist options
  • Save keicoder/9243751 to your computer and use it in GitHub Desktop.
Save keicoder/9243751 to your computer and use it in GitHub Desktop.
objective-c : Image to NSData and Reverse Transform for saving Core Data
//Image to NSData and Reverse Transform for saving Core Data
//1. CoreData 모델 생성
//ex) Entities name : Photo / Attributes : date (type Date), image (type Transformable)
//2. create NSManagedObject Subclass (in this case : TWPictureDataTransformer class)
//3. TWPictureDataTransformer.h
@interface TWPictureDataTransformer : NSValueTransformer
@end
//4. TWPictureDataTransformer.m
// 이미지를 코어데이터에 저장하기 위한 클래스
// Image to NSData and Reverse Transform for saving Core Data
#import "TWPictureDataTransformer.h"
@implementation TWPictureDataTransformer
+ (Class)transformedValueClass
{
return [NSData class]; //transform UIImage object into NSData
}
+ (BOOL)allowsReverseTransformation
{
return YES; //allows NSData object into UIImage
}
- (id)transformedValue:(id)value
{
return UIImagePNGRepresentation(value); //returns the data for the specified image in PNG format
}
- (id)reverseTransformedValue:(id)value
{
UIImage *image = [UIImage imageWithData:value];
return image; //returns the image object
}
@end
//5. Creating and Storing Photos
//TWPhotosCollectionViewController.h
#import "Album.h" //앨범 클래스
@interface TWPhotosCollectionViewController : UICollectionViewController
@property (strong, nonatomic) Album *album;
- (IBAction)cameraBarButtonItemPressed:(UIBarButtonItem *)sender;
@end
//TWPhotosCollectionViewController.m
#import "Photo.h" //데이터 모델
#import "TWPictureDataTransformer.h" //Image to NSData and Reverse Transform for saving Core Data
#import "TWCoreDataHelper.h" //Helper Class which returns an NSManagesObjectContext object from our App Delegate
#pragma mark - Helper Methods (persist photo to core data)
/* Instance method which accepts a UIImage and persists it to Core Data. */
- (Photo *)photoFromImage:(UIImage *)image
{
/* Create a photo object using the method insertNewObjectForEntityForName for the entity name Photo */
Photo *photo = [NSEntityDescription insertNewObjectForEntityForName:@"Photo" inManagedObjectContext:[TWCoreDataHelper managedObjectContext]];
/* Set the photo's attributes */
photo.image = image;
photo.date = [NSDate date];
photo.albumBook = self.album;
NSError *error = nil;
/* Save the photo, the if statement evaluates to true if there is an error */
if (![[photo managedObjectContext] save:&error]){
//Error in saving
NSLog(@"%@", error);
}
return photo;
}
//6. Implementing Creating and Storing the Photos
#pragma mark - UIImagePickerController 델리게이트
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
if (debug==1) {NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));}
NSLog (@"Finish image picking");
UIImage *image= info[UIImagePickerControllerEditedImage];
if (!image) image = info[UIImagePickerControllerOriginalImage];
[self.photos addObject:[self photoFromImage:image]]; //photoFromImage: for persisting photo to core data
[self.collectionView reloadData];
//이미지 피커 뷰 해제
[self dismissViewControllerAnimated:YES completion:^{
NSLog(@"Finished image picking");
}];
}
#pragma mark - UICollectionView 데이터 소스
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
if (debug==1) {NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));}
static NSString *CellIdentifier = @"Photo Cell";
//커스텀 컬렉션 뷰 셀 사용
TWPhotoCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier
forIndexPath:indexPath];
Photo *photo = self.photos[indexPath.row];
cell.backgroundColor = [UIColor whiteColor];
cell.imageView.image = photo.image;
return cell;
}
//7. prepareForSegue
//TWAlbumTableViewController.m
#import "TWPhotosCollectionViewController.h" //이미지를 자져오기 위해 import함
#pragma mark - Navigation (prepareForSegue)
// In a story board-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:@"Album Chosen"]) {
if ([segue.destinationViewController isKindOfClass:[TWPhotosCollectionViewController class]]) {
NSIndexPath *path = [self.tableView indexPathForSelectedRow];
TWPhotosCollectionViewController *targetViewController = segue.destinationViewController;
targetViewController.album = self.albums[path.row];
}
}
}
//8. Querying the Photos
//TWPhotosCollectionViewController.m
- (void)viewDidLoad
{
if (debug==1) {NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));}
[super viewDidLoad];
//Querying the Photos
NSSet *unorderedPhotos = self.album.photos; //unordered collection
NSSortDescriptor *dateDescripor = [NSSortDescriptor sortDescriptorWithKey:@"date" ascending:YES];
NSArray *sortedPhotos = [unorderedPhotos sortedArrayUsingDescriptors:@[dateDescripor]];
self.photos = [sortedPhotos mutableCopy];
}
//9. Deleting a Photo from Core Data
//TWPhotoDetailViewController.h
//포토 디테일 뷰 컨트롤러
@class Photo; //photo 데이터 클래스
@interface TWPhotoDetailViewController : UIViewController
@property (strong, nonatomic) Photo *photo; //photo 데이터 클래스
@property (strong, nonatomic) IBOutlet UIImageView *imageView;
- (IBAction)addFilterButtonPressed:(UIButton *)sender;
- (IBAction)deleteButtonPressed:(UIButton *)sender;
@end
//TWPhotoDetailViewController.m
// 포토 디테일 뷰 컨트롤러
#import "TWPhotoDetailViewController.h"
#import "Photo.h" //photo 데이터 클래스
@interface TWPhotoDetailViewController ()
@end
@implementation TWPhotoDetailViewController
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.imageView.image = self.photo.image;
}
- (IBAction)addFilterButtonPressed:(UIButton *)sender {
}
- (IBAction)deleteButtonPressed:(UIButton *)sender
{
if (debug==1) {NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));}
[[self.photo managedObjectContext] deleteObject:self.photo];
[self.navigationController popViewControllerAnimated:YES]; //push segue, pop from TWPhotoDetailViewController to TWPhotosCollectionViewController
}
@end
//TWPhotosCollectionViewController.m
#import "TWPhotoDetailViewController.h" //포토 디테일 뷰 컨트롤러
#pragma mark - Navigation (prepareForSegue)
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sende
{
if (debug==1) {NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));}
/* Confirm that the correct segue is being triggered */
if ([segue.identifier isEqualToString:@"Detail Segue"])
{
/* Confirm that the correct View Controller is being transitioned to */
if ([segue.destinationViewController isKindOfClass:[TWPhotosCollectionViewController class]]) {
TWPhotoDetailViewController *targetViewController = segue.destinationViewController;
NSIndexPath *indexPath = [[self.collectionView indexPathsForSelectedItems] lastObject];
/* Access the photo that was tapped and set the property of the target ViewController */
Photo *selectedPhoto = self.photos[indexPath.row];
targetViewController.photo = selectedPhoto;
}
}
}
@end
//10. Fixing the Bug (Deleting a Photo from Core Data)
//TWPhotosCollectionViewController.m
//move code from vieDidLoad to viewWillAppear
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
/* Access the photos from Core Data in the viewWillAppear method since this method is called everytime this viewcontroller appears on the screen instead of only the first time (viewDidLoad only is called only when it is created) */
-(void)viewWillAppear:(BOOL)animated
{
/* Call to the super classes implementation of viewWillAppear */
[super viewWillAppear:YES];
/* The Photos are stored in Core Data as an NSSet. */
NSSet *unorderedPhotos = self.album.photos;
/* To organize them we use a NSSort descriptor to arrange the photos by date. */
NSSortDescriptor *dateDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"date" ascending:YES];
NSArray *sortedPhotos = [unorderedPhotos sortedArrayUsingDescriptors:@[dateDescriptor]];
self.photos = [sortedPhotos mutableCopy];
/* Now that the photos are arranged we reload our CollectionView. */
[self.collectionView reloadData];
}
//TWPhotoDetailViewController.m
- (IBAction)deleteButtonPressed:(UIButton *)sender
{
/* Access the managed object context from our photo. Then tell core data to delete the photo object. */
[[self.photo managedObjectContext] deleteObject:self.photo];
NSError *error = nil;
/* Calling save is unecessary except that we are doing our testing on the simulator and auto saving does not work properly on it.*/
[[self.photo managedObjectContext] save:&error];
if (error){
NSLog(@"error");
}
[self.navigationController popViewControllerAnimated:YES];
}
//11. Preparing for Image Filters
//Adding a CollectionViewController for the Filters
//The edit button on our detailed view controller is going to be used for filtering our image. Create the TWFiltersCollectionViewController and segue to it.
//Creating Filters
//using a class called CIFilter. A filter takes an image (or more than one image) as input, and outputs a CIImage after a method called outputImage.
//If you’re thinking we have two images now, UIImage and CIImage. Why is that? Well, there actually are many more than two, and each class is used and optimized for different things.
//Our CI in the front of CIImage stands for the Core Image framework.
//Core Image is pretty low level, but provides an easy to use interface that hides the details that makes it so powerful. It efficiently uses threads efficiently through Grand Central Dispatch and can leverage the GPU (or CPU) for processing.
//converting a UIImage to a CIImage and back is actually a non-trivial process, although its not too much a challenge.
//However, try not to focus on that and instead focus the big picture.
//We are also going to be using an object called a CIContext, which like other contexts (NSMangedObjectContexts) acts very much like a scratchpad where we do our work. We’ll use when grabbing our CIImage from our Filter.
//TWFiltersCollectionViewController.h
/* Class forward */
@class Photo;
@interface TWFiltersCollectionViewController : UICollectionViewController
@property (strong, nonatomic) Photo *photo;
@end
//TWPhotoDetailViewController.m
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
/* Confirm that we are using the proper segue and transitioning to the proper ViewController */
if ([segue.identifier isEqualToString:@"Filter Segue"]){
if ([segue.destinationViewController isKindOfClass:[TWFiltersCollectionViewController class]]){
/* Set the targetViewController's photo property. The FiltersCollectionViewController will now have access to the photo object. */
TWFiltersCollectionViewController *targetViewController = segue.destinationViewController;
targetViewController.photo = self.photo;
}
}
}
//TWFiltersCollectionViewController.m
#import "TWPhotoCollectionViewCell.h" //reuse cell
#import "Photo.h"
@interface TWFiltersCollectionViewController ()
@property (strong, nonatomic) NSMutableArray *filters;
@end
@implementation TWFiltersCollectionViewController
/* Lazy Instantiation */
-(NSMutableArray *)filters
{
if (!_filters) _filters = [[NSMutableArray alloc] init];
return _filters;
}
#pragma mark - UICollectionView DataSource
/* Setup the CollectionView cells with the UIImages with the filters applied */
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Photo Cell";
TWPhotoCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
cell.backgroundColor = [UIColor whiteColor];
cell.imageView.image = self.photo.image;
return cell;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [self.filters count];
}
//12. Adding Filters
//TWFiltersCollectionViewController.m
#pragma mark - Helpers
/* Class method that returns an Array of filters */
+ (NSArray *)photoFilters
{
//filterWithName: Creates a CIFilter object for a specific kind of filter and initializes the input values.
CIFilter *sepia = [CIFilter filterWithName:@"CISepiaTone" keysAndValues:nil];
CIFilter *blur = [CIFilter filterWithName:@"CIGaussianBlur" keysAndValues: nil];
CIFilter *colorClamp = [CIFilter filterWithName:@"CIColorClamp"
keysAndValues:@"inputMaxComponents",
[CIVector vectorWithX:0.9 Y:0.9 Z:0.9 W:0.9],
@"inputMinComponents",
[CIVector vectorWithX:0.2 Y:0.2 Z:0.2 W:0.2], nil];
CIFilter *instant = [CIFilter filterWithName:@"CIPhotoEffectInstant" keysAndValues: nil];
CIFilter *noir = [CIFilter filterWithName:@"CIPhotoEffectNoir" keysAndValues: nil];
CIFilter *vignette = [CIFilter filterWithName:@"CIVignetteEffect" keysAndValues: nil];
CIFilter *colorControls = [CIFilter filterWithName:@"CIColorControls" keysAndValues:kCIInputSaturationKey, @0.5, nil];
CIFilter *transfer = [CIFilter filterWithName:@"CIPhotoEffectTransfer" keysAndValues: nil];
CIFilter *unsharpen = [CIFilter filterWithName:@"CIUnsharpMask" keysAndValues: nil];
CIFilter *monochrome = [CIFilter filterWithName:@"CIColorMonochrome" keysAndValues: nil];
NSArray *allFilters = @[sepia, blur, colorClamp, instant, noir, vignette, colorControls, transfer, unsharpen, monochrome];
return allFilters;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
/* call class method. Set the filters property using the class method photoFilters */
self.filters = [[[self class] photoFilters] mutableCopy]; //copy array object to mutableArray
}
//13. through filters
//TWFiltersCollectionViewController.m
@interface TWFiltersCollectionViewController ()
@property (strong, nonatomic) CIContext *context;
@end
@implementation TWFiltersCollectionViewController
/* Lazy Instantiation */
-(CIContext *)context
{
if (!_context) _context = [CIContext contextWithOptions:nil];
return _context;
}
#pragma mark - Helpers (convert to CIImage and apply filter, then convert CIImage to UIImage)
/* Method returns a UIImage with a filter applied */
-(UIImage *)filteredImageFromImage:(UIImage *)image andFilter:(CIFilter *)filter
{
/* Create a CIImage using the property on UIImage, CGImage. */
CIImage *unfilteredImage = [[CIImage alloc] initWithCGImage:image.CGImage];
/* Set the filter with the unfiltered CIImage for key kCIInputImageKey */
[filter setValue:unfilteredImage forKey:kCIInputImageKey];
/* Get the filtered image back calling the method outputImage */
CIImage *filteredImage = [filter outputImage];
/* Get the size of the image using the method extent */
CGRect extent = [filteredImage extent];
/* Create a CGImageRef using the method createCGImage with the size extent. */
CGImageRef cgImage = [self.context createCGImage:filteredImage fromRect:extent];
/* Create a UIImage from our cgImage. */
UIImage *finalImage = [UIImage imageWithCGImage:cgImage];
NSLog(@"Look at all of this data %@", UIImagePNGRepresentation(finalImage));
return finalImage;
}
#pragma mark - UICollectionView DataSource
/* Setup the CollectionView cells with the UIImages with the filters applied */
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Photo Cell";
TWPhotoCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
cell.backgroundColor = [UIColor whiteColor];
//convert to CIImage and apply filter, then convert CIImage to UIImage
cell.imageView.image = [self filteredImageFromImage:self.photo.image andFilter:self.filters[indexPath.row]];
return cell;
}
//14. saving filters
//TWFiltersCollectionViewController.m
#pragma mark - UICollectionView Delegate
/* When the user selects one of the filters save the image with the filter. */
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
TWPhotoCollectionViewCell *selectedCell = (TWPhotoCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
self.photo.image = selectedCell.imageView.image;
NSError *error = nil;
if (![[self.photo managedObjectContext] save:&error]){
//Handle Error
NSLog(@"%@", error);
}
[self.navigationController popViewControllerAnimated:YES];
}
//15. Blocks, GCD and Threading
//TWFiltersCollectionViewController.m
#pragma mark - UICollectionView DataSource
/* Setup the CollectionView cells with the UIImages with the filters applied */
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Photo Cell";
TWPhotoCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
cell.backgroundColor = [UIColor whiteColor];
/* Create a queue so that we can have the filter application occur on another thread. */
dispatch_queue_t filterQueue = dispatch_queue_create("filter queue", NULL);
/* Kick off another thread using a block. Perform the filter in the block*/
dispatch_async(filterQueue, ^{
UIImage *filterImage = [self filteredImageFromImage:self.photo.image andFilter:self.filters[indexPath.row]];
/* UI adjustments must occur on the main thread. */
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = filterImage;
});
});
return cell;
}
//16. Debug (필터가 적용되고난 후에 이미지 selection 가능하도록 함)
//TWFiltersCollectionViewController.m
#pragma mark - UICollectionView Delegate
/* When the user selects one of the filters save the image with the filter. */
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
TWPhotoCollectionViewCell *selectedCell = (TWPhotoCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
self.photo.image = selectedCell.imageView.image;
/* Make sure that cell has a filter before we allow the user to select the cell. */
//필터가 적용되고난 후에 이미지 selection 가능하도록 함
if (self.photo.image){
NSError *error = nil;
if (![[self.photo managedObjectContext] save:&error]){
//Handle Error
NSLog(@"%@", error);
}
[self.navigationController popViewControllerAnimated:YES];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment