Epub阅读器0.0.1

This commit is contained in:
shen
2026-05-21 19:40:51 +08:00
commit daa36d8fe7
559 changed files with 106266 additions and 0 deletions
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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