HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Creating and saving blurred screenshots in iOS

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
screenshotsioscreatingblurredandsaving

Problem

I am trying to create a class that takes a screenshot of the screen, makes it blurry and saves it to NSUserDefaults.

- (UIImage*)blurImage
{
    UIImage* newImage = [[UIImage screenshot] applyBlurWithRadius:20
                                                         blurType:BOXFILTER
                                                        tintColor:[UIColor colorWithWhite:0.11f alpha:0.5]
                                            saturationDeltaFactor:1.3
                                                        maskImage:nil];
    return newImage;
}
- (void)saveBlurredImageToUserDefaults
{
    [[NSUserDefaults standardUserDefaults] setObject:UIImagePNGRepresentation([self blurImage]) forKey:@"blurredImage"];
    [[NSUserDefaults standardUserDefaults] synchronize];
}


applyBlurWithRadius:blurType:tintColor:saturationDeltaFactor:maskImage: is a UIImage category based on code provided by Apple at WWDC 2013. that can be found here in full.

Here is that method in case that repo disappears:

```
  • (UIImage )applyBlurWithRadius:(CGFloat)blurRadius blurType: (BlurType) blurType tintColor:(UIColor )tintColor saturationDeltaFactor:(CGFloat)saturationDeltaFactor maskImage:(UIImage *)maskImage


{
// Check pre-conditions.
if (self.size.width = 1: %@", self.size.width, self.size.height, self);
return nil;
}
if (!self.CGImage) {
NSLog (@"*** error: image must be backed by a CGImage: %@", self);
return nil;
}
if (maskImage && !maskImage.CGImage) {
NSLog (@"*** error: maskImage must be backed by a CGImage: %@", maskImage);
return nil;
}

CGRect imageRect = { CGPointZero, self.size };
UIImage *effectImage = self;

BOOL hasBlur = blurRadius > __FLT_EPSILON__ && blurType != NOBLUR;
BOOL hasSaturationChange = fabs(saturationDeltaFactor - 1.) > __FLT_EPSILON__;
if (hasBlur || hasSaturationChange) {
UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] sc

Solution

Ultimately, it looks like your code has gone a long way toward being very convenient in the one spot you want to save a blurred screenshot to NSUserDefaults... but a long way toward being highly specific and not at all reusable.

For starters, we don't need a class for this at all. Classes should be thought of as things... as nouns... as the subject of a sentence. Methods, meanwhile, should be thought of as actions... as verbs... as the predicate of a sentence. We should create a class when we need to represent a thing. We should create a method when we need to do an action. We should not create a class simply because we feel like we need something to hold our collection of methods.

Objective-C is a strict superset of C. Anything you can do in C can also be done in Objective-C. And in C, you don't even have classes! So you certainly weren't creating classes just for the sake of holding a collection of methods! But like in C, in Objective-C, we can create "free functions". Functions that do not belong to any classes.

Also, importantly, in Objective-C, when we need to add actions to an existing thing, we can do that too!

If we keep with the grammar metaphor, the properties of a class are sort of like adjectives. If we need to add adjectives to a class, we should create a subclass. If all we need to do is add actions (verbs... methods...) to a class, then in Objective-C, we can create a category.

Your code makes use of two good examples of using categories, so I'm not sure why your blurImage method is anything but a UIImage category and why you've created a class?

So, let's make it a category:

UIImage+Blur.h

@interface UIImage (Blur)

- (UIImage *)blurImage;

@end


UIImage+Blur.m

@implementation UIImage (Blur)

- (UIImage *)blurImage {
    return [self applyBlurWithRadius:20
                            blurType:BOXFILTER
                           tintColor:[UIColor colorWithWhite:0.11f alpha:0.5]
               saturationDeltaFactor:1.3
                           maskImage:nil];
}

@end


Though, a more specific name for this specific type of blur would probably be appropriate. You may decide you want to create other convenience methods for other types of blurs.

Now, we can get a blurred screenshot as simple as:

UIImage *blurredScreenshot = [[UIImage screenshot] blurImage];


And if we're really doing this so frequently, we can get rid of the nesting by adding another method to our category:

+ (UIImage *)blurredScreenshot {
    return [[UIImage screenshot] blurImage];
}


And then:

UIImage *blurredScreenshot = [UIImage blurredScreenshot];


The advantage here is that we've made it more convenient for us (created the blurredScreenshot method), but we haven't hampered the reusability, because we still have the screenshot and blurImage methods. These methods do distinct things and anyone may want to use these individually at a later date (including yourself).

As far as saving to NSUserDefaults? This is already a one-liner. Calling synchronize is almost always unnecessary and doing so can have a negative impact on your app's performance. The only time you will lose data via not synchronized is if your app crashes or possibly if the user's device crashes. If the former is a problem, calling synchronize isn't really the real fix, is it? If the latter is the problem... I wouldn't bother with it... and tell the user to get their device fixed...

And finally, one-lining things isn't always inherently good. Code should be readable, not terse. Fewer lines doesn't make code run faster, meanwhile, it does generally make human reading and debugging more difficult.

Also, keys should typically be defined as a constant somewhere... I usually have a header file:

NSUserDefaultsKeys.h

NSString * const kUSERDEFKEY_BLURREDIMAGE = @"blurredImage";


So, with the given category above, I'd most likely implement it as such:

UIImage *blurredScreenshot = [[UIImage screenshot] blurImage];
NSData *blurredJPEGdata = UIImagePNGRepresentation(blurredScreenshot);
[[NSUserDefaults standardDefaults] setObject:blurredJPEGdata 
                                      forKey:kUSERDEFKEY_BLURREDIMAGE];


And I wouldn't necessarily try wrapping this up into any sort of convenience method or function. This is just fine as is...

Code Snippets

@interface UIImage (Blur)

- (UIImage *)blurImage;

@end
@implementation UIImage (Blur)

- (UIImage *)blurImage {
    return [self applyBlurWithRadius:20
                            blurType:BOXFILTER
                           tintColor:[UIColor colorWithWhite:0.11f alpha:0.5]
               saturationDeltaFactor:1.3
                           maskImage:nil];
}

@end
UIImage *blurredScreenshot = [[UIImage screenshot] blurImage];
+ (UIImage *)blurredScreenshot {
    return [[UIImage screenshot] blurImage];
}
UIImage *blurredScreenshot = [UIImage blurredScreenshot];

Context

StackExchange Code Review Q#82623, answer score: 4

Revisions (0)

No revisions yet.