Epub阅读器0.0.1
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// DTActivityTitleView.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Rene Pirringer on 12.09.12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Availability.h>
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
/**
|
||||
Alternative view for showing titles with a configurable activity indicator
|
||||
instead of default title view in navigationItem.
|
||||
*/
|
||||
@interface DTActivityTitleView : UIView
|
||||
|
||||
/**
|
||||
Title that is shown
|
||||
*/
|
||||
@property (nonatomic, copy) NSString *title;
|
||||
|
||||
/**
|
||||
When busy is set to YES the activity indicator starts spinning
|
||||
When set to NO the activity indicator stops spinning
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL busy;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,129 @@
|
||||
//
|
||||
// DTActivityTitleView.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Rene Pirringer on 12.09.12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DTActivityTitleView.h"
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface DTActivityTitleView ()
|
||||
|
||||
@property (nonatomic, strong) UIActivityIndicatorView *activityIndicator;
|
||||
@property (nonatomic, strong) UILabel *titleLabel;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation DTActivityTitleView
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
self.titleLabel = [[UILabel alloc] init];
|
||||
self.titleLabel.backgroundColor = [UIColor clearColor];
|
||||
|
||||
self.activityIndicator.hidesWhenStopped = YES;
|
||||
|
||||
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
|
||||
{
|
||||
if (@available(iOS 13, tvOS 13, *)) {
|
||||
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium];
|
||||
}
|
||||
else
|
||||
{
|
||||
#if TARGET_OS_TV
|
||||
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
|
||||
#else
|
||||
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
|
||||
#endif
|
||||
}
|
||||
|
||||
self.titleLabel.textColor = [UIColor colorWithRed:113.0/255.0 green:120.0/255.0 blue:128.0/255.0 alpha:1.0];
|
||||
self.titleLabel.shadowOffset = CGSizeMake(0, 1);
|
||||
self.titleLabel.shadowColor = [UIColor whiteColor];
|
||||
}
|
||||
else
|
||||
{
|
||||
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
|
||||
self.titleLabel.textColor = [UIColor whiteColor];
|
||||
self.titleLabel.shadowOffset = CGSizeMake(0, -1);
|
||||
self.titleLabel.shadowColor = [UIColor blackColor];
|
||||
}
|
||||
|
||||
self.titleLabel.font = [UIFont boldSystemFontOfSize:20];
|
||||
[self addSubview:self.titleLabel];
|
||||
[self addSubview:self.activityIndicator];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (void)setBusy:(BOOL)busy
|
||||
{
|
||||
if (busy)
|
||||
{
|
||||
[self.activityIndicator startAnimating];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self.activityIndicator stopAnimating];
|
||||
}
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (BOOL)busy
|
||||
{
|
||||
return self.activityIndicator.isAnimating;
|
||||
}
|
||||
|
||||
- (void)setTitle:(NSString *)title
|
||||
{
|
||||
self.titleLabel.text = title;
|
||||
CGFloat gap = 5.0;
|
||||
CGFloat height = self.activityIndicator.frame.size.height;
|
||||
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED > __IPHONE_6_1
|
||||
// issue 60: sizeWithFont: is deprecated with deployment target >= iOS 7
|
||||
NSDictionary *attribs = @{NSFontAttributeName:self.titleLabel.font};
|
||||
CGSize neededSize = [self.titleLabel.text sizeWithAttributes:attribs];
|
||||
#else
|
||||
CGSize neededSize = [self.titleLabel.text sizeWithFont:self.titleLabel.font];
|
||||
#endif
|
||||
|
||||
if (height < neededSize.height)
|
||||
{
|
||||
height = neededSize.height;
|
||||
}
|
||||
|
||||
CGRect titleRect = CGRectMake(self.activityIndicator.frame.size.width+gap, 0, neededSize.width, height);
|
||||
self.titleLabel.frame = titleRect;
|
||||
self.bounds = CGRectMake(0, 0, self.activityIndicator.frame.size.width+neededSize.width+gap, height);
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (NSString *)title
|
||||
{
|
||||
return self.titleLabel.text;
|
||||
}
|
||||
|
||||
@synthesize activityIndicator = _activityIndicator;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// DTAnimatedGIF.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 7/2/14.
|
||||
// Copyright (c) 2014 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Availability.h>
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
|
||||
/**
|
||||
Loads an animated GIF from file, compatible with UIImageView
|
||||
*/
|
||||
UIImage *DTAnimatedGIFFromFile(NSString *path);
|
||||
|
||||
/**
|
||||
Loads an animated GIF from data, compatible with UIImageView
|
||||
*/
|
||||
UIImage *DTAnimatedGIFFromData(NSData *data);
|
||||
|
||||
#endif
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
//
|
||||
// DTAnimatedGIF.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 7/2/14.
|
||||
// Copyright (c) 2014 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DTAnimatedGIF.h"
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
|
||||
#import <ImageIO/ImageIO.h>
|
||||
|
||||
// returns the frame duration for a given image in 1/100th seconds
|
||||
// source: http://stackoverflow.com/questions/16964366/delaytime-or-unclampeddelaytime-for-gifs
|
||||
static NSUInteger DTAnimatedGIFFrameDurationForImageAtIndex(CGImageSourceRef source, NSUInteger index)
|
||||
{
|
||||
NSUInteger frameDuration = 10;
|
||||
|
||||
NSDictionary *frameProperties = CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(source,index,nil));
|
||||
NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
|
||||
|
||||
NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
|
||||
|
||||
if(delayTimeUnclampedProp)
|
||||
{
|
||||
frameDuration = [delayTimeUnclampedProp floatValue]*100;
|
||||
}
|
||||
else
|
||||
{
|
||||
NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];
|
||||
|
||||
if(delayTimeProp)
|
||||
{
|
||||
frameDuration = [delayTimeProp floatValue]*100;
|
||||
}
|
||||
}
|
||||
|
||||
// Many annoying ads specify a 0 duration to make an image flash as quickly as possible.
|
||||
// We follow Firefox's behavior and use a duration of 100 ms for any frames that specify
|
||||
// a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082>
|
||||
// for more information.
|
||||
|
||||
if (frameDuration < 1)
|
||||
{
|
||||
frameDuration = 10;
|
||||
}
|
||||
|
||||
return frameDuration;
|
||||
}
|
||||
|
||||
// returns the great common factor of two numbers
|
||||
static NSUInteger DTAnimatedGIFGreatestCommonFactor(NSUInteger num1, NSUInteger num2)
|
||||
{
|
||||
NSUInteger t, remainder;
|
||||
|
||||
if (num1 < num2)
|
||||
{
|
||||
t = num1;
|
||||
num1 = num2;
|
||||
num2 = t;
|
||||
}
|
||||
|
||||
remainder = num1 % num2;
|
||||
|
||||
if (!remainder)
|
||||
{
|
||||
return num2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return DTAnimatedGIFGreatestCommonFactor(num2, remainder);
|
||||
}
|
||||
}
|
||||
|
||||
static UIImage *DTAnimatedGIFFromImageSource(CGImageSourceRef source)
|
||||
{
|
||||
size_t const numImages = CGImageSourceGetCount(source);
|
||||
|
||||
NSMutableArray *frames = [NSMutableArray arrayWithCapacity:numImages];
|
||||
|
||||
// determine gretest common factor of all image durations
|
||||
NSUInteger greatestCommonFactor = DTAnimatedGIFFrameDurationForImageAtIndex(source, 0);
|
||||
|
||||
for (NSUInteger i=1; i<numImages; i++)
|
||||
{
|
||||
NSUInteger centiSecs = DTAnimatedGIFFrameDurationForImageAtIndex(source, i);
|
||||
greatestCommonFactor = DTAnimatedGIFGreatestCommonFactor(greatestCommonFactor, centiSecs);
|
||||
}
|
||||
|
||||
// build array of images, duplicating as necessary
|
||||
for (NSUInteger i=0; i<numImages; i++)
|
||||
{
|
||||
CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, i, NULL);
|
||||
UIImage *frame = [UIImage imageWithCGImage:cgImage];
|
||||
|
||||
NSUInteger centiSecs = DTAnimatedGIFFrameDurationForImageAtIndex(source, i);
|
||||
NSUInteger repeat = centiSecs/greatestCommonFactor;
|
||||
|
||||
for (NSUInteger j=0; j<repeat; j++)
|
||||
{
|
||||
[frames addObject:frame];
|
||||
}
|
||||
|
||||
CGImageRelease(cgImage);
|
||||
}
|
||||
|
||||
// create animated image from the array
|
||||
NSTimeInterval totalDuration = [frames count] * greatestCommonFactor / 100.0;
|
||||
return [UIImage animatedImageWithImages:frames duration:totalDuration];
|
||||
}
|
||||
|
||||
UIImage * _Nullable DTAnimatedGIFFromFile(NSString * _Nonnull path)
|
||||
{
|
||||
NSURL *URL = [NSURL fileURLWithPath:path];
|
||||
CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)(URL), NULL);
|
||||
|
||||
if (!source)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
UIImage *image = DTAnimatedGIFFromImageSource(source);
|
||||
CFRelease(source);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
UIImage * _Nullable DTAnimatedGIFFromData(NSData * _Nonnull data)
|
||||
{
|
||||
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)(data), NULL);
|
||||
|
||||
if (!source)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
UIImage *image = DTAnimatedGIFFromImageSource(source);
|
||||
CFRelease(source);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// DTCustomColoredAccessory.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 2/10/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Availability.h>
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
/**
|
||||
Constant used by DTCustomColoredAccessory to specify the type of accessory.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, DTCustomColoredAccessoryType)
|
||||
{
|
||||
/**
|
||||
An accessoring pointing to the right side
|
||||
*/
|
||||
DTCustomColoredAccessoryTypeRight = 0,
|
||||
|
||||
/**
|
||||
An accessoring pointing to the left side
|
||||
*/
|
||||
DTCustomColoredAccessoryTypeLeft,
|
||||
|
||||
/**
|
||||
An accessoring pointing upwards
|
||||
*/
|
||||
DTCustomColoredAccessoryTypeUp,
|
||||
|
||||
/**
|
||||
An accessoring pointing downwards
|
||||
*/
|
||||
DTCustomColoredAccessoryTypeDown,
|
||||
|
||||
/**
|
||||
A front square drawn on top of a back square with the back square offset up and to the right
|
||||
*/
|
||||
DTCustomColoredAccessoryTypeSquare
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
An accessory control that can be used instead of the standard disclosure indicator in a `UITableView`. See the DTCustomColoredAccessoryType for supported styles.
|
||||
*/
|
||||
|
||||
@interface DTCustomColoredAccessory : UIControl
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Creating A Custom-Colored Accessory
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
Creates a custom-colored right disclosure indicator accessory with a given color
|
||||
@param color The color to use
|
||||
*/
|
||||
+ (DTCustomColoredAccessory *)accessoryWithColor:(UIColor *)color;
|
||||
|
||||
/**
|
||||
Creates a custom-colored accessory with a given color and type
|
||||
@param color The color to use
|
||||
@param type The DTCustomColoredAccessoryType to use
|
||||
*/
|
||||
+ (DTCustomColoredAccessory *)accessoryWithColor:(UIColor *)color type:(DTCustomColoredAccessoryType)type;
|
||||
|
||||
/**
|
||||
Creates a custom-colored square on top of a square with offset
|
||||
@param color The color to use
|
||||
@param backgroundColor The backgroundColor to use
|
||||
*/
|
||||
+ (DTCustomColoredAccessory *)squareAccessoryWithColor:(UIColor *)color backgroundColor:(UIColor *)backgroundColor;
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Properties
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
The color to draw the accessory in
|
||||
*/
|
||||
@property (nonatomic, retain) UIColor *accessoryColor;
|
||||
|
||||
/**
|
||||
The color to draw the accessory in while highlighted
|
||||
*/
|
||||
@property (nonatomic, retain) UIColor *highlightedColor;
|
||||
/**
|
||||
The color to draw the front square of the square accessory in while not highlighted
|
||||
*/
|
||||
@property (nonatomic, retain) UIColor *frontSquareAccessoryColor;
|
||||
/**
|
||||
The color to draw the back square of the square accessory in while not highlighted
|
||||
*/
|
||||
@property (nonatomic, retain) UIColor *backSquareAccessoryColor;
|
||||
|
||||
/**
|
||||
The DTCustomColoredAccessoryType of the accessory.
|
||||
*/
|
||||
@property (nonatomic, assign) DTCustomColoredAccessoryType type;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,242 @@
|
||||
//
|
||||
// DTCustomColoredAccessory.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 2/10/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DTCustomColoredAccessory.h"
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@implementation DTCustomColoredAccessory
|
||||
{
|
||||
UIColor *_accessoryColor;
|
||||
UIColor *_highlightedColor;
|
||||
|
||||
DTCustomColoredAccessoryType _type;
|
||||
}
|
||||
|
||||
#pragma mark - Creating A Custom-Colored Accessory
|
||||
|
||||
+ (DTCustomColoredAccessory *)accessoryWithColor:(UIColor *)color
|
||||
{
|
||||
return [self accessoryWithColor:color type:DTCustomColoredAccessoryTypeRight];
|
||||
}
|
||||
|
||||
+ (DTCustomColoredAccessory *)accessoryWithColor:(UIColor *)color type:(DTCustomColoredAccessoryType)type
|
||||
{
|
||||
DTCustomColoredAccessory *ret = [[DTCustomColoredAccessory alloc] initWithFrame:CGRectMake(0, 0, 15.0, 15.0)];
|
||||
ret.accessoryColor = color;
|
||||
ret.frontSquareAccessoryColor = color;
|
||||
ret.type = type;
|
||||
ret.backSquareAccessoryColor = color == [UIColor blackColor] ? [UIColor whiteColor] : [UIColor blackColor];
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
+ (DTCustomColoredAccessory *)squareAccessoryWithColor:(UIColor *)color backgroundColor:(UIColor *)backgroundColor
|
||||
{
|
||||
DTCustomColoredAccessory *ret = [[DTCustomColoredAccessory alloc] initWithFrame:CGRectMake(0, 0, 15.0, 15.0)];
|
||||
ret.accessoryColor = color;
|
||||
ret.frontSquareAccessoryColor = color;
|
||||
ret.type = DTCustomColoredAccessoryTypeSquare;
|
||||
ret.backSquareAccessoryColor = backgroundColor;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#pragma mark - Internal Methods
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if ((self = [super initWithFrame:frame]))
|
||||
{
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
CGContextRef ctxt = UIGraphicsGetCurrentContext();
|
||||
|
||||
const CGFloat R = 4.5;
|
||||
|
||||
BOOL doFinal = true;
|
||||
|
||||
switch (_type)
|
||||
{
|
||||
case DTCustomColoredAccessoryTypeRight:
|
||||
{
|
||||
// (x,y) is the tip of the arrow
|
||||
CGFloat x = CGRectGetMaxX(self.bounds)-3.0;;
|
||||
CGFloat y = CGRectGetMidY(self.bounds);
|
||||
|
||||
CGContextMoveToPoint(ctxt, x-R, y-R);
|
||||
CGContextAddLineToPoint(ctxt, x, y);
|
||||
CGContextAddLineToPoint(ctxt, x-R, y+R);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DTCustomColoredAccessoryTypeLeft:
|
||||
{
|
||||
// (x,y) is the tip of the arrow
|
||||
CGFloat x = CGRectGetMaxX(self.bounds)-10.0;;
|
||||
CGFloat y = CGRectGetMidY(self.bounds);
|
||||
|
||||
CGContextMoveToPoint(ctxt, x+R, y+R);
|
||||
CGContextAddLineToPoint(ctxt, x, y);
|
||||
CGContextAddLineToPoint(ctxt, x+R, y-R);
|
||||
break;
|
||||
}
|
||||
|
||||
case DTCustomColoredAccessoryTypeUp:
|
||||
{
|
||||
// (x,y) is the tip of the arrow
|
||||
CGFloat x = CGRectGetMaxX(self.bounds)-7.0;;
|
||||
CGFloat y = CGRectGetMinY(self.bounds)+5.0;
|
||||
|
||||
CGContextMoveToPoint(ctxt, x-R, y+R);
|
||||
CGContextAddLineToPoint(ctxt, x, y);
|
||||
CGContextAddLineToPoint(ctxt, x+R, y+R);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DTCustomColoredAccessoryTypeDown:
|
||||
{
|
||||
// (x,y) is the tip of the arrow
|
||||
CGFloat x = CGRectGetMaxX(self.bounds)-7.0;;
|
||||
CGFloat y = CGRectGetMaxY(self.bounds)-5.0;
|
||||
|
||||
CGContextMoveToPoint(ctxt, x-R, y-R);
|
||||
CGContextAddLineToPoint(ctxt, x, y);
|
||||
CGContextAddLineToPoint(ctxt, x+R, y-R);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DTCustomColoredAccessoryTypeSquare:
|
||||
{
|
||||
doFinal = false;
|
||||
|
||||
// (x,y) is the tip of the arrow
|
||||
CGFloat x = CGRectGetMinX(self.bounds)+5.0;
|
||||
CGFloat y = CGRectGetMinY(self.bounds)+1.0;
|
||||
|
||||
CGFloat s = 9.0;
|
||||
|
||||
CGContextMoveToPoint(ctxt, x, y);
|
||||
CGContextAddLineToPoint(ctxt, x+s, y);
|
||||
CGContextAddLineToPoint(ctxt, x+s, y+s);
|
||||
CGContextAddLineToPoint(ctxt, x, y+s);
|
||||
CGContextClosePath(ctxt);
|
||||
|
||||
CGContextSetLineCap(ctxt, kCGLineCapSquare);
|
||||
CGContextSetLineJoin(ctxt, kCGLineJoinMiter);
|
||||
CGContextSetLineWidth(ctxt, 3);
|
||||
|
||||
if (self.highlighted)
|
||||
{
|
||||
[self.highlightedColor setStroke];
|
||||
}
|
||||
else
|
||||
{
|
||||
_accessoryColor = self.backSquareAccessoryColor;
|
||||
[self.accessoryColor setStroke];
|
||||
}
|
||||
|
||||
CGContextStrokePath(ctxt);
|
||||
|
||||
x = CGRectGetMinX(self.bounds)+3.0;
|
||||
y = CGRectGetMinY(self.bounds)+3.0;
|
||||
|
||||
CGContextMoveToPoint(ctxt, x, y);
|
||||
CGContextAddLineToPoint(ctxt, x+s, y);
|
||||
CGContextAddLineToPoint(ctxt, x+s, y+s);
|
||||
CGContextAddLineToPoint(ctxt, x, y+s);
|
||||
CGContextClosePath(ctxt);
|
||||
|
||||
CGContextSetLineCap(ctxt, kCGLineCapSquare);
|
||||
CGContextSetLineJoin(ctxt, kCGLineJoinMiter);
|
||||
CGContextSetLineWidth(ctxt, 3);
|
||||
|
||||
if (self.highlighted)
|
||||
{
|
||||
[self.highlightedColor setStroke];
|
||||
}
|
||||
else
|
||||
{
|
||||
_accessoryColor = self.frontSquareAccessoryColor;
|
||||
[self.accessoryColor setStroke];
|
||||
}
|
||||
|
||||
CGContextStrokePath(ctxt);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (doFinal)
|
||||
{
|
||||
CGContextSetLineCap(ctxt, kCGLineCapSquare);
|
||||
CGContextSetLineJoin(ctxt, kCGLineJoinMiter);
|
||||
CGContextSetLineWidth(ctxt, 3);
|
||||
|
||||
if (self.highlighted)
|
||||
{
|
||||
[self.highlightedColor setStroke];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self.accessoryColor setStroke];
|
||||
}
|
||||
|
||||
CGContextStrokePath(ctxt);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (void)setHighlighted:(BOOL)highlighted
|
||||
{
|
||||
[super setHighlighted:highlighted];
|
||||
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (UIColor *)accessoryColor
|
||||
{
|
||||
if (!_accessoryColor)
|
||||
{
|
||||
return [UIColor blackColor];
|
||||
}
|
||||
|
||||
return _accessoryColor;
|
||||
}
|
||||
|
||||
- (UIColor *)highlightedColor
|
||||
{
|
||||
if (!_highlightedColor)
|
||||
{
|
||||
return [UIColor whiteColor];
|
||||
}
|
||||
|
||||
return _highlightedColor;
|
||||
}
|
||||
|
||||
@synthesize accessoryColor = _accessoryColor;
|
||||
@synthesize highlightedColor = _highlightedColor;
|
||||
@synthesize type = _type;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// DTPieProgressIndicator.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 16.05.12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Availability.h>
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
/**
|
||||
A Progress indicator shaped like a pie chart. If you don't specify a color then the current tintColor is used. This is useful when using it as a subview of a UIVisualEffectsView with vibrancy effect. Then all subviews using tintColor have the vibrancy applied.
|
||||
*/
|
||||
|
||||
@interface DTPieProgressIndicator : UIView
|
||||
|
||||
/**
|
||||
The progress in percent
|
||||
*/
|
||||
@property (nonatomic, assign) float progressPercent;
|
||||
|
||||
/**
|
||||
The color of the pie
|
||||
*/
|
||||
@property (nonatomic, strong) UIColor *color;
|
||||
|
||||
/**
|
||||
Creates a pie progress indicator of the correct size
|
||||
*/
|
||||
+ (DTPieProgressIndicator *)pieProgressIndicator;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// DTPieProgressIndicator.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 16.05.12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DTPieProgressIndicator.h"
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "DTCoreGraphicsUtils.h"
|
||||
|
||||
#define PIE_SIZE CGFloat_(34)
|
||||
|
||||
@implementation DTPieProgressIndicator
|
||||
{
|
||||
float _progressPercent;
|
||||
UIColor *_color;
|
||||
}
|
||||
|
||||
+ (DTPieProgressIndicator *)pieProgressIndicator
|
||||
{
|
||||
return [[DTPieProgressIndicator alloc] initWithFrame:CGRectMake(0, 0, PIE_SIZE, PIE_SIZE)];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self)
|
||||
{
|
||||
self.contentMode = UIViewContentModeRedraw;
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
[super awakeFromNib];
|
||||
|
||||
self.contentMode = UIViewContentModeRedraw;
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
// Drawing code
|
||||
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
||||
|
||||
if (_color)
|
||||
{
|
||||
[_color set];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self.tintColor set];
|
||||
}
|
||||
|
||||
CGContextBeginTransparencyLayer(ctx, NULL);
|
||||
|
||||
CGFloat smallerDimension = MIN(self.bounds.size.width-CGFloat_(6), self.bounds.size.height-CGFloat_(6));
|
||||
CGRect drawRect = CGRectMake(round(CGRectGetMidX(self.bounds)-smallerDimension/CGFloat_(2)), round(CGRectGetMidY(self.bounds)-smallerDimension/CGFloat_(2)), smallerDimension, smallerDimension);
|
||||
|
||||
CGContextSetLineWidth(ctx, CGFloat_(3));
|
||||
CGContextStrokeEllipseInRect(ctx, drawRect);
|
||||
|
||||
// enough percent to draw
|
||||
if (_progressPercent > 0.1f)
|
||||
{
|
||||
CGPoint center = CGPointMake(CGRectGetMidX(drawRect), CGRectGetMidY(drawRect));
|
||||
CGFloat radius = center.x - drawRect.origin.x;
|
||||
CGFloat angle = CGFloat_(_progressPercent) * CGFloat_(2.0 * M_PI);
|
||||
|
||||
CGContextMoveToPoint(ctx, center.x, center.y);
|
||||
CGContextAddArc(ctx, center.x, center.y, radius, CGFloat_(-M_PI_2), angle-CGFloat_(M_PI_2), 0);
|
||||
CGContextAddLineToPoint(ctx, center.x, center.y);
|
||||
|
||||
CGContextFillPath(ctx);
|
||||
}
|
||||
|
||||
CGContextEndTransparencyLayer(ctx);
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange
|
||||
{
|
||||
[super tintColorDidChange];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
#pragma mark Properties
|
||||
|
||||
- (void)setProgressPercent:(float)progressPercent
|
||||
{
|
||||
if (_progressPercent != progressPercent)
|
||||
{
|
||||
_progressPercent = progressPercent;
|
||||
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setColor:(UIColor *)color
|
||||
{
|
||||
if (_color != color)
|
||||
{
|
||||
_color = color;
|
||||
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,93 @@
|
||||
//
|
||||
// DTSmartPagingScrollView.h
|
||||
// DTSmartPhotoView
|
||||
//
|
||||
// Created by Stefan Gugarel on 5/11/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Availability.h>
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <DTFoundation/DTWeakSupport.h>
|
||||
|
||||
@class DTSmartPagingScrollView;
|
||||
|
||||
|
||||
/**
|
||||
Protocol for providing pages to <DTSmartPagingScrollView>
|
||||
*/
|
||||
@protocol DTSmartPagingScrollViewDatasource <NSObject>
|
||||
|
||||
/**
|
||||
The number of pages for the <DTSmartPagingScrollView>
|
||||
@param smartPagingScrollView The scroll view asking
|
||||
@returns The number of pages
|
||||
*/
|
||||
- (NSUInteger)numberOfPagesInSmartPagingScrollView:(DTSmartPagingScrollView *)smartPagingScrollView;
|
||||
|
||||
/**
|
||||
Method to provide UIViews to be used for the pages
|
||||
|
||||
The frame of the passed view will be adjusted to the page size of the scroll view
|
||||
@param smartPagingScrollView The scroll view asking
|
||||
@param index The index of the page to provide
|
||||
@returns The view to use for the given page index.
|
||||
*/
|
||||
- (UIView *)smartPagingScrollView:(DTSmartPagingScrollView *)smartPagingScrollView viewForPageAtIndex:(NSUInteger)index;
|
||||
|
||||
@optional
|
||||
/**
|
||||
The number of pages for the <DTSmartPagingScrollView>
|
||||
@param smartPagingScrollView The scroll view asking
|
||||
@param index The index of the page
|
||||
*/
|
||||
- (void)smartPagingScrollView:(DTSmartPagingScrollView *)smartPagingScrollView didScrollToPageAtIndex:(NSUInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
A scroll view that automatically manages a set of pages
|
||||
*/
|
||||
@interface DTSmartPagingScrollView : UIScrollView <UIScrollViewDelegate>
|
||||
|
||||
/**
|
||||
The page data source for the receiver
|
||||
*/
|
||||
@property (nonatomic, DT_WEAK_PROPERTY) IBOutlet id <DTSmartPagingScrollViewDatasource> pageDatasource;
|
||||
|
||||
/**
|
||||
The current page index visible on the receiver
|
||||
*/
|
||||
@property (nonatomic, assign) NSUInteger currentPageIndex;
|
||||
|
||||
/**
|
||||
Reloads the pages from the datasource
|
||||
*/
|
||||
- (void)reloadData;
|
||||
|
||||
/**
|
||||
The range of indexes of the currently visible pages
|
||||
*/
|
||||
- (NSRange)rangeOfVisiblePages;
|
||||
|
||||
/**
|
||||
Scroll the receiver to the given page index
|
||||
@param page The index of the page to move to
|
||||
@param animated Whether the move should be animated
|
||||
*/
|
||||
- (void)scrollToPage:(NSInteger)page animated:(BOOL)animated;
|
||||
|
||||
/**
|
||||
Get a view for a specified index
|
||||
@param index The index of the view to retrieve
|
||||
*/
|
||||
- (UIView *)viewForIndex:(NSUInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,280 @@
|
||||
//
|
||||
// DTSmartPagingScrollView.m
|
||||
// DTSmartPhotoView
|
||||
//
|
||||
// Created by Stefan Gugarel on 5/11/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DTSmartPagingScrollView.h"
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "DTCoreGraphicsUtils.h"
|
||||
|
||||
@interface DTSmartPagingScrollView ()
|
||||
|
||||
- (void)_setupVisiblePageViews;
|
||||
- (CGRect)frameForPageViewAtIndex:(NSUInteger)index;
|
||||
- (void)_updateCurrentPage;
|
||||
- (void)_commonInit;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation DTSmartPagingScrollView
|
||||
{
|
||||
DT_WEAK_VARIABLE id <DTSmartPagingScrollViewDatasource> _pageDatasource;
|
||||
|
||||
NSUInteger _numberOfPages;
|
||||
NSMutableDictionary *_viewsByPage;
|
||||
|
||||
NSMutableSet *_visiblePageViews;
|
||||
|
||||
NSUInteger _currentPageIndex;
|
||||
|
||||
BOOL _firstLayoutDone;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self)
|
||||
{
|
||||
[self _commonInit];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self)
|
||||
{
|
||||
[self _commonInit];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_commonInit
|
||||
{
|
||||
#if !TARGET_OS_TV
|
||||
self.pagingEnabled = YES;
|
||||
#endif
|
||||
// no indicators because zooming subview has there
|
||||
self.showsVerticalScrollIndicator = NO;
|
||||
self.showsHorizontalScrollIndicator = NO;
|
||||
|
||||
_viewsByPage = [[NSMutableDictionary alloc] init];
|
||||
_visiblePageViews = [[NSMutableSet alloc] init];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
if (!_numberOfPages)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.dragging)
|
||||
{
|
||||
// perform update right away
|
||||
[self _updateCurrentPage];
|
||||
}
|
||||
|
||||
// Note: first content size plus page so that the visible view is properly shown
|
||||
CGSize neededContentSize = CGSizeMake(self.bounds.size.width * _numberOfPages, self.bounds.size.height);
|
||||
|
||||
if (!CGSizeEqualToSize(neededContentSize, self.contentSize))
|
||||
{
|
||||
// frame changed
|
||||
self.contentSize = neededContentSize;
|
||||
|
||||
[self scrollToPage:_currentPageIndex animated:NO];
|
||||
}
|
||||
|
||||
[self _setupVisiblePageViews];
|
||||
}
|
||||
|
||||
- (void)_updateCurrentPage
|
||||
{
|
||||
NSUInteger newPageIndex = round(self.contentOffset.x / self.frame.size.width);
|
||||
|
||||
if (_currentPageIndex != newPageIndex)
|
||||
{
|
||||
[self willChangeValueForKey:@"currentPageIndex"];
|
||||
_currentPageIndex = newPageIndex;
|
||||
|
||||
if ([_pageDatasource respondsToSelector:@selector(smartPagingScrollView:didScrollToPageAtIndex:)])
|
||||
{
|
||||
[_pageDatasource smartPagingScrollView:self didScrollToPageAtIndex:_currentPageIndex];
|
||||
}
|
||||
|
||||
[self didChangeValueForKey:@"currentPageIndex"];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSRange)rangeOfVisiblePages
|
||||
{
|
||||
CGFloat position = self.contentOffset.x / self.bounds.size.width;
|
||||
|
||||
NSInteger firstVisibleIndex = MAX(CGFloat_(0), floor(position));
|
||||
NSInteger lastVisibleIndex = MIN(ceil(position), CGFloat_(_numberOfPages-1));
|
||||
|
||||
// check if right page is really visible
|
||||
CGRect rightFrame = [self frameForPageViewAtIndex:lastVisibleIndex];
|
||||
|
||||
if (!CGRectIntersectsRect(rightFrame, self.bounds))
|
||||
{
|
||||
lastVisibleIndex--;
|
||||
}
|
||||
|
||||
// check if left page is really visible
|
||||
CGRect leftFrame = [self frameForPageViewAtIndex:firstVisibleIndex];
|
||||
|
||||
if (!CGRectIntersectsRect(leftFrame, self.bounds))
|
||||
{
|
||||
firstVisibleIndex++;
|
||||
}
|
||||
|
||||
return NSMakeRange(firstVisibleIndex, lastVisibleIndex - firstVisibleIndex + 1);
|
||||
}
|
||||
|
||||
- (UIView *)viewForIndex:(NSUInteger)index
|
||||
{
|
||||
NSNumber *cacheKey = [NSNumber numberWithUnsignedInteger:index];
|
||||
|
||||
UIView *view = [_viewsByPage objectForKey:cacheKey];
|
||||
|
||||
if (view)
|
||||
{
|
||||
// got cached view
|
||||
return view;
|
||||
}
|
||||
|
||||
// get view from datasource
|
||||
view = [_pageDatasource smartPagingScrollView:self viewForPageAtIndex:index];
|
||||
|
||||
// cache it
|
||||
[_viewsByPage setObject:view forKey:cacheKey];
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
- (void)_setupVisiblePageViews
|
||||
{
|
||||
NSRange visibleRange = [self rangeOfVisiblePages];
|
||||
|
||||
[CATransaction begin];
|
||||
[CATransaction setDisableActions:NO];
|
||||
|
||||
NSMutableSet *newVisiblePageViews = [NSMutableSet set];
|
||||
|
||||
for (NSInteger idx = visibleRange.location; idx<NSMaxRange(visibleRange); idx++)
|
||||
{
|
||||
UIView *view = [self viewForIndex:idx];
|
||||
|
||||
CGRect viewFrame = [self frameForPageViewAtIndex:idx];
|
||||
view.tag = (1000 + idx);
|
||||
|
||||
if (view.superview!=self)
|
||||
{
|
||||
[self insertSubview:view atIndex:0];
|
||||
}
|
||||
|
||||
if (!CGRectEqualToRect(view.frame, viewFrame))
|
||||
{
|
||||
view.frame = viewFrame;
|
||||
}
|
||||
|
||||
[newVisiblePageViews addObject:view];
|
||||
}
|
||||
|
||||
// remove pages that are no longer visible
|
||||
|
||||
NSMutableSet *toBeRemoved = _visiblePageViews;
|
||||
[toBeRemoved minusSet:newVisiblePageViews];
|
||||
_visiblePageViews = newVisiblePageViews;
|
||||
|
||||
for (UIView *view in toBeRemoved)
|
||||
{
|
||||
[view removeFromSuperview];
|
||||
|
||||
NSNumber *cacheKey = [NSNumber numberWithUnsignedInteger:(view.tag - 1000)];
|
||||
[_viewsByPage removeObjectForKey:cacheKey];
|
||||
}
|
||||
[CATransaction commit];
|
||||
}
|
||||
|
||||
- (CGRect)frameForPageViewAtIndex:(NSUInteger)index
|
||||
{
|
||||
CGRect frame = self.bounds;
|
||||
frame.origin.x = index * frame.size.width;
|
||||
|
||||
frame = CGRectInset(frame, CGFloat_(10), CGFloat_(0));
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
- (void)scrollToPage:(NSInteger)page animated:(BOOL)animated
|
||||
{
|
||||
CGRect pageRect = CGRectMake(CGFloat_(page) * self.frame.size.width, CGFloat_(0), self.frame.size.width, self.frame.size.height);
|
||||
[self scrollRectToVisible:pageRect animated:animated];
|
||||
}
|
||||
|
||||
- (void)reloadData
|
||||
{
|
||||
if (!_pageDatasource)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// clean up
|
||||
for (UIView *oneView in _visiblePageViews)
|
||||
{
|
||||
[oneView removeFromSuperview];
|
||||
}
|
||||
|
||||
[_visiblePageViews removeAllObjects];
|
||||
[_viewsByPage removeAllObjects];
|
||||
|
||||
// load
|
||||
_numberOfPages = [_pageDatasource numberOfPagesInSmartPagingScrollView:self];
|
||||
|
||||
// make sure we stay in valid range
|
||||
_currentPageIndex = MAX(0, MIN(_currentPageIndex, _numberOfPages-1));
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setPageDatasource:(id<DTSmartPagingScrollViewDatasource>)pageDatasource
|
||||
{
|
||||
if (_pageDatasource != pageDatasource)
|
||||
{
|
||||
_pageDatasource = pageDatasource;
|
||||
|
||||
// refresh parameters
|
||||
[self reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setCurrentPageIndex:(NSUInteger)currentPageIndex
|
||||
{
|
||||
if (_currentPageIndex != currentPageIndex)
|
||||
{
|
||||
_currentPageIndex = currentPageIndex;
|
||||
|
||||
[self scrollToPage:_currentPageIndex animated:NO];
|
||||
}
|
||||
}
|
||||
|
||||
@synthesize pageDatasource = _pageDatasource;
|
||||
@synthesize currentPageIndex = _currentPageIndex;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// DTTiledLayerWithoutFade.h
|
||||
// DTRichTextEditor
|
||||
//
|
||||
// Created by Oliver Drobnik on 8/24/11.
|
||||
// Copyright 2011 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Availability.h>
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
/**
|
||||
Simple subclass of `CATiledLayer` that does not fade in drawn tiles.
|
||||
*/
|
||||
|
||||
@interface DTTiledLayerWithoutFade : CATiledLayer
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// DTTiledLayerWithoutFade.m
|
||||
// DTRichTextEditor
|
||||
//
|
||||
// Created by Oliver Drobnik on 8/24/11.
|
||||
// Copyright 2011 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DTTiledLayerWithoutFade.h"
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@implementation DTTiledLayerWithoutFade
|
||||
|
||||
+ (CFTimeInterval)fadeDuration
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
+ (BOOL)shouldDrawOnMainThread
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// NSURL+DTAppLinks.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 11/25/11.
|
||||
// Copyright (c) 2011 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Availability.h>
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
|
||||
/** A collection of category extensions for `NSURL` that provide direct access to built-in app capabilities.
|
||||
|
||||
For example: Open the app store on the page for the app
|
||||
|
||||
NSURL *appURL = [NSURL appStoreURLforApplicationIdentifier:@"463623298"];
|
||||
[[UIApplication sharedApplication] openURL:appURL];
|
||||
*/
|
||||
|
||||
@interface NSURL (DTAppLinks)
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Mobile App Store Pages
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/** Returns the URL to open the mobile app store on the app's page.
|
||||
|
||||
URL construction as described in [QA1629](https://developer.apple.com/library/ios/#qa/qa2008/qa1629.html). Test and found to be opening the app store app directly even without the itms: or itms-apps: scheme. This kind of URL can also be used to forward a link to the app to non-iOS devices.
|
||||
|
||||
@param identifier The application identifier that gets assigned to a new app when you add it to iTunes Connect.
|
||||
@return Returns the URL to the direct app store link
|
||||
*/
|
||||
+ (NSURL *)appStoreURLforApplicationIdentifier:(NSString *)identifier;
|
||||
|
||||
|
||||
/** Returns the URL to open the mobile app store on the app's review page.
|
||||
|
||||
The reviews page is a sub-page of the normal app landing page you get with appStoreURLforApplicationIdentifier:
|
||||
|
||||
@param identifier The application identifier that gets assigned to a new app when you add it to iTunes Connect.
|
||||
@return Returns the URL to the direct app store link
|
||||
*/
|
||||
+ (NSURL *)appStoreReviewURLForApplicationIdentifier:(NSString *)identifier;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// NSURL+DTAppLinks.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 11/25/11.
|
||||
// Copyright (c) 2011 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSURL+DTAppLinks.h"
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@implementation NSURL (DTAppLinks)
|
||||
|
||||
+ (NSURL *)appStoreURLforApplicationIdentifier:(NSString *)identifier
|
||||
{
|
||||
NSString *link = [NSString stringWithFormat:@"http://itunes.apple.com/us/app/id%@?mt=8", identifier];
|
||||
|
||||
return [NSURL URLWithString:link];
|
||||
}
|
||||
|
||||
+ (NSURL *)appStoreReviewURLForApplicationIdentifier:(NSString *)identifier
|
||||
{
|
||||
NSString *link = [NSString stringWithFormat:@"itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=%@", identifier];
|
||||
return [NSURL URLWithString:link];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// UIApplication+DTNetworkActivity.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 5/21/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Availability.h>
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
/**
|
||||
Enhancement for `UIApplication` to properly count active network sessions and show the network activity indicator whenever there are more than 0 active sessions.
|
||||
*/
|
||||
@interface UIApplication (DTNetworkActivity)
|
||||
|
||||
/**
|
||||
Increments the number of active network operations
|
||||
*/
|
||||
- (void)pushActiveNetworkOperation;
|
||||
|
||||
/**
|
||||
Decrements the number of active network operations
|
||||
*/
|
||||
- (void)popActiveNetworkOperation;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// UIApplication+DTNetworkActivity.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 5/21/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "UIApplication+DTNetworkActivity.h"
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
static NSUInteger __internalOperationCount = 0;
|
||||
|
||||
@implementation UIApplication (DTNetworkActivity)
|
||||
|
||||
- (void)pushActiveNetworkOperation
|
||||
{
|
||||
@synchronized(self)
|
||||
{
|
||||
__internalOperationCount++;
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
if (!self.isNetworkActivityIndicatorVisible && __internalOperationCount)
|
||||
{
|
||||
self.networkActivityIndicatorVisible = YES;
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
- (void)popActiveNetworkOperation
|
||||
{
|
||||
@synchronized(self)
|
||||
{
|
||||
if (__internalOperationCount==0)
|
||||
{
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
__internalOperationCount--;
|
||||
#if !TARGET_OS_TV
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (self.isNetworkActivityIndicatorVisible && !__internalOperationCount)
|
||||
{
|
||||
self.networkActivityIndicatorVisible = NO;
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,115 @@
|
||||
//
|
||||
// UIImage+DTFoundation.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 3/8/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Availability.h>
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
/**
|
||||
Methods to help with working with images.
|
||||
*/
|
||||
@interface UIImage (DTFoundation)
|
||||
|
||||
/**
|
||||
@name Generating Images
|
||||
*/
|
||||
|
||||
/**
|
||||
Creates an image filled with a solid color
|
||||
@param color The solid color that fills the image
|
||||
@param size The size of the image
|
||||
@returns The image filled with given color and given size
|
||||
*/
|
||||
+ (UIImage *)imageWithSolidColor:(UIColor *)color size:(CGSize)size;
|
||||
|
||||
|
||||
/**
|
||||
Creates an image filled with a tint color using the receiver as image mask. The resulting image ignores the receiver's color values and instead uses the alpha values combined with the passed color.
|
||||
@param color The color to use for tinting
|
||||
@returns A new image
|
||||
*/
|
||||
- (UIImage *)imageMaskedAndTintedWithColor:(UIColor *)color;
|
||||
|
||||
/**
|
||||
@name Loading from RemoteURLs
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
Creates and returns an image object synchronously by loading the image data from the specified URL and optionally caching it.
|
||||
|
||||
Useful values for cachePolicy are:
|
||||
|
||||
- NSURLRequestUseProtocolCachePolicy (default)
|
||||
- NSURLRequestReloadIgnoringLocalCacheData
|
||||
- NSURLRequestReturnCacheDataElseLoad
|
||||
- NSURLRequestReturnCacheDataDontLoad
|
||||
|
||||
@param URL The URL to load the image from
|
||||
@param cachePolicy The cache policy to apply.
|
||||
@param error An optional output parameter to return an error if the loading fails
|
||||
@returns The image object for the specified URL, or nil if the method could not load the specified image.
|
||||
*/
|
||||
+ (UIImage *)imageWithContentsOfURL:(NSURL *)URL cachePolicy:(NSURLRequestCachePolicy)cachePolicy error:(NSError **)error;
|
||||
|
||||
|
||||
/**
|
||||
@name Drawing
|
||||
*/
|
||||
|
||||
/**
|
||||
Mimicks the way images are drawn differently by UIImageView based on the set content mode.
|
||||
@param rect The rectangle to drawn in
|
||||
@param contentMode The content mode. Note that UIViewContentModeRedraw is treated the same as UIViewContentModeScaleToFill.
|
||||
*/
|
||||
- (void)drawInRect:(CGRect)rect withContentMode:(UIViewContentMode)contentMode;
|
||||
|
||||
/**
|
||||
@name Working with Tiles
|
||||
*/
|
||||
|
||||
/**
|
||||
Cuts out a tile at the given row and column
|
||||
|
||||
@param column The index of the column
|
||||
@param columns The total number of columns
|
||||
@param row The index of the row
|
||||
@param rows The total number of rows
|
||||
@returns The resulting image
|
||||
*/
|
||||
- (UIImage *)tileImageAtColumn:(NSUInteger)column ofColumns:(NSUInteger)columns row:(NSUInteger)row ofRows:(NSUInteger)rows;
|
||||
|
||||
/**
|
||||
Cuts out a tile at the given clip rect relative to the bounds
|
||||
|
||||
@param clipRect The clipping rect to extract
|
||||
@param bounds The bounds to which the clipRect is relative to
|
||||
@param scale The image scale
|
||||
@returns The resulting image
|
||||
*/
|
||||
- (UIImage *)tileImageInClipRect:(CGRect)clipRect inBounds:(CGRect)bounds scale:(CGFloat)scale;
|
||||
|
||||
|
||||
/**
|
||||
@name Modifying Images
|
||||
*/
|
||||
|
||||
/**
|
||||
Resizes the receiver to the given size.
|
||||
|
||||
@param newSize The target image size
|
||||
@returns The resulting image
|
||||
*/
|
||||
- (UIImage *)imageScaledToSize:(CGSize)newSize;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,358 @@
|
||||
//
|
||||
// UIImage+DTFoundation.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 3/8/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "UIImage+DTFoundation.h"
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "DTCoreGraphicsUtils.h"
|
||||
#import "DTLog.h"
|
||||
|
||||
@implementation UIImage (DTFoundation)
|
||||
|
||||
#pragma mark - Generating Images
|
||||
|
||||
+ (UIImage *)imageWithSolidColor:(UIColor *)color size:(CGSize)size
|
||||
{
|
||||
NSParameterAssert(color);
|
||||
NSAssert(!CGSizeEqualToSize(size, CGSizeZero), @"Size cannot be CGSizeZero");
|
||||
|
||||
CGRect rect = CGRectMake(0, 0, size.width, size.height);
|
||||
|
||||
// Create a context depending on given size
|
||||
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
|
||||
|
||||
// Fill it with your color
|
||||
[color setFill];
|
||||
UIRectFill(rect);
|
||||
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
- (UIImage *)imageMaskedAndTintedWithColor:(UIColor *)color
|
||||
{
|
||||
NSParameterAssert(color);
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
|
||||
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
||||
|
||||
CGRect bounds = (CGRect){CGPointZero, self.size};
|
||||
|
||||
// do a vertical flip so that image is correct
|
||||
CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, bounds.size.height);
|
||||
CGContextConcatCTM(ctx, flipVertical);
|
||||
|
||||
// create mask of image
|
||||
CGContextClipToMask(ctx, bounds, self.CGImage);
|
||||
|
||||
// fill with given color
|
||||
[color setFill];
|
||||
CGContextFillRect(ctx, bounds);
|
||||
|
||||
// get back new image
|
||||
UIImage *retImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return retImage;
|
||||
}
|
||||
|
||||
#pragma mark - Loading
|
||||
|
||||
+ (UIImage *)imageWithContentsOfURL:(NSURL *)URL cachePolicy:(NSURLRequestCachePolicy)cachePolicy error:(NSError **)error
|
||||
{
|
||||
NSURLRequest *request = [NSURLRequest requestWithURL:URL cachePolicy:cachePolicy timeoutInterval:10.0];
|
||||
|
||||
NSCachedURLResponse *cacheResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
|
||||
|
||||
__block NSData *data;
|
||||
__block NSError *internalError;
|
||||
|
||||
if (cacheResponse)
|
||||
{
|
||||
DTLogDebug(@"cache hit for %@", [URL absoluteString]);
|
||||
}
|
||||
else
|
||||
{
|
||||
DTLogDebug(@"cache fail for %@", [URL absoluteString]);
|
||||
}
|
||||
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0
|
||||
|
||||
NSURLResponse *response;
|
||||
data = [NSURLConnection sendSynchronousRequest:request
|
||||
returningResponse:&response
|
||||
error:error];
|
||||
#else
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
|
||||
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *responseData, NSURLResponse *response, NSError *responseError) {
|
||||
|
||||
data = responseData;
|
||||
internalError = responseError;
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}] resume];
|
||||
|
||||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
||||
#endif
|
||||
|
||||
if (!data)
|
||||
{
|
||||
DTLogError(@"Error loading image at %@", URL);
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
*error = internalError;
|
||||
}
|
||||
|
||||
UIImage *image = [UIImage imageWithData:data];
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Drawing
|
||||
|
||||
- (void)drawInRect:(CGRect)rect withContentMode:(UIViewContentMode)contentMode
|
||||
{
|
||||
CGRect drawRect;
|
||||
CGSize size = self.size;
|
||||
|
||||
switch (contentMode)
|
||||
{
|
||||
case UIViewContentModeRedraw:
|
||||
case UIViewContentModeScaleToFill:
|
||||
{
|
||||
// nothing to do
|
||||
[self drawInRect:rect];
|
||||
return;
|
||||
}
|
||||
|
||||
case UIViewContentModeScaleAspectFit:
|
||||
{
|
||||
CGFloat factor;
|
||||
|
||||
if (size.width<size.height)
|
||||
{
|
||||
factor = rect.size.height / size.height;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
factor = rect.size.width / size.width;
|
||||
}
|
||||
|
||||
|
||||
size.width = round(size.width * factor);
|
||||
size.height = round(size.height * factor);
|
||||
|
||||
// otherwise same as center
|
||||
drawRect = CGRectMake(round(CGRectGetMidX(rect)-size.width/CGFloat_(2)),
|
||||
round(CGRectGetMidY(rect)-size.height/CGFloat_(2)),
|
||||
size.width,
|
||||
size.height);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case UIViewContentModeScaleAspectFill:
|
||||
{
|
||||
CGFloat factor;
|
||||
|
||||
if (size.width<size.height)
|
||||
{
|
||||
factor = rect.size.width / size.width;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
factor = rect.size.height / size.height;
|
||||
}
|
||||
|
||||
|
||||
size.width = round(size.width * factor);
|
||||
size.height = round(size.height * factor);
|
||||
|
||||
// otherwise same as center
|
||||
drawRect = CGRectMake(round(CGRectGetMidX(rect)-size.width/CGFloat_(2)),
|
||||
round(CGRectGetMidY(rect)-size.height/CGFloat_(2)),
|
||||
size.width,
|
||||
size.height);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case UIViewContentModeCenter:
|
||||
{
|
||||
drawRect = CGRectMake(round(CGRectGetMidX(rect)-size.width/CGFloat_(2)),
|
||||
round(CGRectGetMidY(rect)-size.height/CGFloat_(2)),
|
||||
size.width,
|
||||
size.height);
|
||||
break;
|
||||
}
|
||||
|
||||
case UIViewContentModeTop:
|
||||
{
|
||||
drawRect = CGRectMake(round(CGRectGetMidX(rect)-size.width/CGFloat_(2)),
|
||||
rect.origin.y-size.height,
|
||||
size.width,
|
||||
size.height);
|
||||
break;
|
||||
}
|
||||
|
||||
case UIViewContentModeBottom:
|
||||
{
|
||||
drawRect = CGRectMake(round(CGRectGetMidX(rect)-size.width/CGFloat_(2)),
|
||||
rect.origin.y-size.height,
|
||||
size.width,
|
||||
size.height);
|
||||
break;
|
||||
}
|
||||
|
||||
case UIViewContentModeLeft:
|
||||
{
|
||||
drawRect = CGRectMake(rect.origin.x,
|
||||
round(CGRectGetMidY(rect)-size.height/CGFloat_(2)),
|
||||
size.width,
|
||||
size.height);
|
||||
break;
|
||||
}
|
||||
|
||||
case UIViewContentModeRight:
|
||||
{
|
||||
drawRect = CGRectMake(CGRectGetMaxX(rect)-size.width,
|
||||
round(CGRectGetMidY(rect)-size.height/CGFloat_(2)),
|
||||
size.width,
|
||||
size.height);
|
||||
break;
|
||||
}
|
||||
|
||||
case UIViewContentModeTopLeft:
|
||||
{
|
||||
drawRect = CGRectMake(rect.origin.x,
|
||||
rect.origin.y,
|
||||
size.width,
|
||||
size.height);
|
||||
break;
|
||||
}
|
||||
|
||||
case UIViewContentModeTopRight:
|
||||
{
|
||||
drawRect = CGRectMake(CGRectGetMaxX(rect)-size.width,
|
||||
rect.origin.y,
|
||||
size.width,
|
||||
size.height);
|
||||
break;
|
||||
}
|
||||
|
||||
case UIViewContentModeBottomLeft:
|
||||
{
|
||||
drawRect = CGRectMake(rect.origin.x,
|
||||
CGRectGetMaxY(rect)-size.height,
|
||||
size.width,
|
||||
size.height);
|
||||
break;
|
||||
}
|
||||
|
||||
case UIViewContentModeBottomRight:
|
||||
{
|
||||
drawRect = CGRectMake(CGRectGetMaxX(rect)-size.width,
|
||||
CGRectGetMaxY(rect)-size.height,
|
||||
size.width,
|
||||
size.height);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextSaveGState(context);
|
||||
|
||||
// clip to rect
|
||||
CGContextAddRect(context, rect);
|
||||
CGContextClip(context);
|
||||
|
||||
// draw
|
||||
[self drawInRect:drawRect];
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
#pragma mark Tiles
|
||||
- (UIImage *)tileImageAtColumn:(NSUInteger)column ofColumns:(NSUInteger)columns row:(NSUInteger)row ofRows:(NSUInteger)rows
|
||||
{
|
||||
// calculate resulting size
|
||||
CGFloat retWidth = round(self.size.width / CGFloat_(columns));
|
||||
CGFloat retHeight = round(self.size.height / CGFloat_(rows));
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(CGSizeMake(retWidth, retHeight), YES, self.scale);
|
||||
|
||||
// move the context such that the left/top of the tile is at the left/top of the context
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextTranslateCTM(context, -retWidth*column, -retHeight*row);
|
||||
|
||||
// draw the image
|
||||
[self drawAtPoint:CGPointZero];
|
||||
|
||||
UIImage *retImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return retImage;
|
||||
}
|
||||
|
||||
- (UIImage *)tileImageInClipRect:(CGRect)clipRect inBounds:(CGRect)bounds scale:(CGFloat)scale
|
||||
{
|
||||
UIGraphicsBeginImageContextWithOptions(clipRect.size, YES, scale);
|
||||
|
||||
CGFloat zoom = self.size.width / bounds.size.width;
|
||||
|
||||
// this is the part from the origin image
|
||||
CGRect clipInOriginal = clipRect;
|
||||
clipInOriginal.origin.x *= zoom;
|
||||
clipInOriginal.origin.y *= zoom;
|
||||
clipInOriginal.size.width *= zoom;
|
||||
clipInOriginal.size.height *= zoom;
|
||||
|
||||
// move the context such that the left/top of the tile is at the left/top of the context
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextTranslateCTM(context, -clipRect.origin.x, -clipRect.origin.y);
|
||||
CGContextScaleCTM(context, CGFloat_(1)/zoom, CGFloat_(1)/zoom);
|
||||
|
||||
// draw the image
|
||||
[self drawAtPoint:CGPointZero];
|
||||
|
||||
UIImage *retImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return retImage;
|
||||
}
|
||||
|
||||
#pragma mark Modifying Images
|
||||
|
||||
- (UIImage *)imageScaledToSize:(CGSize)newSize
|
||||
{
|
||||
UIGraphicsBeginImageContextWithOptions(newSize, NO, self.scale);
|
||||
[self drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
|
||||
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// UIScreen+DTFoundation.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Johannes Marbach on 16.10.17.
|
||||
// Copyright © 2017 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
/** DTFoundation enhancements for `UIView` */
|
||||
|
||||
#import <Availability.h>
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_TV && !TARGET_OS_WATCH
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface UIScreen (DTFoundation)
|
||||
|
||||
- (UIInterfaceOrientation)orientation;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// UIScreen+DTFoundation.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Johannes Marbach on 16.10.17.
|
||||
// Copyright © 2017 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "UIScreen+DTFoundation.h"
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_TV && !TARGET_OS_WATCH
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@implementation UIScreen (DTFoundation)
|
||||
|
||||
- (UIInterfaceOrientation)orientation {
|
||||
CGPoint point = [self.coordinateSpace convertPoint:CGPointZero toCoordinateSpace:self.fixedCoordinateSpace];
|
||||
if (point.x == 0 && point.y == 0) {
|
||||
return UIInterfaceOrientationPortrait;
|
||||
} else if (point.x != 0 && point.y != 0) {
|
||||
return UIInterfaceOrientationPortraitUpsideDown;
|
||||
} else if (point.x == 0 && point.y != 0) {
|
||||
return UIInterfaceOrientationLandscapeLeft;
|
||||
} else if (point.x != 0 && point.y == 0) {
|
||||
return UIInterfaceOrientationLandscapeRight;
|
||||
} else {
|
||||
return UIInterfaceOrientationUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// UIView+DTFoundation.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 12/23/11.
|
||||
// Copyright (c) 2011 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
/** DTFoundation enhancements for `UIView` */
|
||||
|
||||
#import <Availability.h>
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface UIView (DTFoundation)
|
||||
|
||||
/**---------------------------------------------------------------------------------------
|
||||
* @name Getting Snapshot Images
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/** Creates a snapshot of the receiver.
|
||||
|
||||
@return Returns a bitmap image with the same contents and dimensions as the receiver.
|
||||
*/
|
||||
- (UIImage * _Nonnull)snapshotImage;
|
||||
|
||||
|
||||
/**---------------------------------------------------------------------------------------
|
||||
* @name Rounded Corners
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/** Sets the corner attributes of the receiver's layer.
|
||||
|
||||
The advantage of using this method is that you do not need to import the QuartzCore headers just for setting the corners.
|
||||
@param radius The corner radius.
|
||||
@param width The width of the border line.
|
||||
@param color The color to be used for the border line. Can be `nil` to leave it unchanged.
|
||||
*/
|
||||
- (void)setRoundedCornersWithRadius:(CGFloat)radius width:(CGFloat)width color:(UIColor * _Nullable)color;
|
||||
|
||||
|
||||
/**---------------------------------------------------------------------------------------
|
||||
* @name Shadows
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/** Adds a layer-based shadow to the receiver.
|
||||
|
||||
The advantage of using this method is that you do not need to import the QuartzCore headers just for adding the shadow.
|
||||
Layer-based shadows are properly combined for views that are on the same superview. This does not add a shadow path,
|
||||
you should call updateShadowPathToBounds whenever the receiver's bounds change and also after setting the initial frame.
|
||||
@warn Disables clipping to bounds because this would also clip off the shadow.
|
||||
@param color The shadow color. Can be `nil` for default black.
|
||||
@param alpha The alpha value of the shadow.
|
||||
@param radius The amount that the shadow is blurred.
|
||||
@param offset The offset of the shadow
|
||||
@see updateShadowPathToBounds:withDuration:
|
||||
*/
|
||||
- (void)addShadowWithColor:(UIColor * _Nullable)color alpha:(CGFloat)alpha radius:(CGFloat)radius offset:(CGSize)offset;
|
||||
|
||||
|
||||
/** sets the shadow path to fit the receiver's bounds.
|
||||
|
||||
This should be called whenever the receiver's bounds change, or else the shadow detaches.
|
||||
@warn Since this a CALayer property it needs to be explicitly animated, for example in the willRotate ... method of a `UIViewController`.
|
||||
@param bounds The new bounds of the shadow path
|
||||
@param duration The animation duration. Specify a duration of 0 to not do an animation
|
||||
*/
|
||||
- (void)updateShadowPathToBounds:(CGRect)bounds withDuration:(NSTimeInterval)duration;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// UIView+DTFoundation.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 12/23/11.
|
||||
// Copyright (c) 2011 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "UIView+DTFoundation.h"
|
||||
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NSString *shadowContext = @"Shadow";
|
||||
|
||||
@implementation UIView (DTFoundation)
|
||||
|
||||
- (UIImage *)snapshotImage
|
||||
{
|
||||
NSAssert(self.bounds.size.height > 0 && self.bounds.size.width > 0, @"Trying to create a snapshot from a zero size view");
|
||||
|
||||
UIGraphicsBeginImageContext(self.bounds.size);
|
||||
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
- (void)setRoundedCornersWithRadius:(CGFloat)radius width:(CGFloat)width color:(UIColor * _Nullable)color
|
||||
{
|
||||
self.clipsToBounds = YES;
|
||||
self.layer.cornerRadius = radius;
|
||||
self.layer.borderWidth = width;
|
||||
|
||||
if (color)
|
||||
{
|
||||
self.layer.borderColor = color.CGColor;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addShadowWithColor:(UIColor * _Nullable)color alpha:(CGFloat)alpha radius:(CGFloat)radius offset:(CGSize)offset
|
||||
{
|
||||
self.layer.shadowOpacity = alpha;
|
||||
self.layer.shadowRadius = radius;
|
||||
self.layer.shadowOffset = offset;
|
||||
|
||||
if (color)
|
||||
{
|
||||
self.layer.shadowColor = [color CGColor];
|
||||
}
|
||||
|
||||
// cannot have masking
|
||||
self.layer.masksToBounds = NO;
|
||||
}
|
||||
|
||||
- (void)updateShadowPathToBounds:(CGRect)bounds withDuration:(NSTimeInterval)duration
|
||||
{
|
||||
CGPathRef oldPath = self.layer.shadowPath;
|
||||
CGPathRef newPath = CGPathCreateWithRect(bounds, NULL);
|
||||
|
||||
if (oldPath && duration>0)
|
||||
{
|
||||
CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:@"shadowPath"];
|
||||
theAnimation.duration = duration;
|
||||
theAnimation.fromValue = (__bridge id)oldPath;
|
||||
theAnimation.toValue = (__bridge id)newPath;
|
||||
theAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
|
||||
[self.layer addAnimation:theAnimation forKey:@"shadowPath"];
|
||||
}
|
||||
|
||||
self.layer.shadowPath = newPath;
|
||||
|
||||
CGPathRelease(newPath);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user