Skip to content

Instantly share code, notes, and snippets.

@ffried
Last active August 29, 2015 14:00
Show Gist options
  • Save ffried/11168447 to your computer and use it in GitHub Desktop.
Save ffried/11168447 to your computer and use it in GitHub Desktop.
UIView+FFGradient
//
// UIView+FFGradient.h
//
// Created by Florian Friedrich on 22.04.14.
// Copyright (c) 2014 Florian Friedrich. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import <UIKit/UIKit.h>
/**
* Represents a stop in an FFGradient.
* @see FFGradient
*/
@interface FFGradientStop : NSObject
/**
* The color of the stop.
*/
@property (nonatomic, strong) UIColor *color;
/**
* Where the color will be. Must be between 0.0f and 1.0f.
*/
@property (nonatomic, strong) NSNumber *location;
/**
* Creates a new FFGradientStop with a color and a location.
* @param color The color for the stop.
* @param location The location of the stop.
* @return A new FFGradientStop instance.
*/
+ (instancetype)stopWithColor:(UIColor *)color location:(CGFloat)location;
/**
* Creates a new FFGradientStop with only a color.
* @param color The color for the stop.
* @return A new FFGradientStop instance.
*/
+ (instancetype)stopWithColor:(UIColor *)color;
/**
* Creates a new FFGradientStop with a color and a location.
* @param color The color for the stop.
* @param location The location of the stop.
* @return A new FFGradientStop instance.
*/
- (instancetype)initWithColor:(UIColor *)color location:(NSNumber *)location;
@end
/**
* Represents a gradient.
*/
@interface FFGradient : NSObject
/**
* An array of FFGradientStop instances.
*/
@property (nonatomic, strong) NSArray *stops;
/**
* Creates a new FFGradient with given stops.
* @param stops The stops of the new gradient.
* @return A new FFGradient instance.
*/
+ (instancetype)gradientWithStops:(NSArray *)stops;
/**
* Creates a new FFGradient with given stops.
* @param stops The stops of the new gradient.
* @return A new FFGradient instance.
*/
- (instancetype)initWithStops:(NSArray *)stops;
/**
* Calculates the locations for the stops for a linear gradient.
*/
- (void)calculateLocations;
@end
/**
* Allows to set an FFGradient as UIView background.
*/
@interface UIView (FFGradient)
/**
* The view's background gradient.
*/
@property (nonatomic, strong) FFGradient *backgroundGradient;
@end
//
// UIView+FFGradient.m
//
// Created by Florian Friedrich on 22.04.14.
// Copyright (c) 2014 Florian Friedrich. All rights reserved.
//
#import "UIView+FFGradient.h"
#import <objc/runtime.h>
#import <QuartzCore/QuartzCore.h>
@implementation FFGradientStop
+ (instancetype)stopWithColor:(UIColor *)color location:(CGFloat)location
{
return [[self alloc] initWithColor:color location:@(location)];
}
+ (instancetype)stopWithColor:(UIColor *)color
{
return [[self alloc] initWithColor:color location:nil];
}
- (instancetype)initWithColor:(UIColor *)color location:(NSNumber *)location
{
NSAssert(location.floatValue >= 0.0f, @"Location must be between 0.0f and 1.0f!");
NSAssert(location.floatValue <= 1.0f, @"Location must be between 0.0f and 1.0f!");
self = [super init];
if (self) {
self.color = color;
self.location = location;
}
return self;
}
- (instancetype)init { return [self initWithColor:nil location:nil]; }
@end
NSString *const FFGradientLayerName = @"FFGradientLayer";
@interface FFGradient ()
@property (nonatomic, strong, readonly) CAGradientLayer *gradientLayer;
@end
@implementation FFGradient
+ (instancetype)gradientWithStops:(NSArray *)stops
{
return [[self alloc] initWithStops:stops];
}
- (instancetype)initWithStops:(NSArray *)stops
{
self = [super init];
if (self) {
self.stops = stops;
}
return self;
}
- (instancetype)init { return [self initWithStops:nil]; }
- (void)calculateLocations
{
CGFloat count = (CGFloat)self.stops.count - 1.0f;
[self.stops enumerateObjectsUsingBlock:^(FFGradientStop *gStop, NSUInteger idx, BOOL *stop) {
gStop.location = @((CGFloat)idx / count);
}];
}
- (CAGradientLayer *)gradientLayer
{
if (!self.stops.count) return nil;
NSMutableArray *colors = [NSMutableArray array];
NSMutableArray *locations = [NSMutableArray array];
CGFloat count = (CGFloat)self.stops.count - 1.0f;
[self.stops enumerateObjectsUsingBlock:^(FFGradientStop *gStop, NSUInteger idx, BOOL *stop) {
UIColor *color = gStop.color;
if (!color) color = [UIColor clearColor];
[colors addObject:(__bridge id)gStop.color.CGColor];
NSNumber *location = gStop.location;
if (!location) location = @((CGFloat)idx / count);
[locations addObject:location];
}];
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.colors = [colors copy];
gradient.locations = [locations copy];
gradient.name = FFGradientLayerName;
return gradient;
}
@end
@implementation UIView (FFGradient)
static NSString *const FFBackgroundGradientKey = @"FFBackgroundGradient";
- (FFGradient *)backgroundGradient
{
return objc_getAssociatedObject(self, (__bridge const void *)(FFBackgroundGradientKey));
}
- (void)setBackgroundGradient:(FFGradient *)backgroundGradient
{
objc_setAssociatedObject(self, (__bridge const void *)(FFBackgroundGradientKey),
backgroundGradient, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[[self.layer.sublayers copy] enumerateObjectsUsingBlock:^(CALayer *l, NSUInteger idx, BOOL *stop) {
if ([l.name isEqualToString:FFGradientLayerName]) {
[l removeFromSuperlayer];
}
}];
if (backgroundGradient) {
CAGradientLayer *gradientLayer = backgroundGradient.gradientLayer;
gradientLayer.frame = self.bounds;
[self.layer insertSublayer:gradientLayer atIndex:0];
}
}
@end
@ffried
Copy link
Author

ffried commented Apr 22, 2014

Example usages

With locations specified:

NSArray *stops = @[
                   [FFGradientStop stopWithColor:[UIColor greenColor] location:0.0f],
                   [FFGradientStop stopWithColor:[UIColor yellowColor] location:0.5f],
                   [FFGradientStop stopWithColor:[UIColor redColor] location:1.0f]
                   ];
self.gradientView.backgroundGradient = [FFGradient gradientWithStops:stops];

Without locations specified:

NSArray *stops = @[
                   [FFGradientStop stopWithColor:[UIColor blueColor]],
                   [FFGradientStop stopWithColor:[UIColor purpleColor]],
                   [FFGradientStop stopWithColor:[UIColor greenColor]],
                   [FFGradientStop stopWithColor:[UIColor yellowColor]],
                   [FFGradientStop stopWithColor:[UIColor orangeColor]],
                   [FFGradientStop stopWithColor:[UIColor redColor]]
                   ];
self.gradientView.backgroundGradient = [FFGradient gradientWithStops:stops];

If you don't specify the locations they will be calculated to become a regular linear gradient when you set it as backgroundGradient of a view. You can also force the calculation (or a re-calculation) yourself by calling [myGradient calculateLocations]; where myGradient is an FFGradient instance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment