Epub阅读器0.0.1
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// DTBase64Coding.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 04.03.13.
|
||||
// Copyright (c) 2013 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
Utility class for encoding and decoding data in base64 format.
|
||||
|
||||
This was formerly a category on `NSData` but since Matt Gallagher's category has become so enormously popular people where reporting more and more conflicts. Thus we decided to move it into a properly named class.
|
||||
|
||||
Since all methods are class methods you never need to actually initialize it, doing so will raises a `DTAbstractClassException`.
|
||||
*/
|
||||
|
||||
@interface DTBase64Coding : NSObject
|
||||
|
||||
/**
|
||||
Encoding and Decoding
|
||||
*/
|
||||
|
||||
/**
|
||||
Encodes data as base64 string.
|
||||
@param data The data to encode
|
||||
@returns The encoded string
|
||||
*/
|
||||
+ (NSString *)stringByEncodingData:(NSData *)data;
|
||||
|
||||
/**
|
||||
Encodes data as base64 string.
|
||||
@param string The string with data encoded in base64 format
|
||||
@returns data The decoded data
|
||||
*/
|
||||
+ (NSData *)dataByDecodingString:(NSString *)string;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,296 @@
|
||||
//
|
||||
// DTBase64Coding.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Original code from NSData+Base64.m
|
||||
//
|
||||
// Created by Matt Gallagher on 2009/06/03.
|
||||
// Copyright 2009 Matt Gallagher. All rights reserved.
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software. Permission is granted to anyone to
|
||||
// use this software for any purpose, including commercial applications, and to
|
||||
// alter it and redistribute it freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source
|
||||
// distribution.
|
||||
|
||||
|
||||
#import "DTBase64Coding.h"
|
||||
|
||||
|
||||
// Function Prototypes
|
||||
void *DT__NewBase64Decode(const char *inputBuffer, size_t length, size_t *outputLength);
|
||||
char *DT__NewBase64Encode(const void *buffer, size_t length, bool separateLines, size_t *outputLength);
|
||||
|
||||
//
|
||||
// Mapping from 6 bit pattern to ASCII character.
|
||||
//
|
||||
static unsigned char base64EncodeLookup[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
//
|
||||
// Definition for "masked-out" areas of the base64DecodeLookup mapping
|
||||
//
|
||||
#define xx 65
|
||||
|
||||
//
|
||||
// Mapping from ASCII character to 6 bit pattern.
|
||||
//
|
||||
static unsigned char base64DecodeLookup[256] =
|
||||
{
|
||||
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
|
||||
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
|
||||
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 62, xx, xx, xx, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, xx, xx, xx, xx, xx, xx,
|
||||
xx, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, xx, xx, xx, xx, xx,
|
||||
xx, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, xx, xx, xx, xx, xx,
|
||||
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
|
||||
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
|
||||
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
|
||||
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
|
||||
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
|
||||
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
|
||||
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
|
||||
xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
|
||||
};
|
||||
|
||||
//
|
||||
// Fundamental sizes of the binary and base64 encode/decode units in bytes
|
||||
//
|
||||
#define BINARY_UNIT_SIZE 3
|
||||
#define BASE64_UNIT_SIZE 4
|
||||
|
||||
//
|
||||
// NewBase64Decode
|
||||
//
|
||||
// Decodes the base64 ASCII string in the inputBuffer to a newly malloced
|
||||
// output buffer.
|
||||
//
|
||||
// inputBuffer - the source ASCII string for the decode
|
||||
// length - the length of the string or -1 (to specify strlen should be used)
|
||||
// outputLength - if not-NULL, on output will contain the decoded length
|
||||
//
|
||||
// returns the decoded buffer. Must be free'd by caller. Length is given by
|
||||
// outputLength.
|
||||
//
|
||||
void *DT__NewBase64Decode ( const char *inputBuffer, size_t length, size_t *outputLength)
|
||||
{
|
||||
if ((long)length == -1)
|
||||
{
|
||||
length = strlen(inputBuffer);
|
||||
}
|
||||
|
||||
size_t outputBufferSize =
|
||||
((length+BASE64_UNIT_SIZE-1) / BASE64_UNIT_SIZE) * BINARY_UNIT_SIZE;
|
||||
unsigned char *outputBuffer = (unsigned char *)malloc(outputBufferSize);
|
||||
|
||||
size_t i = 0;
|
||||
size_t j = 0;
|
||||
while (i < length)
|
||||
{
|
||||
//
|
||||
// Accumulate 4 valid characters (ignore everything else)
|
||||
//
|
||||
unsigned char accumulated[BASE64_UNIT_SIZE];
|
||||
size_t accumulateIndex = 0;
|
||||
while (i < length)
|
||||
{
|
||||
unsigned char decode = base64DecodeLookup[(int) inputBuffer[i++]];
|
||||
if (decode != xx)
|
||||
{
|
||||
accumulated[accumulateIndex] = decode;
|
||||
accumulateIndex++;
|
||||
|
||||
if (accumulateIndex == BASE64_UNIT_SIZE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Store the 6 bits from each of the 4 characters as 3 bytes
|
||||
//
|
||||
// (Uses improved bounds checking suggested by Alexandre Colucci)
|
||||
//
|
||||
if(accumulateIndex >= 2)
|
||||
outputBuffer[j] = (unsigned char)((accumulated[0] << 2) | (accumulated[1] >> 4));
|
||||
if(accumulateIndex >= 3)
|
||||
outputBuffer[j + 1] = (unsigned char)((accumulated[1] << 4) | (accumulated[2] >> 2));
|
||||
if(accumulateIndex >= 4)
|
||||
outputBuffer[j + 2] = (unsigned char)((accumulated[2] << 6) | accumulated[3]);
|
||||
j += accumulateIndex - 1;
|
||||
}
|
||||
|
||||
if (outputLength)
|
||||
{
|
||||
*outputLength = j;
|
||||
}
|
||||
return outputBuffer;
|
||||
}
|
||||
|
||||
//
|
||||
// NewBase64Encode
|
||||
//
|
||||
// Encodes the arbitrary data in the inputBuffer as base64 into a newly malloced
|
||||
// output buffer.
|
||||
//
|
||||
// inputBuffer - the source data for the encode
|
||||
// length - the length of the input in bytes
|
||||
// separateLines - if zero, no CR/LF characters will be added. Otherwise
|
||||
// a CR/LF pair will be added every 64 encoded chars.
|
||||
// outputLength - if not-NULL, on output will contain the encoded length
|
||||
// (not including terminating 0 char)
|
||||
//
|
||||
// returns the encoded buffer. Must be free'd by caller. Length is given by
|
||||
// outputLength.
|
||||
//
|
||||
char *DT__NewBase64Encode(const void *buffer, size_t length, bool separateLines, size_t *outputLength)
|
||||
{
|
||||
const unsigned char *inputBuffer = (const unsigned char *)buffer;
|
||||
|
||||
#define MAX_NUM_PADDING_CHARS 2
|
||||
#define OUTPUT_LINE_LENGTH 64
|
||||
#define INPUT_LINE_LENGTH ((OUTPUT_LINE_LENGTH / BASE64_UNIT_SIZE) * BINARY_UNIT_SIZE)
|
||||
#define CR_LF_SIZE 2
|
||||
|
||||
//
|
||||
// Byte accurate calculation of final buffer size
|
||||
//
|
||||
size_t outputBufferSize =
|
||||
((length / BINARY_UNIT_SIZE)
|
||||
+ ((length % BINARY_UNIT_SIZE) ? 1 : 0))
|
||||
* BASE64_UNIT_SIZE;
|
||||
if (separateLines)
|
||||
{
|
||||
outputBufferSize +=
|
||||
(outputBufferSize / OUTPUT_LINE_LENGTH) * CR_LF_SIZE;
|
||||
}
|
||||
|
||||
//
|
||||
// Include space for a terminating zero
|
||||
//
|
||||
outputBufferSize += 1;
|
||||
|
||||
//
|
||||
// Allocate the output buffer
|
||||
//
|
||||
char *outputBuffer = (char *)malloc(outputBufferSize);
|
||||
if (!outputBuffer)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t index = 0;
|
||||
size_t index2 = 0;
|
||||
const size_t lineLength = separateLines ? INPUT_LINE_LENGTH : length;
|
||||
size_t lineEnd = lineLength;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (lineEnd > length)
|
||||
{
|
||||
lineEnd = length;
|
||||
}
|
||||
|
||||
for (; index + BINARY_UNIT_SIZE - 1 < lineEnd; index += BINARY_UNIT_SIZE)
|
||||
{
|
||||
//
|
||||
// Inner loop: turn 48 bytes into 64 base64 characters
|
||||
//
|
||||
outputBuffer[index2++] = base64EncodeLookup[(inputBuffer[index] & 0xFC) >> 2];
|
||||
outputBuffer[index2++] = base64EncodeLookup[((inputBuffer[index] & 0x03) << 4)
|
||||
| ((inputBuffer[index + 1] & 0xF0) >> 4)];
|
||||
outputBuffer[index2++] = base64EncodeLookup[((inputBuffer[index + 1] & 0x0F) << 2)
|
||||
| ((inputBuffer[index + 2] & 0xC0) >> 6)];
|
||||
outputBuffer[index2++] = base64EncodeLookup[inputBuffer[index + 2] & 0x3F];
|
||||
}
|
||||
|
||||
if (lineEnd == length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
// Add the newline
|
||||
//
|
||||
outputBuffer[index2++] = '\r';
|
||||
outputBuffer[index2++] = '\n';
|
||||
lineEnd += lineLength;
|
||||
}
|
||||
|
||||
if (index + 1 < length)
|
||||
{
|
||||
//
|
||||
// Handle the single '=' case
|
||||
//
|
||||
outputBuffer[index2++] = base64EncodeLookup[(inputBuffer[index] & 0xFC) >> 2];
|
||||
outputBuffer[index2++] = base64EncodeLookup[((inputBuffer[index] & 0x03) << 4)
|
||||
| ((inputBuffer[index + 1] & 0xF0) >> 4)];
|
||||
outputBuffer[index2++] = base64EncodeLookup[(inputBuffer[index + 1] & 0x0F) << 2];
|
||||
outputBuffer[index2++] = '=';
|
||||
}
|
||||
else if (index < length)
|
||||
{
|
||||
//
|
||||
// Handle the double '=' case
|
||||
//
|
||||
outputBuffer[index2++] = base64EncodeLookup[(inputBuffer[index] & 0xFC) >> 2];
|
||||
outputBuffer[index2++] = base64EncodeLookup[(inputBuffer[index] & 0x03) << 4];
|
||||
outputBuffer[index2++] = '=';
|
||||
outputBuffer[index2++] = '=';
|
||||
}
|
||||
outputBuffer[index2] = 0;
|
||||
|
||||
//
|
||||
// Set the output length and return the buffer
|
||||
//
|
||||
if (outputLength)
|
||||
{
|
||||
*outputLength = index2;
|
||||
}
|
||||
return outputBuffer;
|
||||
}
|
||||
|
||||
@implementation DTBase64Coding
|
||||
|
||||
// this is abstract and not meant to be actually used
|
||||
- (id)init
|
||||
{
|
||||
[NSException raise:@"DTAbstractClassException" format:@"You tried to call %@ on an abstract class %@", NSStringFromSelector(_cmd), NSStringFromClass([self class])];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - Encoding and Decoding
|
||||
|
||||
+ (NSString *)stringByEncodingData:(NSData *)data
|
||||
{
|
||||
size_t outputLength = 0;
|
||||
char *outputBuffer = DT__NewBase64Encode([data bytes], [data length], true, &outputLength);
|
||||
|
||||
NSString *result = [[NSString alloc] initWithBytes:outputBuffer length:outputLength encoding:NSASCIIStringEncoding];
|
||||
free(outputBuffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (NSData *)dataByDecodingString:(NSString *)string
|
||||
{
|
||||
NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding];
|
||||
size_t outputLength;
|
||||
void *outputBuffer = DT__NewBase64Decode([data bytes], [data length], &outputLength);
|
||||
NSData *result = [NSData dataWithBytes:outputBuffer length:outputLength];
|
||||
free(outputBuffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// DTBlockFunctions.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 02.10.13.
|
||||
// Copyright (c) 2013 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
Block Utility Methods
|
||||
*/
|
||||
|
||||
/**
|
||||
Performs a block synchronous if execution is currently on the main thread or dispatches it asynchronously if not
|
||||
@param block The block to execute
|
||||
*/
|
||||
void DTBlockPerformSyncIfOnMainThreadElseAsync(void (^block)(void));
|
||||
|
||||
/**
|
||||
Performs a block synchronous on the main thread regardless of the current thread
|
||||
@param block The block to execute
|
||||
*/
|
||||
void DTBlockPerformSyncOnMainThread(void (^block)(void));
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// DTBlockFunctions.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 02.10.13.
|
||||
// Copyright (c) 2013 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DTBlockFunctions.h"
|
||||
|
||||
void DTBlockPerformSyncIfOnMainThreadElseAsync(void (^block)(void))
|
||||
{
|
||||
if ([NSThread isMainThread])
|
||||
{
|
||||
// can perform synchronous on main thread
|
||||
block();
|
||||
}
|
||||
else
|
||||
{
|
||||
// need to perform asynchronous
|
||||
dispatch_async(dispatch_get_main_queue(), block);
|
||||
}
|
||||
}
|
||||
|
||||
void DTBlockPerformSyncOnMainThread(void (^block)(void))
|
||||
{
|
||||
if ([NSThread isMainThread])
|
||||
{
|
||||
block();
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatch_sync(dispatch_get_main_queue(), block);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// DTCompatibility.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Rene Pirringer on 30.07.15.
|
||||
// Copyright (c) 2015 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Availability.h>
|
||||
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED > 80400
|
||||
#define DT_SUPPORTED_INTERFACE_ORIENTATIONS_RETURN_TYPE UIInterfaceOrientationMask
|
||||
#else
|
||||
#define DT_SUPPORTED_INTERFACE_ORIENTATIONS_RETURN_TYPE NSUInteger
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// DTCoreGraphicsUtils.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 7/18/10.
|
||||
// Copyright 2010 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <tgmath.h>
|
||||
|
||||
/**
|
||||
Various CoreGraphics-related utility functions
|
||||
*/
|
||||
|
||||
/**
|
||||
Promotes value to CGFloat type.
|
||||
*/
|
||||
#define CGFloat_(__x) ((CGFloat) (__x))
|
||||
|
||||
/**
|
||||
Calculates a size that fits an original size into a different size preserving the aspect ratio.
|
||||
*/
|
||||
CGSize DTCGSizeThatFitsKeepingAspectRatio(CGSize originalSize, CGSize sizeToFit);
|
||||
|
||||
/**
|
||||
Calculates a size that fits an original size into a different size preserving the aspect ratio and filling the target size.
|
||||
*/
|
||||
CGSize DTCGSizeThatFillsKeepingAspectRatio(CGSize originalSize, CGSize sizeToFit);
|
||||
|
||||
/**
|
||||
Replacement for buggy CGSizeMakeWithDictionaryRepresentation
|
||||
@param dict The dictionary containing an encoded `CGSize`
|
||||
@param size The `CGSize` to decode from the dictionary
|
||||
@see http://www.cocoanetics.com/2012/09/radar-cgrectmakewithdictionaryrepresentation/
|
||||
*/
|
||||
BOOL DTCGSizeMakeWithDictionaryRepresentation(NSDictionary *dict, CGSize *size);
|
||||
|
||||
/**
|
||||
Replacement for buggy CGSizeCreateDictionaryRepresentation
|
||||
@param size The `CGSize` to encode in the returned dictionary
|
||||
@see http://www.cocoanetics.com/2012/09/radar-cgrectmakewithdictionaryrepresentation/
|
||||
*/
|
||||
NSDictionary *DTCGSizeCreateDictionaryRepresentation(CGSize size);
|
||||
|
||||
/**
|
||||
Replacement for buggy CGRectMakeWithDictionaryRepresentation
|
||||
@param dict The dictionary containing an encoded `CGRect`
|
||||
@param rect The `CGRect` to decode from the dictionary
|
||||
@see http://www.cocoanetics.com/2012/09/radar-cgrectmakewithdictionaryrepresentation/
|
||||
*/
|
||||
BOOL DTCGRectMakeWithDictionaryRepresentation(NSDictionary *dict, CGRect *rect);
|
||||
|
||||
/**
|
||||
Replacement for buggy CGRectCreateDictionaryRepresentation
|
||||
@param rect The `CGRect` to encode in the returned dictionary
|
||||
@see http://www.cocoanetics.com/2012/09/radar-cgrectmakewithdictionaryrepresentation/
|
||||
*/
|
||||
NSDictionary *DTCGRectCreateDictionaryRepresentation(CGRect rect);
|
||||
|
||||
/**
|
||||
Convenience method to find the center of a CGRect. Uses CGRectGetMidX and CGRectGetMidY.
|
||||
@returns The point which is the center of rect.
|
||||
*/
|
||||
CGPoint DTCGRectCenter(CGRect rect);
|
||||
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// DTCoreGraphicsUtils.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 7/18/10.
|
||||
// Copyright 2010 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
|
||||
#import "DTCoreGraphicsUtils.h"
|
||||
|
||||
CGSize DTCGSizeThatFitsKeepingAspectRatio(CGSize originalSize, CGSize sizeToFit)
|
||||
{
|
||||
CGFloat necessaryZoomWidth = sizeToFit.width / originalSize.width;
|
||||
CGFloat necessaryZoomHeight = sizeToFit.height / originalSize.height;
|
||||
|
||||
CGFloat smallerZoom = MIN(necessaryZoomWidth, necessaryZoomHeight);
|
||||
|
||||
return CGSizeMake(round(originalSize.width*smallerZoom), round(originalSize.height*smallerZoom));
|
||||
}
|
||||
|
||||
CGSize DTCGSizeThatFillsKeepingAspectRatio(CGSize originalSize, CGSize sizeToFit)
|
||||
{
|
||||
CGFloat necessaryZoomWidth = sizeToFit.width / originalSize.width;
|
||||
CGFloat necessaryZoomHeight = sizeToFit.height / originalSize.height;
|
||||
|
||||
CGFloat largerZoom = MAX(necessaryZoomWidth, necessaryZoomHeight);
|
||||
|
||||
return CGSizeMake(round(originalSize.width*largerZoom), round(originalSize.height*largerZoom));
|
||||
}
|
||||
|
||||
BOOL DTCGSizeMakeWithDictionaryRepresentation(NSDictionary *dict, CGSize *size)
|
||||
{
|
||||
NSNumber *widthNumber = dict[@"Width"];
|
||||
NSNumber *heightNumber = dict[@"Height"];
|
||||
|
||||
if (!widthNumber || !heightNumber)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (size)
|
||||
{
|
||||
#if CGFLOAT_IS_DOUBLE
|
||||
size->width = [widthNumber doubleValue];
|
||||
size->height = [heightNumber doubleValue];
|
||||
#else
|
||||
size->width = [widthNumber floatValue];
|
||||
size->height = [heightNumber floatValue];
|
||||
#endif
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
NSDictionary *DTCGSizeCreateDictionaryRepresentation(CGSize size)
|
||||
{
|
||||
#if CGFLOAT_IS_DOUBLE
|
||||
NSNumber *widthNumber = [NSNumber numberWithDouble:size.width];
|
||||
NSNumber *heightNumber = [NSNumber numberWithDouble:size.height];
|
||||
#else
|
||||
NSNumber *widthNumber = [NSNumber numberWithFloat:size.width];
|
||||
NSNumber *heightNumber = [NSNumber numberWithFloat:size.height];
|
||||
#endif
|
||||
|
||||
return @{@"Width": widthNumber,
|
||||
@"Height": heightNumber};
|
||||
}
|
||||
|
||||
|
||||
BOOL DTCGRectMakeWithDictionaryRepresentation(NSDictionary *dict, CGRect *rect)
|
||||
{
|
||||
NSNumber *widthNumber = dict[@"Width"];
|
||||
NSNumber *heightNumber = dict[@"Height"];
|
||||
NSNumber *xNumber = dict[@"X"];
|
||||
NSNumber *yNumber = dict[@"Y"];
|
||||
|
||||
if (!widthNumber || !heightNumber || !xNumber || !yNumber)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (rect)
|
||||
{
|
||||
#if CGFLOAT_IS_DOUBLE
|
||||
rect->origin.x = [xNumber doubleValue];
|
||||
rect->origin.y = [yNumber doubleValue];
|
||||
rect->size.width = [widthNumber doubleValue];
|
||||
rect->size.height = [heightNumber doubleValue];
|
||||
#else
|
||||
rect->origin.x = [xNumber floatValue];
|
||||
rect->origin.y = [yNumber floatValue];
|
||||
rect->size.width = [widthNumber floatValue];
|
||||
rect->size.height = [heightNumber floatValue];
|
||||
#endif
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
NSDictionary *DTCGRectCreateDictionaryRepresentation(CGRect rect)
|
||||
{
|
||||
#if CGFLOAT_IS_DOUBLE
|
||||
NSNumber *widthNumber = [NSNumber numberWithDouble:rect.size.width];
|
||||
NSNumber *heightNumber = [NSNumber numberWithDouble:rect.size.height];
|
||||
NSNumber *xNumber = [NSNumber numberWithDouble:rect.origin.x];
|
||||
NSNumber *yNumber = [NSNumber numberWithDouble:rect.origin.y];
|
||||
#else
|
||||
NSNumber *widthNumber = [NSNumber numberWithFloat:rect.size.width];
|
||||
NSNumber *heightNumber = [NSNumber numberWithFloat:rect.size.height];
|
||||
NSNumber *xNumber = [NSNumber numberWithFloat:rect.origin.x];
|
||||
NSNumber *yNumber = [NSNumber numberWithFloat:rect.origin.y];
|
||||
#endif
|
||||
|
||||
return @{@"Width": widthNumber,
|
||||
@"Height": heightNumber,
|
||||
@"X": xNumber,
|
||||
@"Y": yNumber};
|
||||
}
|
||||
|
||||
CGPoint DTCGRectCenter(CGRect rect)
|
||||
{
|
||||
return (CGPoint){CGRectGetMidX(rect), CGRectGetMidY(rect)};
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// DTExtendedFileAttributes.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 3/6/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
This class provides read/write access to extended file attributes of a file or folder. It wraps the standard xattr Posix functions to do that.
|
||||
|
||||
Because the file system does not keep track of the data types saved in extended attributes this API so far reads and writes strings.
|
||||
*/
|
||||
@interface DTExtendedFileAttributes : NSObject
|
||||
|
||||
|
||||
/**
|
||||
@name Creating an Extended File Attribute Manager
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
Creates an Extended File Attribute Manager.
|
||||
|
||||
@param path The file path
|
||||
*/
|
||||
- (id)initWithPath:(NSString *)path;
|
||||
|
||||
|
||||
/**
|
||||
@name Reading/Writing Extended Attributes
|
||||
*/
|
||||
|
||||
/**
|
||||
Removes an extended file attribute from the receiver.
|
||||
|
||||
@param attribute The name of the attribute.
|
||||
@returns `YES` if successful.
|
||||
*/
|
||||
- (BOOL)removeAttribute:(NSString *)attribute;
|
||||
|
||||
|
||||
/**
|
||||
Sets the value of an extended file attribute for the receiver.
|
||||
|
||||
If the value is `nil` then this is the same as calling <removeAttribute:>.
|
||||
|
||||
@param value The string to save for this attribute.
|
||||
@param attribute The name of the attribute.
|
||||
@returns `YES` if successful.
|
||||
*/
|
||||
- (BOOL)setValue:(NSString *)value forAttribute:(NSString *)attribute;
|
||||
|
||||
|
||||
/**
|
||||
Gets the value of an extended file attribute from the receiver.
|
||||
|
||||
@param attribute The name of the attribute.
|
||||
@returns The string for the value or `nil` if the value is not set.
|
||||
*/
|
||||
- (NSString *)valueForAttribute:(NSString *)attribute;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// DTExtendedFileAttributes.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 3/6/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DTExtendedFileAttributes.h"
|
||||
|
||||
#import <sys/xattr.h>
|
||||
|
||||
@implementation DTExtendedFileAttributes
|
||||
{
|
||||
NSString *_path;
|
||||
}
|
||||
|
||||
- (id)initWithPath:(NSString *)path
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
if (![path length])
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
_path = path;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)removeAttribute:(NSString *)attribute
|
||||
{
|
||||
const char *attrName = [attribute UTF8String];
|
||||
const char *filePath = [_path fileSystemRepresentation];
|
||||
|
||||
int result = removexattr(filePath, attrName, 0);
|
||||
|
||||
return (result==0);
|
||||
}
|
||||
|
||||
- (BOOL)setValue:(NSString *)value forAttribute:(NSString *)attribute
|
||||
{
|
||||
if (![attribute length])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (!value)
|
||||
{
|
||||
// remove it instead
|
||||
return [self removeAttribute:attribute];
|
||||
}
|
||||
|
||||
const char *attrName = [attribute UTF8String];
|
||||
const char *filePath = [_path fileSystemRepresentation];
|
||||
|
||||
const char *val = [value UTF8String];
|
||||
|
||||
int result = setxattr(filePath, attrName, val, strlen(val), 0, 0);
|
||||
|
||||
return (result==0);
|
||||
}
|
||||
|
||||
- (NSString *)valueForAttribute:(NSString *)attribute
|
||||
{
|
||||
if (![attribute length])
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
const char *attrName = [attribute UTF8String];
|
||||
const char *filePath = [_path fileSystemRepresentation];
|
||||
|
||||
// get size of needed buffer
|
||||
ssize_t bufferLength = getxattr(filePath, attrName, NULL, 0, 0, 0);
|
||||
|
||||
if (bufferLength<=0)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
// make a buffer of sufficient length
|
||||
char *buffer = malloc(bufferLength);
|
||||
|
||||
// now actually get the attribute string
|
||||
getxattr(filePath, attrName, buffer, bufferLength, 0, 0);
|
||||
|
||||
// convert to NSString
|
||||
NSString *retString = [[NSString alloc] initWithBytes:buffer length:bufferLength encoding:NSUTF8StringEncoding];
|
||||
|
||||
// release buffer
|
||||
free(buffer);
|
||||
|
||||
return retString;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// DTFolderMonitor.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 05.08.13.
|
||||
// Copyright (c) 2013 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// The block to execute if a monitored folder changes
|
||||
typedef void (^DTFolderMonitorBlock) (void);
|
||||
|
||||
/**
|
||||
Class for monitoring changes on a folder. This can be used to monitor the application documents folder for changes in the files there if the user adds or removes files via iTunes file sharing.
|
||||
*/
|
||||
|
||||
@interface DTFolderMonitor : NSObject
|
||||
|
||||
/**
|
||||
@name Creating a Folder Monitor
|
||||
*/
|
||||
|
||||
/**
|
||||
Creates a new DTFolderMonitor to watch the folder at the given URL. Whenever there is a change on this folder the block is executed.
|
||||
|
||||
The URL must be a file URL. Both the URL and the block parameter are mandatory. The block is being dispatched on a background queue.
|
||||
|
||||
@param URL The monitored folder URL
|
||||
@param block The block to execute if the folder is being modified
|
||||
@returns The instantiated monitor in suspended mode. Call -startMonitoring to start monitoring.
|
||||
*/
|
||||
+ (DTFolderMonitor * _Nonnull)folderMonitorForURL:(NSURL * _Nonnull)URL block: (DTFolderMonitorBlock _Nullable)block;
|
||||
|
||||
|
||||
/**
|
||||
@name Starting/Stopping Monitoring
|
||||
*/
|
||||
|
||||
/**
|
||||
Start monitoring the folder. A monitor can be started and stopped multiple times.
|
||||
*/
|
||||
- (void)startMonitoring;
|
||||
|
||||
/**
|
||||
Stop monitoring the folder. A monitor can be started and stopped multiple times.
|
||||
*/
|
||||
- (void)stopMonitoring;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// DTFolderMonitor.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 05.08.13.
|
||||
// Copyright (c) 2013 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DTFolderMonitor.h"
|
||||
|
||||
@implementation DTFolderMonitor
|
||||
{
|
||||
NSURL *_URL;
|
||||
DTFolderMonitorBlock _block;
|
||||
|
||||
int _fileDescriptor;
|
||||
dispatch_queue_t _queue;
|
||||
dispatch_source_t _source;
|
||||
}
|
||||
|
||||
+ (DTFolderMonitor *)folderMonitorForURL:(NSURL *)URL block:(DTFolderMonitorBlock)block
|
||||
{
|
||||
return [[DTFolderMonitor alloc] initWithURL:URL block:block];
|
||||
}
|
||||
|
||||
- (instancetype)initWithURL:(NSURL *)URL block:(DTFolderMonitorBlock)block
|
||||
{
|
||||
NSParameterAssert(URL);
|
||||
NSParameterAssert(block);
|
||||
NSAssert([URL isFileURL], @"URL Parameter must be a folder URL");
|
||||
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_URL = URL;
|
||||
_block = [block copy];
|
||||
_queue = dispatch_queue_create("DTFolderMonitor Queue", 0);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self stopMonitoring];
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(_queue);
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)startMonitoring
|
||||
{
|
||||
@synchronized(self)
|
||||
{
|
||||
if (_source)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_fileDescriptor = open([_URL.path fileSystemRepresentation], O_EVTONLY);
|
||||
|
||||
if (!_fileDescriptor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// watch the file descriptor for writes
|
||||
_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, _fileDescriptor, DISPATCH_VNODE_WRITE, _queue);
|
||||
|
||||
// call the passed block if the source is modified
|
||||
dispatch_source_set_event_handler(_source, _block);
|
||||
|
||||
// close the file descriptor when the dispatch source is cancelled
|
||||
dispatch_source_set_cancel_handler(_source, ^{
|
||||
|
||||
close(self->_fileDescriptor);
|
||||
});
|
||||
|
||||
// at this point the dispatch source is paused, so start watching
|
||||
dispatch_resume(_source);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopMonitoring
|
||||
{
|
||||
@synchronized(self)
|
||||
{
|
||||
if (!_source)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_source_cancel(_source);
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(_source);
|
||||
#endif
|
||||
_source = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// DTFoundationConstants.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Stefan Gugarel on 10/18/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
// string constant for NSError domain
|
||||
extern NSString * const DTFoundationErrorDomain;
|
||||
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// DTFoundationConstants.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Stefan Gugarel on 10/18/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DTFoundationConstants.h"
|
||||
|
||||
NSString * const DTFoundationErrorDomain = @"DTFoundation";
|
||||
@@ -0,0 +1,182 @@
|
||||
//
|
||||
// DTHTMLParser.h
|
||||
// DTCoreText
|
||||
//
|
||||
// Created by Oliver Drobnik on 1/18/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <DTFoundation/DTWeakSupport.h>
|
||||
|
||||
@class DTHTMLParser;
|
||||
/** The DTHTMLParserDelegate protocol defines the optional methods implemented by delegates of DTHTMLParser objects.
|
||||
|
||||
Dependencies: libxml2.dylib
|
||||
*/
|
||||
@protocol DTHTMLParserDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
Sent by the parser object to the delegate when it begins parsing a document.
|
||||
|
||||
@param parser A parser object.
|
||||
*/
|
||||
- (void)parserDidStartDocument:(DTHTMLParser *)parser;
|
||||
|
||||
/**
|
||||
Sent by the parser object to the delegate when it has successfully completed parsing
|
||||
|
||||
@param parser A parser object.
|
||||
*/
|
||||
- (void)parserDidEndDocument:(DTHTMLParser *)parser;
|
||||
|
||||
/**
|
||||
Sent by a parser object to its delegate when it encounters a start tag for a given element.
|
||||
|
||||
@param parser A parser object.
|
||||
@param elementName A string that is the name of an element (in its start tag).
|
||||
@param attributeDict A dictionary that contains any attributes associated with the element. Keys are the names of attributes, and values are attribute values.
|
||||
*/
|
||||
- (void)parser:(DTHTMLParser *)parser didStartElement:(NSString *)elementName attributes:(NSDictionary *)attributeDict;
|
||||
|
||||
/**
|
||||
Sent by a parser object to its delegate when it encounters an end tag for a specific element.
|
||||
|
||||
@param parser A parser object.
|
||||
@param elementName A string that is the name of an element (in its end tag).
|
||||
*/
|
||||
- (void)parser:(DTHTMLParser *)parser didEndElement:(NSString *)elementName;
|
||||
|
||||
/**
|
||||
Sent by a parser object to provide its delegate with a string representing all or part of the characters of the current element.
|
||||
|
||||
The parser object may send the delegate several parser:foundCharacters: messages to report the characters of an element. Because string may be only part of the total character content for the current element, you should append it to the current accumulation of characters until the element changes.
|
||||
|
||||
@param parser A parser object.
|
||||
@param string A string representing the complete or partial textual content of the current element.
|
||||
*/
|
||||
- (void)parser:(DTHTMLParser *)parser foundCharacters:(NSString *)string;
|
||||
|
||||
/**
|
||||
Sent by a parser object to its delegate when it encounters a comment in the HTML.
|
||||
|
||||
@param parser A DTHTMLParser object parsing HTML.
|
||||
@param comment A string that is a the content of a comment in the XML.
|
||||
*/
|
||||
- (void)parser:(DTHTMLParser *)parser foundComment:(NSString *)comment;
|
||||
|
||||
/**
|
||||
Sent by a parser object to its delegate when it encounters a CDATA block.
|
||||
|
||||
Through this method the parser object passes the contents of the block to its delegate in an NSData object. The CDATA block is character data that is ignored by the parser. The encoding of the character data is UTF-8. To convert the data object to a string object, use the NSString method initWithData:encoding:. Note: CSS style blocks are returned as CDATA.
|
||||
|
||||
@param parser A DTHTMLParser object parsing HTML.
|
||||
@param CDATABlock A data object containing a block of CDATA.
|
||||
*/
|
||||
- (void)parser:(DTHTMLParser *)parser foundCDATA:(NSData *)CDATABlock;
|
||||
|
||||
/**
|
||||
Sent by a parser object to its delegate when it encounters a processing instruction.
|
||||
|
||||
@param parser A DTHTMLParser object parsing HTML.
|
||||
@param target A string representing the target of a processing instruction.
|
||||
@param data A string representing the data for a processing instruction.
|
||||
*/
|
||||
- (void)parser:(DTHTMLParser *)parser foundProcessingInstructionWithTarget:(NSString *)target data:(NSString *)data;
|
||||
|
||||
/**
|
||||
Sent by a parser object to its delegate when it encounters a fatal error.
|
||||
|
||||
When this method is invoked, parsing is stopped. For further information about the error, you can query parseError or you can send the parser a parserError message. You can also send the parser lineNumber and columnNumber messages to further isolate where the error occurred. Typically you implement this method to display information about the error to the user.
|
||||
|
||||
@param parser A parser object.
|
||||
@param parseError An `NSError` object describing the parsing error that occurred.
|
||||
*/
|
||||
- (void)parser:(DTHTMLParser *)parser parseErrorOccurred:(NSError *)parseError;
|
||||
|
||||
@end
|
||||
|
||||
/** Instances of this class parse HTML documents (including DTD declarations) in an event-driven manner. A DTHTMLParser notifies its delegate about the items (elements, attributes, CDATA blocks, comments, and so on) that it encounters as it processes an HTML document. It does not itself do anything with those parsed items except report them. It also reports parsing errors. For convenience, an DTHTMLParser object in the following descriptions is sometimes referred to as a parser object.
|
||||
*/
|
||||
@interface DTHTMLParser : NSObject
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Initializing a Parser Object
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
Initializes the receiver with the HTML contents encapsulated in a given data object.
|
||||
|
||||
@param data An `NSData` object containing XML markup.
|
||||
@param encoding The encoding used for encoding the HTML data
|
||||
@returns An initialized `DTHTMLParser` object or nil if an error occurs.
|
||||
*/
|
||||
- (id)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding;
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Parsing
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
Starts the event-driven parsing operation.
|
||||
|
||||
If you invoke this method, the delegate, if it implements parser:parseErrorOccurred:, is informed of the cancelled parsing operation.
|
||||
|
||||
@returns `YES` if parsing is successful and `NO` in there is an error or if the parsing operation is aborted.
|
||||
*/
|
||||
- (BOOL)parse;
|
||||
|
||||
/**
|
||||
Stops the parser object.
|
||||
|
||||
@see parse
|
||||
@see parserError
|
||||
*/
|
||||
- (void)abortParsing;
|
||||
|
||||
/**
|
||||
The receiver’s delegate. It is not retained. The delegate must conform to the DTHTMLParserDelegate Protocol protocol.
|
||||
*/
|
||||
@property (nonatomic, DT_WEAK_PROPERTY) id <DTHTMLParserDelegate> delegate;
|
||||
|
||||
/**
|
||||
Returns the column number of the XML document being processed by the receiver.
|
||||
|
||||
The column refers to the nesting level of the HTML elements in the document. You may invoke this method once a parsing operation has begun or after an error occurs.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSInteger columnNumber;
|
||||
|
||||
/**
|
||||
Returns the line number of the HTML document being processed by the receiver.
|
||||
|
||||
You may invoke this method once a parsing operation has begun or after an error occurs.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSInteger lineNumber;
|
||||
|
||||
/**
|
||||
Returns an `NSError` object from which you can obtain information about a parsing error.
|
||||
|
||||
You may invoke this method after a parsing operation abnormally terminates to determine the cause of error.
|
||||
*/
|
||||
@property (nonatomic, readonly, strong) NSError *parserError;
|
||||
|
||||
/**
|
||||
Returns the public identifier of the external entity referenced in the HTML document.
|
||||
|
||||
You may invoke this method once a parsing operation has begun or after an error occurs.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString *publicID;
|
||||
|
||||
/**
|
||||
Returns the system identifier of the external entity referenced in the HTML document.
|
||||
|
||||
You may invoke this method once a parsing operation has begun or after an error occurs.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString *systemID;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,484 @@
|
||||
//
|
||||
// DTHTMLParser.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 1/18/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DTHTMLParser.h"
|
||||
#import <libxml/HTMLparser.h>
|
||||
|
||||
|
||||
@interface DTHTMLParser()
|
||||
|
||||
@property (nonatomic, strong) NSError *parserError;
|
||||
@property (nonatomic, assign) NSStringEncoding encoding;
|
||||
|
||||
- (void)_resetAccumulateBufferAndReportCharacters;
|
||||
- (void)_accumulateCharacters:(const xmlChar *)characters length:(int)length;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark Event function prototypes
|
||||
|
||||
void _startDocument(void *context);
|
||||
void _endDocument(void *context);
|
||||
void _startElement(void *context, const xmlChar *name,const xmlChar **atts);
|
||||
void _startElement_no_delegate(void *context, const xmlChar *name, const xmlChar **atts);
|
||||
void _endElement(void *context, const xmlChar *name);
|
||||
void _endElement_no_delegate(void *context, const xmlChar *chars);
|
||||
void _characters(void *context, const xmlChar *ch, int len);
|
||||
void _comment(void *context, const xmlChar *value);
|
||||
void _dterror(void *context, const char *msg, ...);
|
||||
void _cdataBlock(void *context, const xmlChar *value, int len);
|
||||
void _processingInstruction (void *context, const xmlChar *target, const xmlChar *data);
|
||||
|
||||
#pragma mark Event functions
|
||||
void _startDocument(void *context)
|
||||
{
|
||||
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
||||
|
||||
[myself.delegate parserDidStartDocument:myself];
|
||||
}
|
||||
|
||||
void _endDocument(void *context)
|
||||
{
|
||||
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
||||
|
||||
[myself.delegate parserDidEndDocument:myself];
|
||||
}
|
||||
|
||||
void _startElement(void *context, const xmlChar *name, const xmlChar **atts)
|
||||
{
|
||||
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
||||
|
||||
[myself _resetAccumulateBufferAndReportCharacters];
|
||||
|
||||
NSString *nameStr = [NSString stringWithUTF8String:(char *)name];
|
||||
|
||||
NSMutableDictionary *attributes = nil;
|
||||
|
||||
if (atts)
|
||||
{
|
||||
NSString *key = nil;
|
||||
NSString *value = nil;
|
||||
|
||||
attributes = [[NSMutableDictionary alloc] init];
|
||||
|
||||
int i = 0;
|
||||
while (1)
|
||||
{
|
||||
char *att = (char *)atts[i++];
|
||||
|
||||
if (!key)
|
||||
{
|
||||
if (!att)
|
||||
{
|
||||
// we're done
|
||||
break;
|
||||
}
|
||||
|
||||
key = [NSString stringWithUTF8String:att];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (att)
|
||||
{
|
||||
value = [NSString stringWithUTF8String:att];
|
||||
}
|
||||
else
|
||||
{
|
||||
// solo attribute
|
||||
value = key;
|
||||
}
|
||||
|
||||
[attributes setObject:value forKey:key];
|
||||
|
||||
value = nil;
|
||||
key = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[myself.delegate parser:myself didStartElement:nameStr attributes:attributes];
|
||||
}
|
||||
|
||||
void _startElement_no_delegate(void *context, const xmlChar *name, const xmlChar **atts)
|
||||
{
|
||||
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
||||
|
||||
[myself _resetAccumulateBufferAndReportCharacters];
|
||||
}
|
||||
|
||||
void _endElement(void *context, const xmlChar *chars)
|
||||
{
|
||||
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
||||
|
||||
[myself _resetAccumulateBufferAndReportCharacters];
|
||||
|
||||
NSString *nameStr = [NSString stringWithUTF8String:(char *)chars];
|
||||
|
||||
[myself.delegate parser:myself didEndElement:nameStr];
|
||||
}
|
||||
|
||||
void _endElement_no_delegate(void *context, const xmlChar *chars)
|
||||
{
|
||||
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
||||
|
||||
[myself _resetAccumulateBufferAndReportCharacters];
|
||||
}
|
||||
|
||||
// libxml reports characters in batches of at most 1000 at a time
|
||||
// in addition, entities are reported separately
|
||||
void _characters(void *context, const xmlChar *chars, int len)
|
||||
{
|
||||
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
||||
|
||||
[myself _accumulateCharacters:chars length:len];
|
||||
}
|
||||
|
||||
void _comment(void *context, const xmlChar *chars)
|
||||
{
|
||||
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
||||
|
||||
NSString *string = [NSString stringWithCString:(const char *)chars encoding:myself.encoding];
|
||||
|
||||
[myself.delegate parser:myself foundComment:string];
|
||||
}
|
||||
|
||||
void _dterror(void *context, const char *msg, ...)
|
||||
{
|
||||
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
||||
|
||||
char string[256];
|
||||
va_list arg_ptr;
|
||||
|
||||
va_start(arg_ptr, msg);
|
||||
vsnprintf(string, 256, msg, arg_ptr);
|
||||
va_end(arg_ptr);
|
||||
|
||||
NSString *errorMsg = [NSString stringWithUTF8String:string];
|
||||
|
||||
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMsg forKey:NSLocalizedDescriptionKey];
|
||||
myself.parserError = [NSError errorWithDomain:@"DTHTMLParser" code:1 userInfo:userInfo];
|
||||
|
||||
[myself.delegate parser:myself parseErrorOccurred:myself.parserError];
|
||||
}
|
||||
|
||||
void _cdataBlock(void *context, const xmlChar *value, int len)
|
||||
{
|
||||
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
||||
|
||||
NSData *data = [NSData dataWithBytes:(const void *)value length:len];
|
||||
|
||||
[myself.delegate parser:myself foundCDATA:data];
|
||||
}
|
||||
|
||||
void _processingInstruction (void *context, const xmlChar *target, const xmlChar *data)
|
||||
{
|
||||
DTHTMLParser *myself = (__bridge DTHTMLParser *)context;
|
||||
|
||||
NSStringEncoding encoding = myself.encoding;
|
||||
|
||||
NSString *targetStr = [NSString stringWithCString:(const char *)target encoding:encoding];
|
||||
NSString *dataStr = [NSString stringWithCString:(const char *)data encoding:encoding];
|
||||
|
||||
[myself.delegate parser:myself foundProcessingInstructionWithTarget:targetStr data:dataStr];
|
||||
}
|
||||
|
||||
@implementation DTHTMLParser
|
||||
{
|
||||
htmlSAXHandler _handler;
|
||||
|
||||
NSData *_data;
|
||||
NSStringEncoding _encoding;
|
||||
|
||||
DT_WEAK_VARIABLE id <DTHTMLParserDelegate> _delegate;
|
||||
htmlParserCtxtPtr _parserContext;
|
||||
|
||||
NSMutableString *_accumulateBuffer;
|
||||
|
||||
BOOL _isAborting;
|
||||
}
|
||||
|
||||
|
||||
- (id)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding
|
||||
{
|
||||
if (!data)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
_data = data;
|
||||
_encoding = encoding;
|
||||
|
||||
xmlSAX2InitHtmlDefaultSAXHandler(&_handler);
|
||||
|
||||
// set default handlers as we would crash otherwise
|
||||
self.delegate = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (_parserContext)
|
||||
{
|
||||
htmlFreeParserCtxt(_parserContext);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_resetAccumulateBufferAndReportCharacters
|
||||
{
|
||||
if (!_accumulateBuffer.length)
|
||||
{
|
||||
// nothing in the buffer
|
||||
return;
|
||||
}
|
||||
|
||||
[self.delegate parser:self foundCharacters:_accumulateBuffer];
|
||||
|
||||
// reset buffer
|
||||
_accumulateBuffer = nil;
|
||||
}
|
||||
|
||||
- (void)_accumulateCharacters:(const xmlChar *)characters length:(int)length
|
||||
{
|
||||
if (!_accumulateBuffer)
|
||||
{
|
||||
_accumulateBuffer = [[NSMutableString alloc] initWithBytes:characters length:length encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
else
|
||||
{
|
||||
// we don't need to use the copy version since _accumulateBuffer will copy characters immediately
|
||||
[_accumulateBuffer appendString:[[NSString alloc] initWithBytesNoCopy:(void *)characters length:length encoding:NSUTF8StringEncoding freeWhenDone:NO]];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)parse
|
||||
{
|
||||
void *dataBytes = (char *)[_data bytes];
|
||||
unsigned long dataSize = [_data length];
|
||||
|
||||
// detect encoding if necessary
|
||||
xmlCharEncoding charEnc = XML_CHAR_ENCODING_NONE;
|
||||
|
||||
if (!_encoding)
|
||||
{
|
||||
charEnc = xmlDetectCharEncoding(dataBytes, (int)dataSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
// convert the encoding
|
||||
CFStringEncoding cfenc = CFStringConvertNSStringEncodingToEncoding(_encoding);
|
||||
|
||||
if (cfenc != kCFStringEncodingInvalidId)
|
||||
{
|
||||
CFStringRef cfencstr = CFStringConvertEncodingToIANACharSetName(cfenc);
|
||||
|
||||
if (cfencstr)
|
||||
{
|
||||
NSString *NS_VALID_UNTIL_END_OF_SCOPE encstr = [NSString stringWithString:(__bridge NSString*)cfencstr];
|
||||
const char *enc = [encstr UTF8String];
|
||||
|
||||
charEnc = xmlParseCharEncoding(enc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create a parse context
|
||||
_parserContext = htmlCreatePushParserCtxt(&_handler, (__bridge void *)self, dataBytes, (int)dataSize, NULL, charEnc);
|
||||
|
||||
// set some options
|
||||
htmlCtxtUseOptions(_parserContext, HTML_PARSE_RECOVER | HTML_PARSE_NONET | HTML_PARSE_COMPACT | HTML_PARSE_NOBLANKS);
|
||||
|
||||
// parse!
|
||||
int result = htmlParseDocument(_parserContext);
|
||||
|
||||
return (result==0 && !_isAborting);
|
||||
}
|
||||
|
||||
- (void)abortParsing
|
||||
{
|
||||
if (_parserContext)
|
||||
{
|
||||
// apparently this frees it too
|
||||
xmlStopParser(_parserContext);
|
||||
_parserContext = NULL;
|
||||
}
|
||||
|
||||
_isAborting = YES;
|
||||
|
||||
// prevent future callbacks
|
||||
_handler.startDocument = NULL;
|
||||
_handler.endDocument = NULL;
|
||||
_handler.startElement = NULL;
|
||||
_handler.endElement = NULL;
|
||||
_handler.characters = NULL;
|
||||
_handler.comment = NULL;
|
||||
_handler.error = NULL;
|
||||
_handler.processingInstruction = NULL;
|
||||
|
||||
// inform delegate
|
||||
__strong __typeof__(_delegate) delegate = _delegate;
|
||||
if ([delegate respondsToSelector:@selector(parser:parseErrorOccurred:)])
|
||||
{
|
||||
[delegate parser:self parseErrorOccurred:self.parserError];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Properties
|
||||
|
||||
- (id <DTHTMLParserDelegate>)delegate
|
||||
{
|
||||
return _delegate;
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id <DTHTMLParserDelegate>)delegate
|
||||
{
|
||||
_delegate = delegate;
|
||||
|
||||
if ([delegate respondsToSelector:@selector(parserDidStartDocument:)])
|
||||
{
|
||||
_handler.startDocument = _startDocument;
|
||||
}
|
||||
else
|
||||
{
|
||||
_handler.startDocument = NULL;
|
||||
}
|
||||
|
||||
if ([delegate respondsToSelector:@selector(parserDidEndDocument:)])
|
||||
{
|
||||
_handler.endDocument = _endDocument;
|
||||
}
|
||||
else
|
||||
{
|
||||
_handler.endDocument = NULL;
|
||||
}
|
||||
|
||||
if ([delegate respondsToSelector:@selector(parser:foundCharacters:)])
|
||||
{
|
||||
_handler.characters = _characters;
|
||||
}
|
||||
else
|
||||
{
|
||||
_handler.characters = NULL;
|
||||
}
|
||||
|
||||
if ([delegate respondsToSelector:@selector(parser:didStartElement:attributes:)])
|
||||
{
|
||||
_handler.startElement = _startElement;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if there is a character handler we need to still report start of elements for accumulation
|
||||
if (_handler.characters)
|
||||
{
|
||||
_handler.startElement = _startElement_no_delegate;
|
||||
}
|
||||
else
|
||||
{
|
||||
_handler.startElement = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if ([delegate respondsToSelector:@selector(parser:didEndElement:)])
|
||||
{
|
||||
_handler.endElement = _endElement;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if there is a character handler we need to still report start of elements for accumulation
|
||||
if (_handler.characters)
|
||||
{
|
||||
_handler.endElement = _endElement_no_delegate;
|
||||
}
|
||||
else
|
||||
{
|
||||
_handler.endElement = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if ([delegate respondsToSelector:@selector(parser:foundComment:)])
|
||||
{
|
||||
_handler.comment = _comment;
|
||||
}
|
||||
else
|
||||
{
|
||||
_handler.comment = NULL;
|
||||
}
|
||||
|
||||
if ([delegate respondsToSelector:@selector(parser:parseErrorOccurred:)])
|
||||
{
|
||||
_handler.error = _dterror;
|
||||
}
|
||||
else
|
||||
{
|
||||
_handler.error = NULL;
|
||||
}
|
||||
|
||||
if ([delegate respondsToSelector:@selector(parser:foundCDATA:)])
|
||||
{
|
||||
_handler.cdataBlock = _cdataBlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
_handler.cdataBlock = NULL;
|
||||
}
|
||||
|
||||
if ([delegate respondsToSelector:@selector(parser:foundProcessingInstructionWithTarget:data:)])
|
||||
{
|
||||
_handler.processingInstruction = _processingInstruction;
|
||||
}
|
||||
else
|
||||
{
|
||||
_handler.processingInstruction = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)lineNumber
|
||||
{
|
||||
return xmlSAX2GetLineNumber(_parserContext);
|
||||
}
|
||||
|
||||
- (NSInteger)columnNumber
|
||||
{
|
||||
return xmlSAX2GetColumnNumber(_parserContext);
|
||||
}
|
||||
|
||||
- (NSString *)systemID
|
||||
{
|
||||
char *systemID = (char *)xmlSAX2GetSystemId(_parserContext);
|
||||
|
||||
if (!systemID)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [NSString stringWithUTF8String:systemID];
|
||||
}
|
||||
|
||||
- (NSString *)publicID
|
||||
{
|
||||
char *publicID = (char *)xmlSAX2GetPublicId(_parserContext);
|
||||
|
||||
if (!publicID)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [NSString stringWithUTF8String:publicID];
|
||||
}
|
||||
|
||||
|
||||
@synthesize parserError = _parserError;
|
||||
@synthesize encoding = _encoding;
|
||||
|
||||
|
||||
@end
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
//
|
||||
// DTLog.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 06.08.13.
|
||||
// Copyright (c) 2013 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
Replacement for `NSLog` which can be configured to output certain log levels at run time.
|
||||
*/
|
||||
|
||||
// block signature called for each log statement
|
||||
typedef void (^DTLogBlock)(NSUInteger logLevel, NSString *fileName, NSUInteger lineNumber, NSString *methodName, NSString *format, ...);
|
||||
|
||||
|
||||
// internal variables needed by macros
|
||||
extern DTLogBlock DTLogHandler;
|
||||
extern NSUInteger DTCurrentLogLevel;
|
||||
|
||||
/**
|
||||
There is a macro for each ASL log level:
|
||||
|
||||
- DTLogEmergency (0)
|
||||
- DTLogAlert (1)
|
||||
- DTLogCritical (2)
|
||||
- DTLogError (3)
|
||||
- DTLogWarning (4)
|
||||
- DTLogNotice (5)
|
||||
- DTLogInfo (6)
|
||||
- DTLogDebug (7)
|
||||
*/
|
||||
|
||||
/**
|
||||
Constants for log levels used by DTLog
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, DTLogLevel)
|
||||
{
|
||||
/**
|
||||
Log level for *emergency* messages
|
||||
*/
|
||||
DTLogLevelEmergency = 0,
|
||||
|
||||
/**
|
||||
Log level for *alert* messages
|
||||
*/
|
||||
DTLogLevelAlert = 1,
|
||||
|
||||
/**
|
||||
Log level for *critical* messages
|
||||
*/
|
||||
DTLogLevelCritical = 2,
|
||||
|
||||
/**
|
||||
Log level for *error* messages
|
||||
*/
|
||||
DTLogLevelError = 3,
|
||||
|
||||
/**
|
||||
Log level for *warning* messages
|
||||
*/
|
||||
DTLogLevelWarning = 4,
|
||||
|
||||
/**
|
||||
Log level for *notice* messages
|
||||
*/
|
||||
DTLogLevelNotice = 5,
|
||||
|
||||
/**
|
||||
Log level for *info* messages. This is the default log level for DTLog.
|
||||
*/
|
||||
DTLogLevelInfo = 6,
|
||||
|
||||
/**
|
||||
Log level for *debug* messages
|
||||
*/
|
||||
DTLogLevelDebug = 7
|
||||
};
|
||||
|
||||
/**
|
||||
@name Logging Functions
|
||||
*/
|
||||
|
||||
/**
|
||||
Sets the block to be executed for messages with a log level less or equal the currently set log level
|
||||
@param handler The block to handle log output
|
||||
*/
|
||||
void DTLogSetLoggerBlock(DTLogBlock handler);
|
||||
|
||||
/**
|
||||
Modifies the current log level
|
||||
@param logLevel The ASL log level (0-7) to set, lower numbers being more important
|
||||
*/
|
||||
void DTLogSetLogLevel(NSUInteger logLevel);
|
||||
|
||||
/**
|
||||
Variant of DTLogMessage that takes a va_list.
|
||||
@param logLevel The DTLogLevel for the message
|
||||
@param format The log message format
|
||||
@param args The va_list of arguments
|
||||
*/
|
||||
void DTLogMessagev(DTLogLevel logLevel, NSString *format, va_list args);
|
||||
|
||||
/**
|
||||
Same as `NSLog` but allows for setting a message log level
|
||||
@param logLevel The DTLogLevel for the message
|
||||
@param format The log message format and optional variables
|
||||
*/
|
||||
void DTLogMessage(DTLogLevel logLevel, NSString *format, ...);
|
||||
|
||||
/**
|
||||
Retrieves the log messages currently available for the running app
|
||||
@returns an `NSArray` of `NSDictionary` entries
|
||||
*/
|
||||
NSArray *DTLogGetMessages(void);
|
||||
|
||||
/**
|
||||
@name Macros
|
||||
*/
|
||||
|
||||
// log macro for error level (0)
|
||||
#define DTLogEmergency(format, ...) DTLogCallHandlerIfLevel(DTLogLevelEmergency, format, ##__VA_ARGS__)
|
||||
|
||||
// log macro for error level (1)
|
||||
#define DTLogAlert(format, ...) DTLogCallHandlerIfLevel(DTLogLevelAlert, format, ##__VA_ARGS__)
|
||||
|
||||
// log macro for error level (2)
|
||||
#define DTLogCritical(format, ...) DTLogCallHandlerIfLevel(DTLogLevelCritical, format, ##__VA_ARGS__)
|
||||
|
||||
// log macro for error level (3)
|
||||
#define DTLogError(format, ...) DTLogCallHandlerIfLevel(DTLogLevelError, format, ##__VA_ARGS__)
|
||||
|
||||
// log macro for error level (4)
|
||||
#define DTLogWarning(format, ...) DTLogCallHandlerIfLevel(DTLogLevelWarning, format, ##__VA_ARGS__)
|
||||
|
||||
// log macro for error level (5)
|
||||
#define DTLogNotice(format, ...) DTLogCallHandlerIfLevel(DTLogLevelNotice, format, ##__VA_ARGS__)
|
||||
|
||||
// log macro for info level (6)
|
||||
#define DTLogInfo(format, ...) DTLogCallHandlerIfLevel(DTLogLevelInfo, format, ##__VA_ARGS__)
|
||||
|
||||
// log macro for debug level (7)
|
||||
#define DTLogDebug(format, ...) DTLogCallHandlerIfLevel(DTLogLevelDebug, format, ##__VA_ARGS__)
|
||||
|
||||
// macro that gets called by individual level macros
|
||||
#define DTLogCallHandlerIfLevel(logLevel, format, ...) \
|
||||
if (DTLogHandler && DTCurrentLogLevel>=logLevel) DTLogHandler(logLevel, DTLogSourceFileName, DTLogSourceLineNumber, DTLogSourceMethodName, format, ##__VA_ARGS__)
|
||||
|
||||
// helper to get the current source file name as NSString
|
||||
#define DTLogSourceFileName [[NSString stringWithUTF8String:__FILE__] lastPathComponent]
|
||||
|
||||
// helper to get current method name
|
||||
#define DTLogSourceMethodName [NSString stringWithUTF8String:__PRETTY_FUNCTION__]
|
||||
|
||||
// helper to get current line number
|
||||
#define DTLogSourceLineNumber __LINE__
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// DTLog.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 06.08.13.
|
||||
// Copyright (c) 2013 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DTLog.h"
|
||||
#import <asl.h>
|
||||
#import <Availability.h>
|
||||
|
||||
DTLogLevel DTCurrentLogLevel = DTLogLevelInfo;
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED > __IPHONE_6_1 && __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_7_1
|
||||
#define DTLOG_USE_NEW_ASL_METHODS 1
|
||||
#endif
|
||||
#else
|
||||
#if __MAC_OS_X_VERSION_MIN_REQUIRED > __MAC_10_9
|
||||
#define DTLOG_USE_NEW_ASL_METHODS 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if DEBUG
|
||||
|
||||
// set default handler for debug mode
|
||||
DTLogBlock DTLogHandler = ^(NSUInteger logLevel, NSString *fileName, NSUInteger lineNumber, NSString *methodName, NSString *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
|
||||
DTLogMessagev(logLevel, format, args);
|
||||
|
||||
va_end(args);
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
// set no default handler for non-DEBUG mode
|
||||
DTLogBlock DTLogHandler = NULL;
|
||||
|
||||
#endif
|
||||
|
||||
#pragma mark - Logging Functions
|
||||
|
||||
void DTLogSetLoggerBlock(DTLogBlock handler)
|
||||
{
|
||||
DTLogHandler = [handler copy];
|
||||
}
|
||||
|
||||
void DTLogSetLogLevel(DTLogLevel logLevel)
|
||||
{
|
||||
DTCurrentLogLevel = logLevel;
|
||||
}
|
||||
|
||||
void DTLogMessagev(DTLogLevel logLevel, NSString *format, va_list args)
|
||||
{
|
||||
NSString *facility = [[NSBundle mainBundle] bundleIdentifier];
|
||||
aslclient client = asl_open(NULL, [facility UTF8String], ASL_OPT_STDERR); // also log to stderr
|
||||
|
||||
aslmsg msg = asl_new(ASL_TYPE_MSG);
|
||||
asl_set(msg, ASL_KEY_READ_UID, "-1"); // without this the message cannot be found by asl_search
|
||||
|
||||
// convert to via NSString, since printf does not know %@
|
||||
NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
|
||||
|
||||
asl_log(client, msg, (int)logLevel, "%s", [message UTF8String]);
|
||||
|
||||
asl_free(msg);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void DTLogMessage(DTLogLevel logLevel, NSString *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
|
||||
DTLogMessagev(logLevel, format, args);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
NSArray *DTLogGetMessages(void)
|
||||
{
|
||||
aslmsg query, message;
|
||||
int index;
|
||||
const char *key, *val;
|
||||
|
||||
NSString *facility = [[NSBundle mainBundle] bundleIdentifier];
|
||||
|
||||
query = asl_new(ASL_TYPE_QUERY);
|
||||
|
||||
// search only for current app messages
|
||||
asl_set_query(query, ASL_KEY_FACILITY, [facility UTF8String], ASL_QUERY_OP_EQUAL);
|
||||
|
||||
aslresponse response = asl_search(NULL, query);
|
||||
|
||||
NSMutableArray *tmpArray = [NSMutableArray array];
|
||||
|
||||
#if DTLOG_USE_NEW_ASL_METHODS
|
||||
while ((message = asl_next(response)))
|
||||
#else
|
||||
while ((message = aslresponse_next(response)))
|
||||
#endif
|
||||
{
|
||||
NSMutableDictionary *tmpDict = [NSMutableDictionary dictionary];
|
||||
|
||||
for (index = 0; ((key = asl_key(message, index))); index++)
|
||||
{
|
||||
NSString *keyString = [NSString stringWithUTF8String:(char *)key];
|
||||
|
||||
val = asl_get(message, key);
|
||||
|
||||
NSString *string = val?[NSString stringWithUTF8String:val]:@"";
|
||||
tmpDict[keyString] = string;
|
||||
}
|
||||
|
||||
[tmpArray addObject:tmpDict];
|
||||
}
|
||||
|
||||
asl_free(query);
|
||||
#if DTLOG_USE_NEW_ASL_METHODS
|
||||
asl_release(response);
|
||||
#else
|
||||
aslresponse_free(response);
|
||||
#endif
|
||||
|
||||
if ([tmpArray count])
|
||||
{
|
||||
return [tmpArray copy];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
//
|
||||
// DTVersion.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 11/25/11.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
Class that represents a version number comprised of major, minor and maintenance number separated by dots. For example "1.2.2".
|
||||
This encapsulation simplifies comparing versions against each other. Sub-numbers that are omitted on creating a `DTVersion` are assumed to be 0.
|
||||
*/
|
||||
@interface DTVersion : NSObject
|
||||
{
|
||||
NSUInteger _major;
|
||||
NSUInteger _minor;
|
||||
NSUInteger _maintenance;
|
||||
NSUInteger _build;
|
||||
}
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Properties
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
The major version number
|
||||
*/
|
||||
@property (nonatomic, readonly) NSUInteger major;
|
||||
|
||||
/**
|
||||
The minor version number
|
||||
*/
|
||||
@property (nonatomic, readonly) NSUInteger minor;
|
||||
|
||||
|
||||
/**
|
||||
The maintenance/hotfix version number
|
||||
*/
|
||||
@property (nonatomic, readonly) NSUInteger maintenance;
|
||||
|
||||
/**
|
||||
The build number
|
||||
*/
|
||||
@property (nonatomic, readonly) NSUInteger build;
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Creating Versions
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
Initializes the receiver with major, minor and maintenance version.
|
||||
@param major The major version number
|
||||
@param minor The minor version number
|
||||
@param maintenance The maintainance/hotfix version number
|
||||
@returns The initialized `DTVersion`
|
||||
*/
|
||||
- (DTVersion *)initWithMajor:(NSUInteger)major minor:(NSUInteger)minor maintenance:(NSUInteger)maintenance;
|
||||
|
||||
/**
|
||||
Initializes the receiver with major, minor and maintenance version.
|
||||
@param major The major version number
|
||||
@param minor The minor version number
|
||||
@param maintenance The maintainance/hotfix version number
|
||||
@param build The build number
|
||||
@returns The initialized `DTVersion`
|
||||
*/
|
||||
- (DTVersion *)initWithMajor:(NSUInteger)major minor:(NSUInteger)minor maintenance:(NSUInteger)maintenance build:(NSUInteger)build;
|
||||
|
||||
/**
|
||||
creates and returns a DTVersion object initialized using the provided string
|
||||
@param versionString The `NSString` to create a `DTVersion` from
|
||||
@returns A DTVersion object or <code>nil</code> if the string is not a valid version number
|
||||
*/
|
||||
+ (DTVersion *)versionWithString:(NSString *)versionString;
|
||||
|
||||
/**
|
||||
creates and retuns a DTVersion object initialized with the version information of the current application
|
||||
@returns A DTVersion object or <code>nil</code> if the string of the current application is not a valid version number
|
||||
*/
|
||||
+ (DTVersion *)appBundleVersion;
|
||||
|
||||
/**
|
||||
creates and retuns a DTVersion object initialized with the version information of the operating system
|
||||
@returns A DTVersion object or <code>nil</code> if the string of the current application is not a valid version number
|
||||
*/
|
||||
+ (DTVersion *)osVersion;
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Comparing Versions
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
@param versionString The OS version as `NSString` to compare the receiver to
|
||||
@returns <code>true</code> if the given version string is valid and less then the osVersion
|
||||
*/
|
||||
+ (BOOL)osVersionIsLessThen:(NSString *)versionString;
|
||||
|
||||
/**
|
||||
@param versionString The OS version as `NSString` to compare the receiver to
|
||||
@returns <code>true</code> if the given version string is valid and greater then the osVersion
|
||||
*/
|
||||
+ (BOOL)osVersionIsGreaterThen:(NSString *)versionString;
|
||||
|
||||
/**
|
||||
@param version The `DTVersion` to compare the receiver to
|
||||
@returns <code>true</code> if the give version is less then this version
|
||||
*/
|
||||
- (BOOL)isLessThenVersion:(DTVersion *)version;
|
||||
|
||||
/**
|
||||
@param version The `DTVersion` to compare the receiver to
|
||||
@returns <code>true</code> if the give version is greater then this version
|
||||
*/
|
||||
- (BOOL)isGreaterThenVersion:(DTVersion *)version;
|
||||
|
||||
/**
|
||||
@param versionString The version as `NSString` to compare the receiver to
|
||||
@returns <code>true</code> if the give version is less then this version string
|
||||
*/
|
||||
- (BOOL)isLessThenVersionString:(NSString *)versionString;
|
||||
|
||||
/**
|
||||
@param versionString The version as `NSString` to compare the receiver to
|
||||
* @returns <code>true</code> if the give version is greater then version string
|
||||
*/
|
||||
- (BOOL)isGreaterThenVersionString:(NSString *)versionString;
|
||||
|
||||
/**
|
||||
Compares the receiver against a passed `DTVersion` instance
|
||||
@param version The `DTVersion` to compare the receiver to
|
||||
@returns `YES` is the versions are equal
|
||||
*/
|
||||
- (BOOL)isEqualToVersion:(DTVersion *)version;
|
||||
|
||||
/**
|
||||
Compares the receiver against a passed version as `NSString`
|
||||
@param versionString The version as `NSString` to compare the receiver to
|
||||
@returns `YES` is the versions are equal
|
||||
*/
|
||||
- (BOOL)isEqualToString:(NSString *)versionString;
|
||||
|
||||
/**
|
||||
Compares the receiver against a passed object
|
||||
@param object An object of either `NSString` or `DTVersion`
|
||||
@returns `YES` is the versions are equal
|
||||
*/
|
||||
- (BOOL)isEqual:(id)object;
|
||||
|
||||
/**
|
||||
Compares the receiver against a passed `DTVersion` instance
|
||||
@param version The `DTVersion` to compare the receiver to
|
||||
@returns The comparison result
|
||||
*/
|
||||
- (NSComparisonResult)compare:(DTVersion *)version;
|
||||
|
||||
@end
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
//
|
||||
// DTVersion.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik
|
||||
// Copyright 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DTVersion.h"
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
#import <UIKit/UIKit.h>
|
||||
#endif
|
||||
|
||||
@implementation DTVersion
|
||||
|
||||
#pragma mark Creating Versions
|
||||
|
||||
- (DTVersion *)initWithMajor:(NSUInteger)major minor:(NSUInteger)minor maintenance:(NSUInteger)maintenance build:(NSUInteger)build
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_major = major;
|
||||
_minor = minor;
|
||||
_maintenance = maintenance;
|
||||
_build = build;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (DTVersion *)initWithMajor:(NSUInteger)major minor:(NSUInteger)minor maintenance:(NSUInteger)maintenance
|
||||
{
|
||||
return [self initWithMajor:major minor:minor maintenance:maintenance build:0];
|
||||
}
|
||||
|
||||
+ (DTVersion *)versionWithString:(NSString*)versionString
|
||||
{
|
||||
if (!versionString)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSInteger major = 0;
|
||||
NSInteger minor = 0;
|
||||
NSInteger maintenance = 0;
|
||||
NSInteger build = 0;
|
||||
|
||||
NSError *error;
|
||||
NSString *pattern = @"^(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?(?:\\.(\\d+))?(?:$|\\s)";
|
||||
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern
|
||||
options:NSRegularExpressionCaseInsensitive
|
||||
error:&error];
|
||||
|
||||
NSTextCheckingResult *match = [regex firstMatchInString:versionString
|
||||
options:0
|
||||
range:NSMakeRange(0, [versionString length])];
|
||||
|
||||
if (!match)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
for (NSUInteger i = 1; i < match.numberOfRanges; i++)
|
||||
{
|
||||
NSRange range = [match rangeAtIndex:i];
|
||||
if (range.location == NSNotFound)
|
||||
{
|
||||
break;
|
||||
}
|
||||
NSUInteger value = [[versionString substringWithRange:range] integerValue];
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 1:
|
||||
major = value;
|
||||
break;
|
||||
case 2:
|
||||
minor = value;
|
||||
break;
|
||||
case 3:
|
||||
maintenance = value;
|
||||
break;
|
||||
case 4:
|
||||
build = value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (major >= 0 &&
|
||||
minor >= 0 &&
|
||||
maintenance >= 0 &&
|
||||
build >= 0)
|
||||
{
|
||||
return [[DTVersion alloc] initWithMajor:major minor:minor maintenance:maintenance build:build];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (DTVersion*)appBundleVersion
|
||||
{
|
||||
NSDictionary *info = [[NSBundle mainBundle] infoDictionary];
|
||||
NSString *version = info[@"CFBundleVersion"];
|
||||
|
||||
return [DTVersion versionWithString:version];
|
||||
}
|
||||
|
||||
+ (DTVersion *)osVersion
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
static DTVersion *version = nil;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
#if TARGET_OS_IPHONE && !TARGET_OS_TV && !TARGET_OS_WATCH
|
||||
NSString *versionStr = [[UIDevice currentDevice] systemVersion];
|
||||
version = [DTVersion versionWithString:versionStr];
|
||||
#else
|
||||
NSString *versionStr = [[NSProcessInfo processInfo] operatingSystemVersionString];
|
||||
versionStr = [versionStr stringByReplacingOccurrencesOfString:@"Version" withString:@""];
|
||||
versionStr = [versionStr stringByReplacingOccurrencesOfString:@"Build" withString:@""];
|
||||
versionStr = [versionStr stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
version = [DTVersion versionWithString:versionStr];
|
||||
#endif
|
||||
});
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
#pragma mark Comparing Versions
|
||||
|
||||
+ (BOOL)osVersionIsLessThen:(NSString *)versionString
|
||||
{
|
||||
return [[DTVersion osVersion] isLessThenVersionString:versionString];
|
||||
}
|
||||
|
||||
+ (BOOL)osVersionIsGreaterThen:(NSString *)versionString
|
||||
{
|
||||
return [[DTVersion osVersion] isGreaterThenVersionString:versionString];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)isLessThenVersion:(DTVersion *)version
|
||||
{
|
||||
int result = [self compare:version] == NSOrderedAscending;
|
||||
//DDLogVerbose(@"%@ < %@? %d = %@", self, version, result, result ? @"YES" : @"NO");
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)isGreaterThenVersion:(DTVersion *)version
|
||||
{
|
||||
return [self compare:version] == NSOrderedDescending;
|
||||
}
|
||||
|
||||
- (BOOL)isLessThenVersionString:(NSString *)versionString
|
||||
{
|
||||
return [self isLessThenVersion:[DTVersion versionWithString:versionString]];
|
||||
}
|
||||
|
||||
- (BOOL)isGreaterThenVersionString:(NSString *)versionString
|
||||
{
|
||||
return [self isGreaterThenVersion:[DTVersion versionWithString:versionString]];
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (BOOL)isEqualToVersion:(DTVersion *)version
|
||||
{
|
||||
return (self.major == version.major) && (self.minor == version.minor) && (self.maintenance == version.maintenance);
|
||||
}
|
||||
|
||||
- (BOOL)isEqualToString:(NSString *)versionString
|
||||
{
|
||||
DTVersion *versionToTest = [DTVersion versionWithString:versionString];
|
||||
return [self isEqualToVersion:versionToTest];
|
||||
}
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
NSUInteger hash = self.major;
|
||||
hash = hash * 31u + self.minor;
|
||||
hash = hash * 31u + self.maintenance;
|
||||
hash = hash * 31u + self.build;
|
||||
return hash;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
if ([object isKindOfClass:[DTVersion class]])
|
||||
{
|
||||
return [self isEqualToVersion:(DTVersion*)object];
|
||||
}
|
||||
if ([object isKindOfClass:[NSString class]])
|
||||
{
|
||||
return [self isEqualToString:(NSString*)object];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSComparisonResult)compare:(DTVersion *)version
|
||||
{
|
||||
if (version == nil)
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
|
||||
if (self.major < version.major)
|
||||
{
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
if (self.major > version.major)
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
if (self.minor < version.minor)
|
||||
{
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
if (self.minor > version.minor)
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
if (self.maintenance < version.maintenance)
|
||||
{
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
if (self.maintenance > version.maintenance)
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
if (self.build < version.build)
|
||||
{
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
if (self.build > version.build)
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
|
||||
|
||||
return NSOrderedSame;
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
if (_build > 0)
|
||||
{
|
||||
return [NSString stringWithFormat:@"%d.%d.%d.%d", (int)_major, (int)_minor, (int)_maintenance, (int)_build];
|
||||
}
|
||||
if (_maintenance > 0)
|
||||
{
|
||||
return [NSString stringWithFormat:@"%d.%d.%d", (int)_major, (int)_minor, (int)_maintenance];
|
||||
}
|
||||
return [NSString stringWithFormat:@"%d.%d", (int)_major, (int)_minor];
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
return [[DTVersion allocWithZone:zone] initWithMajor:_major minor:_minor maintenance:_maintenance build:_build];
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
@synthesize major = _major;
|
||||
@synthesize minor = _minor;
|
||||
@synthesize maintenance = _maintenance;
|
||||
@synthesize build = _build;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// DTWeakSupport.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 6/3/13.
|
||||
// Copyright (c) 2013 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
/**
|
||||
Useful defines for building code the compiles with zeroing weak references if the deployment target allows it. This is possible from minimum supported iOS 5.0 and OS X 10.7 and above. Note that on OS X 10.7 some AppKit classes do not support having a weak ref, e.g. NSWindowController or NSViewController.
|
||||
*/
|
||||
|
||||
#import <Availability.h>
|
||||
|
||||
#if __has_feature(objc_arc_weak)
|
||||
|
||||
// zeroing weak refs are supported for ivars and properties
|
||||
#define DT_WEAK_VARIABLE __weak
|
||||
#define DT_WEAK_PROPERTY weak
|
||||
|
||||
#elif __has_feature(objc_arc)
|
||||
|
||||
/// zeroing weak refs not supported, fall back to unsafe unretained and assigning
|
||||
#define DT_WEAK_VARIABLE __unsafe_unretained
|
||||
#define DT_WEAK_PROPERTY assign
|
||||
|
||||
#else
|
||||
|
||||
// define something, as this header might be included in a non-ARC project for using compiled code from an ARC static lib
|
||||
#define DT_WEAK_VARIABLE
|
||||
#define DT_WEAK_PROPERTY assign
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// NSArray+error.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 6/15/10.
|
||||
// Copyright 2010 Drobnik.com. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
A collection of useful additions for `NSArray` to deal with Property Lists and also to get error handling for malformed data.
|
||||
*/
|
||||
|
||||
@interface NSArray (DTError)
|
||||
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Property List Error Handling
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
Creates and returns an array found in a file specified by a given URL.
|
||||
|
||||
@param URL An `NSURL`. The file identified by URL must contain a string representation of a property list whose root object is an array.
|
||||
@param error If an error occurs, upon returns contains an NSError object that describes the problem. If you are not interested in possible errors, pass in `NULL`.
|
||||
@return A new array that contains the array at path, or `nil` if there is a file error or if the contents of the file are an invalid representation of a array.
|
||||
*/
|
||||
+ (NSArray *)arrayWithContentsOfURL:(NSURL *)URL error:(NSError **)error;
|
||||
|
||||
|
||||
/**
|
||||
Creates and returns an array found in a file specified by a given path.
|
||||
|
||||
@param path A full or relative pathname. The file identified by path must contain a string representation of a property list whose root object is a dictionary.
|
||||
@param error If an error occurs, upon returns contains an NSError object that describes the problem. If you are not interested in possible errors, pass in `NULL`.
|
||||
@return A new dictionary that contains the dictionary at path, or `nil` if there is a file error or if the contents of the file are an invalid representation of a dictionary.
|
||||
*/
|
||||
+ (NSArray *)arrayWithContentsOfFile:(NSString *)path error:(NSError **)error;
|
||||
|
||||
|
||||
/**
|
||||
Creates and returns an array encoded in the given blob of data.
|
||||
|
||||
@param data The data object identified by data must contain a string representation of a property list whose root object is an array.
|
||||
@param error If an error occurs, upon returns contains an NSError object that describes the problem. If you are not interested in possible errors, pass in `NULL`.
|
||||
@return A new array that contains the decoded array, or `nil` if there is an error or if the contents of the file are an invalid representation of an array.
|
||||
*/
|
||||
+ (NSArray *)arrayWithContentsOfData:(NSData *)data error:(NSError **)error;
|
||||
@end
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// NSArray+DTError.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 6/15/10.
|
||||
// Copyright 2010 Drobnik.com. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSArray+DTError.h"
|
||||
#import "DTFoundationConstants.h"
|
||||
|
||||
@implementation NSArray (DTError)
|
||||
|
||||
+ (NSArray *)arrayWithContentsOfURL:(NSURL *)URL error:(NSError **)error
|
||||
{
|
||||
NSData *readData = [NSData dataWithContentsOfURL:URL options:0 error:error];
|
||||
|
||||
if (!readData)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [NSArray arrayWithContentsOfData:readData error:error];
|
||||
}
|
||||
|
||||
+ (NSArray *)arrayWithContentsOfFile:(NSString *)path error:(NSError **)error
|
||||
{
|
||||
NSURL *url = [NSURL fileURLWithPath:path];
|
||||
return [NSArray arrayWithContentsOfURL:url error:error];
|
||||
}
|
||||
|
||||
+ (NSArray *)arrayWithContentsOfData:(NSData *)data error:(NSError **)error
|
||||
{
|
||||
CFErrorRef parseError = NULL;
|
||||
NSArray *array = (__bridge_transfer NSArray *)CFPropertyListCreateWithData(kCFAllocatorDefault, (__bridge CFDataRef)data, kCFPropertyListImmutable, NULL, (CFErrorRef *)&parseError);
|
||||
|
||||
if ([array isKindOfClass:[NSArray class]])
|
||||
{
|
||||
return array;
|
||||
}
|
||||
|
||||
if (parseError)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
*error = (__bridge NSError *)(parseError);
|
||||
}
|
||||
|
||||
CFRelease(parseError);
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// NSData+DTCrypto.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Stefan Gugarel on 10/3/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
Useful cryptography methods.
|
||||
*/
|
||||
|
||||
@interface NSData (DTCrypto)
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Generating HMAC Hashes
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
Generates a HMAC from the receiver using the SHA1 algorithm
|
||||
@param key The encryption key
|
||||
@returns The encrypted hash
|
||||
*/
|
||||
- (NSData *)encryptedDataUsingSHA1WithKey:(NSData *)key;
|
||||
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Digest Hashes
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
Generate an MD5 checksum from the receiver
|
||||
@returns An `NSData` containing the md5 digest.
|
||||
*/
|
||||
-(NSData *)dataWithMD5Hash;
|
||||
|
||||
/**
|
||||
Generate an SHA1 checksum from the receiver
|
||||
@returns An `NSData` containing the SHA digest.
|
||||
*/
|
||||
- (NSData *)dataWithSHA1Hash;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// NSData+DTCrypto.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Stefan Gugarel on 10/3/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSData+DTCrypto.h"
|
||||
#include <CommonCrypto/CommonHMAC.h>
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
|
||||
/**
|
||||
Common cryptography methods
|
||||
*/
|
||||
@implementation NSData (DTCrypto)
|
||||
|
||||
|
||||
- (NSData *)encryptedDataUsingSHA1WithKey:(NSData *)key
|
||||
{
|
||||
unsigned char cHMAC[CC_SHA1_DIGEST_LENGTH];
|
||||
|
||||
CCHmac(kCCHmacAlgSHA1, [key bytes], [key length], [self bytes], [self length], cHMAC);
|
||||
|
||||
return [NSData dataWithBytes:&cHMAC length:CC_SHA1_DIGEST_LENGTH];
|
||||
}
|
||||
|
||||
- (NSData *)dataWithMD5Hash
|
||||
{
|
||||
const char *cStr = [self bytes];
|
||||
uint8_t digest[CC_MD5_DIGEST_LENGTH];
|
||||
CC_MD5(cStr, (CC_LONG)[self length], digest);
|
||||
|
||||
return [NSData dataWithBytes:digest length:CC_MD5_DIGEST_LENGTH];
|
||||
}
|
||||
|
||||
- (NSData *)dataWithSHA1Hash
|
||||
{
|
||||
const char *cStr = [self bytes];
|
||||
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
|
||||
CC_SHA1( cStr, (CC_LONG)[self length], digest );
|
||||
|
||||
return [NSData dataWithBytes:digest length:CC_SHA1_DIGEST_LENGTH];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// NSDictionary+DTError.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 4/16/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
A collection of useful additions for `NSDictionary` to deal with Property Lists and also to get error handling for malformed data.
|
||||
*/
|
||||
|
||||
@interface NSDictionary (DTError)
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Property List Error Handling
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
Creates and returns a dictionary using the keys and values found in a file specified by a given URL.
|
||||
|
||||
@param URL An URL. The file identified by URL must contain a string representation of a property list whose root object is a dictionary.
|
||||
@param error If an error occurs, upon returns contains an NSError object that describes the problem. If you are not interested in possible errors, pass in `NULL`.
|
||||
@return A new dictionary that contains the dictionary at path, or `nil` if there is a file error or if the contents of the file are an invalid representation of a dictionary.
|
||||
*/
|
||||
+ (NSDictionary *)dictionaryWithContentsOfURL:(NSURL *)URL error:(NSError **)error;
|
||||
|
||||
/**
|
||||
Creates and returns a dictionary using the keys and values found in a file specified by a given path.
|
||||
|
||||
@param path A full or relative pathname. The file identified by path must contain a string representation of a property list whose root object is a dictionary.
|
||||
@param error If an error occurs, upon returns contains an NSError object that describes the problem. If you are not interested in possible errors, pass in `NULL`.
|
||||
@return A new dictionary that contains the dictionary at path, or `nil` if there is a file error or if the contents of the file are an invalid representation of a dictionary.
|
||||
*/
|
||||
+ (NSDictionary *)dictionaryWithContentsOfFile:(NSString *)path error:(NSError **)error;
|
||||
|
||||
/**
|
||||
Creates and returns a dictionary using the keys and values found in the given data.
|
||||
|
||||
@param data The data object identified by data must contain a string representation of a property list whose root object is a dictionary.
|
||||
@param error If an error occurs, upon returns contains an NSError object that describes the problem. If you are not interested in possible errors, pass in `NULL`.
|
||||
@return A new dictionary that contains the dictionary at path, or `nil` if there is a file error or if the contents of the file are an invalid representation of a dictionary.
|
||||
*/
|
||||
+ (NSDictionary *)dictionaryWithContentsOfData:(NSData *)data error:(NSError **)error;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// NSDictionary+DTError.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 4/16/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSDictionary+DTError.h"
|
||||
#import "DTFoundationConstants.h"
|
||||
|
||||
@implementation NSDictionary (DTError)
|
||||
|
||||
+ (NSDictionary *)dictionaryWithContentsOfURL:(NSURL *)URL error:(NSError **)error
|
||||
{
|
||||
NSData *readData = [NSData dataWithContentsOfURL:URL options:0 error:error];
|
||||
|
||||
if (!readData)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [NSDictionary dictionaryWithContentsOfData:readData error:error];
|
||||
}
|
||||
|
||||
+ (NSDictionary *)dictionaryWithContentsOfFile:(NSString *)path error:(NSError **)error
|
||||
{
|
||||
NSURL *url = [NSURL fileURLWithPath:path];
|
||||
return [NSDictionary dictionaryWithContentsOfURL:url error:error];
|
||||
}
|
||||
|
||||
+ (NSDictionary *)dictionaryWithContentsOfData:(NSData *)data error:(NSError **)error
|
||||
{
|
||||
CFErrorRef parseError = NULL;
|
||||
NSDictionary *dictionary = (__bridge_transfer NSDictionary *)CFPropertyListCreateWithData(kCFAllocatorDefault, (__bridge CFDataRef)data, kCFPropertyListImmutable, NULL, (CFErrorRef *)&parseError);
|
||||
|
||||
// we check if it is the correct type and only return it if it is
|
||||
if ([dictionary isKindOfClass:[NSDictionary class]])
|
||||
{
|
||||
return dictionary;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parseError)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
*error = (__bridge NSError *)parseError;
|
||||
}
|
||||
|
||||
CFRelease(parseError);
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// NSFileWrapper+DTCopying.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 10/19/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
Methods for copying file wrappers.
|
||||
*/
|
||||
@interface NSFileWrapper (DTCopying)
|
||||
|
||||
/**
|
||||
Creates a copy of the receiver by deep copying all contained sub filewrappers.
|
||||
*/
|
||||
- (NSFileWrapper *)fileWrapperByDeepCopying;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// NSFileWrapper+DTCopying.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 10/19/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSFileWrapper+DTCopying.h"
|
||||
|
||||
@implementation NSFileWrapper (DTCopying)
|
||||
|
||||
- (NSFileWrapper *)fileWrapperByDeepCopying
|
||||
{
|
||||
if ([self isDirectory])
|
||||
{
|
||||
NSMutableDictionary *subFileWrappers = [NSMutableDictionary dictionary];
|
||||
|
||||
[self.fileWrappers enumerateKeysAndObjectsUsingBlock:^(NSString *fileName, NSFileWrapper *fileWrapper, BOOL *stop) {
|
||||
NSFileWrapper *copyWrapper = [fileWrapper fileWrapperByDeepCopying];
|
||||
subFileWrappers[fileName] = copyWrapper;
|
||||
}];
|
||||
|
||||
NSFileWrapper *fileWrapper = [[NSFileWrapper alloc] initDirectoryWithFileWrappers:subFileWrappers];
|
||||
|
||||
return fileWrapper;
|
||||
}
|
||||
|
||||
NSFileWrapper *fileWrapper = [[NSFileWrapper alloc] initRegularFileWithContents:self.regularFileContents];
|
||||
return fileWrapper;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// NSMutableArray+DTMoving.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 9/27/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
Methods that add convenient moving methods to `NSMutableArray`.
|
||||
*/
|
||||
|
||||
@interface NSMutableArray (DTMoving)
|
||||
|
||||
/**
|
||||
Moves the object at the specified indexes to the new location.
|
||||
|
||||
@param indexes The indexes of the objects to move.
|
||||
@param idx The index in the mutable array at which to insert the objects.
|
||||
*/
|
||||
- (void)moveObjectsAtIndexes:(NSIndexSet *)indexes toIndex:(NSUInteger)idx;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// NSMutableArray+DTMoving.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 9/27/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSMutableArray+DTMoving.h"
|
||||
|
||||
@implementation NSMutableArray (DTMoving)
|
||||
|
||||
// credit: http://www.cocoabuilder.com/archive/cocoa/189484-nsarray-move-items-at-indexes.html
|
||||
|
||||
- (void)moveObjectsAtIndexes:(NSIndexSet *)indexes toIndex:(NSUInteger)idx
|
||||
{
|
||||
NSArray *objectsToMove = [self objectsAtIndexes:indexes];
|
||||
|
||||
// If any of the removed objects come before the index, we want to decrement the index appropriately
|
||||
idx -= [indexes countOfIndexesInRange:(NSRange){0, idx}];
|
||||
|
||||
[self removeObjectsAtIndexes:indexes];
|
||||
[self replaceObjectsInRange:(NSRange){idx,0} withObjectsFromArray:objectsToMove];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// NSString+DTFormatNumbers.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 11/25/11.
|
||||
// Copyright (c) 2011 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
A collection of category extensions for `NSString` dealing with the formatting of numbers in special contexts.
|
||||
*/
|
||||
|
||||
@interface NSString (DTFormatNumbers)
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Formatting File Sizes
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/** Formats the passed number as a byte value in a form that is pleasing to the user when displayed next to a progress bar.
|
||||
|
||||
Output numbers are rounded to one decimal place. Bytes are not abbreviated because most users might not be used to B for that. Higher units are kB, MB, GB and TB.
|
||||
|
||||
@param bytes The value of the bytes to be formatted
|
||||
@return Returns the formatted string.
|
||||
|
||||
*/
|
||||
+ (NSString *)stringByFormattingBytes:(long long)bytes;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// NSString+DTFormatNumbers.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 11/25/11.
|
||||
// Copyright (c) 2011 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSString+DTFormatNumbers.h"
|
||||
|
||||
@implementation NSString (DTFormatNumbers)
|
||||
|
||||
+ (NSString *)stringByFormattingBytes:(long long)bytes
|
||||
{
|
||||
NSArray *units = @[@"%1.0f Bytes", @"%1.1f KB", @"%1.1f MB", @"%1.1f GB", @"%1.1f TB"];
|
||||
|
||||
long long value = bytes * 10;
|
||||
for (NSUInteger i=0; i<[units count]; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
value = value/1024;
|
||||
}
|
||||
if (value < 10000)
|
||||
{
|
||||
return [NSString stringWithFormat:units[i], value/10.0];
|
||||
}
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:units[[units count]-1], value/10.0];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// NSString+DTPaths.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 2/15/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
A collection of useful additions for `NSString` to deal with paths.
|
||||
*/
|
||||
|
||||
@interface NSString (DTPaths)
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Getting Standard Paths
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/** Determines the path to the Library/Caches folder in the current application's sandbox.
|
||||
|
||||
The return value is cached on the first call.
|
||||
|
||||
@return The path to the app's Caches folder.
|
||||
*/
|
||||
+ (NSString * _Nonnull)cachesPath;
|
||||
|
||||
|
||||
/** Determines the path to the Documents folder in the current application's sandbox.
|
||||
|
||||
The return value is cached on the first call.
|
||||
|
||||
@return The path to the app's Documents folder.
|
||||
*/
|
||||
+ (NSString * _Nonnull)documentsPath;
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Getting Temporary Paths
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/** Determines the path for temporary files in the current application's sandbox.
|
||||
|
||||
The return value is cached on the first call. This value is different in Simulator than on the actual device. In Simulator you get a reference to /tmp wheras on iOS devices it is a special folder inside the application folder.
|
||||
|
||||
@return The path to the app's folder for temporary files.
|
||||
*/
|
||||
+ (NSString * _Nonnull)temporaryPath;
|
||||
|
||||
|
||||
/** Creates a unique filename that can be used for one temporary file or folder.
|
||||
|
||||
The returned string is different on every call. It is created by combining the result from temporaryPath with a unique UUID.
|
||||
|
||||
@return The generated temporary path.
|
||||
*/
|
||||
+ (NSString * _Nonnull)pathForTemporaryFile;
|
||||
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Working with Paths
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/** Appends or Increments a sequence number in brackets
|
||||
|
||||
If the receiver already has a number suffix then it is incremented. If not then (1) is added.
|
||||
|
||||
@return The incremented path
|
||||
*/
|
||||
- (NSString * _Nonnull)pathByIncrementingSequenceNumber;
|
||||
|
||||
|
||||
/** Removes a sequence number in brackets
|
||||
|
||||
If the receiver number suffix then it is removed. If not the receiver is returned.
|
||||
|
||||
@return The modified path
|
||||
*/
|
||||
- (NSString * _Nonnull)pathByDeletingSequenceNumber;
|
||||
|
||||
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,115 @@
|
||||
//
|
||||
// NSString+DTPaths.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 2/15/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSString+DTPaths.h"
|
||||
|
||||
@implementation NSString (DTPaths)
|
||||
|
||||
#pragma mark Standard Paths
|
||||
|
||||
+ (NSString *)cachesPath
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
static NSString *cachedPath;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
cachedPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
|
||||
});
|
||||
|
||||
return cachedPath;
|
||||
}
|
||||
|
||||
+ (NSString *)documentsPath
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
static NSString *cachedPath;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
cachedPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
|
||||
});
|
||||
|
||||
return cachedPath;
|
||||
}
|
||||
|
||||
#pragma mark Temporary Paths
|
||||
|
||||
+ (NSString *)temporaryPath
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
static NSString *cachedPath;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
cachedPath = NSTemporaryDirectory();
|
||||
});
|
||||
|
||||
return cachedPath;
|
||||
}
|
||||
|
||||
+ (NSString *)pathForTemporaryFile
|
||||
{
|
||||
CFUUIDRef newUniqueId = CFUUIDCreate(kCFAllocatorDefault);
|
||||
CFStringRef newUniqueIdString = CFUUIDCreateString(kCFAllocatorDefault, newUniqueId);
|
||||
NSString *tmpPath = [[NSString temporaryPath] stringByAppendingPathComponent:(__bridge NSString *)newUniqueIdString];
|
||||
CFRelease(newUniqueId);
|
||||
CFRelease(newUniqueIdString);
|
||||
|
||||
return tmpPath;
|
||||
}
|
||||
|
||||
#pragma mark Working with Paths
|
||||
|
||||
- (NSString *)pathByIncrementingSequenceNumber
|
||||
{
|
||||
NSString *baseName = [self stringByDeletingPathExtension];
|
||||
NSString *extension = [self pathExtension];
|
||||
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\(([0-9]+)\\)$" options:0 error:NULL];
|
||||
__block NSInteger sequenceNumber = 0;
|
||||
|
||||
[regex enumerateMatchesInString:baseName options:0 range:NSMakeRange(0, [baseName length]) usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop){
|
||||
|
||||
NSRange range = [match rangeAtIndex:1]; // first capture group
|
||||
NSString *substring= [self substringWithRange:range];
|
||||
|
||||
sequenceNumber = [substring integerValue];
|
||||
*stop = YES;
|
||||
}];
|
||||
|
||||
NSString *nakedName = [baseName pathByDeletingSequenceNumber];
|
||||
|
||||
if ([extension isEqualToString:@""])
|
||||
{
|
||||
return [nakedName stringByAppendingFormat:@"(%d)", (int)sequenceNumber+1];
|
||||
}
|
||||
|
||||
return [[nakedName stringByAppendingFormat:@"(%d)", (int)sequenceNumber+1] stringByAppendingPathExtension:extension];
|
||||
}
|
||||
|
||||
- (NSString *)pathByDeletingSequenceNumber
|
||||
{
|
||||
NSString *baseName = [self stringByDeletingPathExtension];
|
||||
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\([0-9]+\\)$" options:0 error:NULL];
|
||||
__block NSRange range = NSMakeRange(NSNotFound, 0);
|
||||
|
||||
[regex enumerateMatchesInString:baseName options:0 range:NSMakeRange(0, [baseName length]) usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop) {
|
||||
|
||||
range = [match range];
|
||||
|
||||
*stop = YES;
|
||||
}];
|
||||
|
||||
if (range.location != NSNotFound)
|
||||
{
|
||||
return [self stringByReplacingCharactersInRange:range withString:@""];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// NSString+DTURLEncoding.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 4/16/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
A collection of useful additions for `NSString` to deal with URL encoding.
|
||||
*/
|
||||
|
||||
@interface NSString (DTURLEncoding)
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Encoding Strings for URLs
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
/** Encoding suitable for use in URLs.
|
||||
|
||||
stringByAddingPercentEscapes does not replace serveral characters which are problematics in URLs.
|
||||
|
||||
@return The encoded version of the receiver.
|
||||
*/
|
||||
- (NSString *)stringByURLEncoding;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// NSString+DTURLEncoding.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 4/16/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSString+DTURLEncoding.h"
|
||||
|
||||
@implementation NSString (DTURLEncoding)
|
||||
|
||||
- (NSString *)stringByURLEncoding
|
||||
{
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0
|
||||
return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef)self, NULL, (CFStringRef)@"!*'();:@&=+$,/?%#[]", kCFStringEncodingUTF8);
|
||||
#else
|
||||
|
||||
static NSCharacterSet *allowedCharacters = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSMutableCharacterSet *tmpSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
|
||||
|
||||
// add some characters that might have special meaning
|
||||
[tmpSet removeCharactersInString: @"!*'();:@&=+$,/?%#[]"];
|
||||
allowedCharacters = [tmpSet copy];
|
||||
});
|
||||
|
||||
return [self stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters];
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// NSString+DTUtilities.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 4/16/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
A collection of utility additions for `NSString`.
|
||||
*/
|
||||
|
||||
@interface NSString (DTUtilities)
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Working with Identifiers
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/** Creates a new string that contains a generated UUID.
|
||||
|
||||
@return The path to the app's Caches folder.
|
||||
*/
|
||||
+ (NSString *)stringWithUUID;
|
||||
|
||||
|
||||
/**-------------------------------------------------------------------------------------
|
||||
@name Working with Checksums
|
||||
---------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/** creates an MD5 checksum
|
||||
|
||||
@return returns an MD5 hash for the receiver.
|
||||
*/
|
||||
- (NSString *)md5Checksum;
|
||||
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// NSString+DTUtilities.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 4/16/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSString+DTUtilities.h"
|
||||
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
|
||||
@implementation NSString (DTUtilities)
|
||||
|
||||
+ (NSString *)stringWithUUID
|
||||
{
|
||||
CFUUIDRef uuidObj = CFUUIDCreate(nil);//create a new UUID
|
||||
|
||||
//get the string representation of the UUID
|
||||
NSString *uuidString = (__bridge_transfer NSString *)CFUUIDCreateString(nil, uuidObj);
|
||||
|
||||
CFRelease(uuidObj);
|
||||
return uuidString;
|
||||
}
|
||||
|
||||
- (NSString *)md5Checksum
|
||||
{
|
||||
const char *cStr = [self UTF8String];
|
||||
unsigned char result [CC_MD5_DIGEST_LENGTH];
|
||||
CC_MD5( cStr, (CC_LONG)strlen(cStr), result );
|
||||
|
||||
return [NSString stringWithFormat: @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
|
||||
result[0], result[1],
|
||||
result[2], result[3],
|
||||
result[4], result[5],
|
||||
result[6], result[7],
|
||||
result[8], result[9],
|
||||
result[10], result[11],
|
||||
result[12], result[13],
|
||||
result[14], result[15]
|
||||
];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// NSURL+DTComparing.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 13.11.12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
Category for comparing URLs.
|
||||
|
||||
Contrary to what you might think isEqual: does not work properly in many cases.
|
||||
*/
|
||||
|
||||
@interface NSURL (DTComparing)
|
||||
|
||||
/**
|
||||
Compares the receiver with another URL
|
||||
@param URL another URL
|
||||
@returns `YES` if the receiver is equivalent with the passed URL
|
||||
*/
|
||||
- (BOOL)isEqualToURL:(NSURL *)URL;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// NSURL+DTComparing.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 13.11.12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSURL+DTComparing.h"
|
||||
|
||||
@implementation NSURL (DTComparing)
|
||||
|
||||
- (BOOL)isEqualToURL:(NSURL *)URL
|
||||
{
|
||||
// scheme must be same
|
||||
if (![[self scheme] isEqualToString:[URL scheme]])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
// host must be same
|
||||
if (![[self host] isEqualToString:[URL host]])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
// path must be same
|
||||
if (![[self path] isEqualToString:[URL path]])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// NSURL+DTUnshorten.h
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 6/2/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/** Method for getting the full length URL for a shortened one.
|
||||
|
||||
For example:
|
||||
|
||||
NSURL *url = [NSURL URLWithString:@"buff.ly/L4uGoza"];
|
||||
|
||||
[url unshortenWithCompletion:^(NSURL *url) {
|
||||
NSLog(@"Unshortened: %@", url);
|
||||
}];
|
||||
|
||||
*/
|
||||
|
||||
typedef void (^NSURLUnshortenCompletionHandler)(NSURL *);
|
||||
|
||||
@interface NSURL (DTUnshorten)
|
||||
|
||||
/**
|
||||
Unshortens the receiver and returns the long URL via the completion handler.
|
||||
|
||||
Results are cached and therefore a subsequent call for the same receiver will return instantly if the result is still present in the cache.
|
||||
@param completion The completion handler
|
||||
*/
|
||||
- (void)unshortenWithCompletion:(NSURLUnshortenCompletionHandler)completion;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// NSURL+DTUnshorten.m
|
||||
// DTFoundation
|
||||
//
|
||||
// Created by Oliver Drobnik on 6/2/12.
|
||||
// Copyright (c) 2012 Cocoanetics. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSURL+DTUnshorten.h"
|
||||
|
||||
@implementation NSURL (DTUnshorten)
|
||||
|
||||
- (void)unshortenWithCompletion:(NSURLUnshortenCompletionHandler)completion
|
||||
{
|
||||
static NSCache *unshortenCache = nil;
|
||||
static dispatch_queue_t shortenQueue = NULL;
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
unshortenCache = [[NSCache alloc] init];
|
||||
shortenQueue = dispatch_queue_create("DTUnshortenQueue", 0);
|
||||
});
|
||||
|
||||
NSURL *shortURL = self;
|
||||
|
||||
// assume HTTP if scheme is missing
|
||||
if (![self scheme])
|
||||
{
|
||||
NSString *str = [@"http://" stringByAppendingString:[self absoluteString]];
|
||||
shortURL = [NSURL URLWithString:str];
|
||||
}
|
||||
|
||||
dispatch_async(shortenQueue, ^{
|
||||
// look into cache first
|
||||
NSURL *longURL = [unshortenCache objectForKey:shortURL];
|
||||
|
||||
// nothing cached, load it
|
||||
if (!longURL)
|
||||
{
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:shortURL];
|
||||
request.HTTPMethod = @"HEAD";
|
||||
|
||||
void (^networkCompletion)(NSURL *responseURL) = ^void(NSURL *responseURL) {
|
||||
// cache result
|
||||
|
||||
if (responseURL)
|
||||
{
|
||||
[unshortenCache setObject:responseURL forKey:shortURL];
|
||||
}
|
||||
|
||||
if (completion)
|
||||
{
|
||||
completion(responseURL);
|
||||
}
|
||||
};
|
||||
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0
|
||||
NSError *error = nil;
|
||||
NSHTTPURLResponse *response = nil;
|
||||
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
|
||||
networkCompletion([response URL]);
|
||||
#else
|
||||
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * data, NSURLResponse * response, NSError * error) {
|
||||
networkCompletion([response URL]);
|
||||
}] resume];
|
||||
#endif
|
||||
} else if (completion) {
|
||||
|
||||
completion(longURL);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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
|
||||
Generated
+22
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2011, Oliver Drobnik All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
About DTFoundation
|
||||
==================
|
||||
|
||||
[](https://travis-ci.org/Cocoanetics/DTFoundation)
|
||||
|
||||
DTFoundation is a collection of utility methods and category extensions that *Cocoanetics* is standardizing on. This should evolve into a toolset of well-documented and -tested code to accelerate future development.
|
||||
|
||||
Documentation can be [browsed online](https://docs.cocoanetics.com/DTFoundation) or installed in your Xcode Organizer via the [Atom Feed URL](https://docs.cocoanetics.com/DTFoundation/DTFoundation.atom).
|
||||
|
||||
Methods, categories and functions are grouped into Subspecs. The grouping determined by the required dependencies. Please refer the programming guides linked from the documentation site for their contents.
|
||||
|
||||
- **Core:** Enhancements for Apple frameworks and classes which are usable on Mac and iOS.
|
||||
- **UIKit:** Enhancements for UIKit
|
||||
- **UIKit Blocks Additions:** Adding blocks-support to UIKit
|
||||
- **AppKit:** Enhancements for AppKit
|
||||
- **DTAWS:** Talking to Amazon Web Services
|
||||
- **DTASN1:** Event-based parser for ASN.1 data
|
||||
- **DTHTMLParser:** Event-based HTML parser based on libxml2
|
||||
- **DTReachability:** Block-based Reachability
|
||||
- **DTSidePanel:** Side-Panel view controller
|
||||
- **DTSQLite:** Objective-C wrapper for SQLite
|
||||
- **DTUTI:** UTI methods
|
||||
- **DTZipArchive:** Handing of Pkzip and GZip files
|
||||
- **DTProgressHUD:** Displaying informations or progress (in the middle of the screen)
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
It is open source and covered by a standard 2-clause BSD license. That means you have to mention *Cocoanetics* as the original author of this code and reproduce the LICENSE text inside your app.
|
||||
|
||||
You can purchase a [Non-Attribution-License](http://www.cocoanetics.com/order/?product=DTFoundation%20Non-Attribution%20License) for 75 Euros for not having to include the LICENSE text.
|
||||
|
||||
We also accept sponsorship for specific enhancements which you might need. Please [contact us via email](mailto:oliver@cocoanetics.com?subject=DTFoundation) for inquiries.
|
||||
Reference in New Issue
Block a user