Commit 546b15f0 authored by 汪洋's avatar 汪洋

删除Phobos对GMKit的依赖

parent fd789797
PODS:
- Base64nl (1.2)
- GMCache (0.1.1):
- TMCache (~> 2.1.0)
- GMFoundation (0.0.2):
- Base64nl (= 1.2)
- GMKit (0.7.11):
- GMFoundation
- GMOCConstant
- Masonry (= 1.0.1)
- SDWebImage (= 3.7.6)
- GMOCConstant (0.0.3)
- GMPhobos (0.2.26):
- GMCache (~> 0.1.1)
- GMKit
- Masonry (1.0.1)
- SDWebImage (3.7.6):
- SDWebImage/Core (= 3.7.6)
- SDWebImage/Core (3.7.6)
- TMCache (2.1.0)
DEPENDENCIES:
......@@ -27,14 +13,8 @@ EXTERNAL SOURCES:
:path: "../"
SPEC CHECKSUMS:
Base64nl: a497bdcd1c01ea793d36b399016195a8713c0e95
GMCache: 73855b613b9d7e34f4f37ad425e8b8153b760c04
GMFoundation: 08b2e6e12c211ed37aa5dce3588f645a133b9165
GMKit: 04a30d67c6b5468f07c8d9f60d0f8b12dd90b162
GMOCConstant: 39371248b4d8d54929391bfcd2c5883776436c4b
GMPhobos: 853ef8239bee6c908286c9cbb3d8b39fa1c70e97
Masonry: a1a931a0d08870ed8ae415a2ea5ea68ebcac77df
SDWebImage: c325cf02c30337336b95beff20a13df489ec0ec9
GMPhobos: 613c57b5b302f8238db03a6090b9022524abc710
TMCache: 95ebcc9b3c7e90fb5fd8fc3036cba3aa781c9bed
PODFILE CHECKSUM: 281ed5ce2f0e8e2e49cc13b1e4726e6686ac6095
......
//
// Base64.h
//
// Version 1.2
//
// Created by Nick Lockwood on 12/01/2012.
// Copyright (C) 2012 Charcoal Design
//
// Distributed under the permissive zlib License
// Get the latest version from here:
//
// https://github.com/nicklockwood/Base64
//
// 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 <Foundation/Foundation.h>
@interface NSData (Base64)
+ (NSData *)dataWithBase64EncodedString:(NSString *)string;
- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth;
- (NSString *)base64EncodedString;
@end
@interface NSString (Base64)
+ (NSString *)stringWithBase64EncodedString:(NSString *)string;
- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth;
- (NSString *)base64EncodedString;
- (NSString *)base64DecodedString;
- (NSData *)base64DecodedData;
@end
//
// Base64.m
//
// Version 1.2
//
// Created by Nick Lockwood on 12/01/2012.
// Copyright (C) 2012 Charcoal Design
//
// Distributed under the permissive zlib License
// Get the latest version from here:
//
// https://github.com/nicklockwood/Base64
//
// 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 aacknowledgment 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 "Base64.h"
#pragma GCC diagnostic ignored "-Wselector"
#import <Availability.h>
#if !__has_feature(objc_arc)
#error This library requires automatic reference counting
#endif
@implementation NSData (Base64)
+ (NSData *)dataWithBase64EncodedString:(NSString *)string
{
if (![string length]) return nil;
NSData *decoded = nil;
#if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_9 || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
if (![NSData instancesRespondToSelector:@selector(initWithBase64EncodedString:options:)])
{
decoded = [[self alloc] initWithBase64Encoding:[string stringByReplacingOccurrencesOfString:@"[^A-Za-z0-9+/=]" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, [string length])]];
}
else
#endif
{
decoded = [[self alloc] initWithBase64EncodedString:string options:NSDataBase64DecodingIgnoreUnknownCharacters];
}
return [decoded length]? decoded: nil;
}
- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth
{
if (![self length]) return nil;
NSString *encoded = nil;
#if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_9 || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
if (![NSData instancesRespondToSelector:@selector(base64EncodedStringWithOptions:)])
{
encoded = [self base64Encoding];
}
else
#endif
{
switch (wrapWidth)
{
case 64:
{
return [self base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
}
case 76:
{
return [self base64EncodedStringWithOptions:NSDataBase64Encoding76CharacterLineLength];
}
default:
{
encoded = [self base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
}
}
}
if (!wrapWidth || wrapWidth >= [encoded length])
{
return encoded;
}
wrapWidth = (wrapWidth / 4) * 4;
NSMutableString *result = [NSMutableString string];
for (NSUInteger i = 0; i < [encoded length]; i+= wrapWidth)
{
if (i + wrapWidth >= [encoded length])
{
[result appendString:[encoded substringFromIndex:i]];
break;
}
[result appendString:[encoded substringWithRange:NSMakeRange(i, wrapWidth)]];
[result appendString:@"\r\n"];
}
return result;
}
- (NSString *)base64EncodedString
{
return [self base64EncodedStringWithWrapWidth:0];
}
@end
@implementation NSString (Base64)
+ (NSString *)stringWithBase64EncodedString:(NSString *)string
{
NSData *data = [NSData dataWithBase64EncodedString:string];
if (data)
{
return [[self alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
return nil;
}
- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth
{
NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
return [data base64EncodedStringWithWrapWidth:wrapWidth];
}
- (NSString *)base64EncodedString
{
NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
return [data base64EncodedString];
}
- (NSString *)base64DecodedString
{
return [NSString stringWithBase64EncodedString:self];
}
- (NSData *)base64DecodedData
{
return [NSData dataWithBase64EncodedString:self];
}
@end
Base64
Version 1.2, Feburary 7th, 2014
Copyright (C) 2012 Charcoal Design
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.
\ No newline at end of file
NOTE: As of iOS 7 and Mac OS 10.9, this library is not longer needed
----------------------------------------------------------------------
In the iOS 7 and Mac OS 10.9 SDKs, Apple introduced new base64 methods on NSData that make it unnecessary to use a 3rd party base 64 decoding library. What's more, they exposed access to private base64 methods that are retrospectively available back as far as IOS 4 and Mac OS 6.
Although use of this library is no longer required, you may still find it useful, as it abstracts the complexity of supporting the deprecated Base64 methods for older OS versions, and also provides some additional utility functions, such as arbitrary wrap widths and NSString encoding.
Purpose
--------------
Base64 is a set of categories that provide methods to encode and decode data as a base-64-encoded string.
Supported OS & SDK Versions
-----------------------------
* Supported build target - iOS 7.0 / Mac OS 10.9 (Xcode 5.0, Apple LLVM compiler 5.0)
* Earliest supported deployment target - iOS 5.0 / Mac OS 10.7
* Earliest compatible deployment target - iOS 4.3 / Mac OS 10.6
NOTE: 'Supported' means that the library has been tested with this version. 'Compatible' means that the library should work on this iOS version (i.e. it doesn't rely on any unavailable SDK features) but is no longer being tested for compatibility and may require tweaking or bug fixes to run correctly.
ARC Compatibility
------------------
As of version 1.1, Base64 requires ARC. If you wish to use Base64 in a non-ARC project, just add the -fobjc-arc compiler flag to the Base64.m file. To do this, go to the Build Phases tab in your target settings, open the Compile Sources group, double-click Base64.m in the list and type -fobjc-arc into the popover.
If you wish to convert your whole project to ARC, comment out the #error line in Base64.m, then run the Edit > Refactor > Convert to Objective-C ARC... tool in Xcode and make sure all files that you wish to use ARC for (including Base64.m) are checked.
Thread Safety
--------------
All the Base64 methods should be safe to call from multiple threads concurrently.
Installation
--------------
To use the Base64 category in an app, just drag the category files (demo files and assets are not needed) into your project and import the header file into any class where you wish to make use of the Base64 functionality.
NSData Extensions
----------------------
Base64 extends NSData with the following methods:
+ (NSData *)dataWithBase64EncodedString:(NSString *)string;
Takes a base-64-encoded string and returns an autoreleased NSData object containing the decoded data. Any non-base-64 characters in the string are ignored, so it is safe to pass a string containing line breaks or other delimiters.
- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth;
Encodes the data as a base-64-encoded string and returns it. The wrapWidth argument allows you to specify the number of characters at which the output should wrap onto a new line. The value of wrapWidth must be a multiple of four. Values that are not a multiple of four will be truncated to the nearest multiple. A value of zero indicates that the data should not wrap.
- (NSString *)base64EncodedString;
Encodes the data as a base-64-encoded string without any wrapping (line breaks).
NSString Extensions
----------------------
Base64 extends NSString with the following methods:
+ (NSString *)stringWithBase64EncodedString:(NSString *)string;
Takes a base-64-encoded string and returns an autoreleased NSString object containing the decoded data, interpreted using UTF8 encoding. The vast majority of use cases for Base64 encoding use Ascii or UTF8 strings, so this should be sufficient for most purposes. If you do need to decode string data in an encoding other than UTF8, convert your string to an NSData object first and then use the NSData dataWithBase64EncodedString: method instead.
- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth;
Converts the string data to UTF8 data and then encodes the data as a base-64-encoded string and returns it. The wrapWidth argument allows you to specify the number of characters at which the output should wrap onto a new line. The value of wrapWidth must be a multiple of four. Values that are not a multiple of four will be truncated to the nearest multiple. A value of zero indicates that the data should not wrap.
- (NSString *)base64EncodedString;
Encodes the string as UTF8 data and then encodes that as a base-64-encoded string without any wrapping (line breaks).
- (NSString *)base64DecodedString;
Treats the string as a base-64-encoded string and returns an autoreleased NSString object containing the decoded data, interpreted using UTF8 encoding. Any non-base-64 characters in the string are ignored, so it is safe to use a string containing line breaks or other delimiters.
- (NSData *)base64DecodedData;
Treats the string as base-64-encoded data and returns an autoreleased NSData object containing the decoded data. Any non-base-64 characters in the string are ignored, so it is safe to use a string containing line breaks or other delimiters.
\ No newline at end of file
//
// GMFont.h
// Gengmei
//
// Created by Thierry on 2/9/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
#define GMFont(size) [UIFont gm_fontWithSize:size]
@interface UIFont (GMFont)
+ (UIFont *)gm_fontWithSize:(NSInteger)size;
@end
//
// GMFont.m
// Gengmei
//
// Created by Thierry on 2/9/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMFont.h"
/*
更美-方正兰亭黑 familyName: GMEI_FZLTH_Thin_GB_YS
familyName 下只有一个字体: FZLTHThin--GB1-4-YS
可以用如下方式取得某个 familyName 下的所有字体
for (NSString *name in [UIFont fontNamesForFamilyName:@"GMEI_FZLTH_Thin_GB_YS"]) {
NSLog(@"%@", name);
}
*/
@implementation UIFont (GMFont)
+ (UIFont *)gm_fontWithSize:(NSInteger)size {
UIFont *font = [UIFont fontWithName:@"FZLTHThin--GB1-4-YS" size:size];
if (!font) {
if(([[[UIDevice currentDevice] systemVersion] compare:@"9.0" options:NSNumericSearch] != NSOrderedAscending)) {
return [UIFont fontWithName:@"PingFangSC-Regular" size:size];
}else{
return [UIFont fontWithName:@"STHeitiSC-Light" size:size];
}
}
return font;
}
@end
//
// NSAttributedString+GMSize.h
// Gengmei
//
// Created by wangyang on 7/22/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSAttributedString (GMSize)
+ (NSMutableAttributedString *)string:(NSString *)string font:(UIFont *)font color:(UIColor *)color shadow:(BOOL)needShadow;
+ (NSMutableAttributedString *)attributedStringWithString:(NSString *)string fontSize:(CGFloat)size textColor:(UIColor *)color;
/**
* @brief 适用于为整个 string 添加属性,其中的 paragraphStyle 为 mutable 的,可以取出来再次修改
@note 最佳使用方法: 用该方法创建 attributedString,再使用 sizeForBoundingRectSize: 方法来计算大小.
@note 默认为 TruncatingTail
*/
+ (NSMutableAttributedString *)attributedStringWithString:(NSString *)string trimBothEndSpace:(BOOL)tripSpace trimInnerReturnSpace:(BOOL)trimReturn fontSize:(CGFloat)size textColor:(UIColor *)color lineSpacing:(CGFloat)spacing;
/**
* @brief 使用该方法返回 attributedString 的大小。比如指定{320, 99},那么就会返回一个不超过该大小的 size,且宽高都经过 ceilf 处理
*
* @param maxSize 指定的最大的 string 大小。
*
* @return 经过 ceilf 处理的最大 size
*/
- (CGSize)sizeForBoundingRectSize:(CGSize)maxSize;
@end
@interface NSMutableAttributedString (GM)
- (void)addJustifiedAligment;
@end
@interface NSString (Attributed)
- (NSString *)trimInnerReturn;
- (NSString *)trimBothEnd;
- (NSMutableAttributedString *)fontSize:(NSInteger)size;
@end
@interface NSMutableAttributedString (Attributed)
- (NSMutableAttributedString *)addColor:(UIColor *)color;
- (NSMutableAttributedString *)addLineSpace:(NSInteger)space;
- (NSMutableAttributedString *)addShadow:(CGFloat)blur;
@end
\ No newline at end of file
//
// NSAttributedString+GMSize.m
// Gengmei
//
// Created by wangyang on 7/22/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "NSAttributedString+GMSize.h"
#import "GMFont.h"
#import "NSString+GM.h"
@implementation NSAttributedString (GMSize)
+ (NSMutableAttributedString *)string:(NSString *)string font:(UIFont *)font color:(UIColor *)color shadow:(BOOL)needShadow{
if (!font) {
NSAssert(0, @"请传递一个正常的字体参数");
}
if (!color) {
NSAssert(0, @"请传递一个正常的字体参数");
}
if (![string isNonEmpty]) {
return [[NSMutableAttributedString alloc] initWithString:@"" attributes:nil];;
}
NSMutableDictionary *stringAttribute = [@{NSForegroundColorAttributeName:color,
NSFontAttributeName:font} mutableCopy];
if (needShadow) {
NSShadow *shadow = [NSShadow new];
shadow.shadowOffset = CGSizeZero;
shadow.shadowBlurRadius = 5;
stringAttribute[NSShadowAttributeName] = shadow;
}
NSMutableAttributedString *attriString = [[NSMutableAttributedString alloc] initWithString:string attributes:stringAttribute];
return attriString;
}
+ (NSMutableAttributedString *)attributedStringWithString:(NSString *)string fontSize:(CGFloat)size textColor:(UIColor *)color{
return [self attributedStringWithString:string trimBothEndSpace:YES trimInnerReturnSpace:YES fontSize:size textColor:color lineSpacing:0];
}
+ (NSMutableAttributedString *)attributedStringWithString:(NSString *)string trimBothEndSpace:(BOOL)tripSpace trimInnerReturnSpace:(BOOL)trimReturn fontSize:(CGFloat)size textColor:(UIColor *)color lineSpacing:(CGFloat)spacing{
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.lineSpacing = spacing;
style.lineBreakMode = NSLineBreakByTruncatingTail;
NSString *trimString = string;
if (tripSpace) {
trimString = [string trimSpace];
}
if (trimReturn) {
trimString = [trimString stringByReplacingOccurrencesOfString:@"\r" withString:@""];
trimString = [trimString stringByReplacingOccurrencesOfString:@"\n" withString:@""];
}
if (!trimString) {
trimString = @"";
}
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:trimString attributes:@{NSFontAttributeName:GMFont(size), NSForegroundColorAttributeName:color, NSParagraphStyleAttributeName:style}];
return [attributedString mutableCopy];
}
- (CGSize)sizeForBoundingRectSize:(CGSize)maxSize{
// mutableCopy 以防修复原来的 string
NSMutableAttributedString *string = [self mutableCopy];
if (![string.string isNonEmpty]) {
return CGSizeZero;
}
// 修改 lineBreakMode 为 NSLineBreakByWordWrapping,因为 Truncate tail 会在 boundingRectWithSize 中指定,在这里指定了,反而会导致 boundingRectWithSize 方法计算不准确
NSMutableParagraphStyle *originStyle = [self attribute:NSParagraphStyleAttributeName atIndex:0 effectiveRange:NULL];
originStyle.lineBreakMode = NSLineBreakByWordWrapping;
CGSize size = [string boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine context:[[NSStringDrawingContext alloc] init]].size;
return CGSizeMake(ceilf(size.width), ceilf(size.height));
}
@end
@implementation NSMutableAttributedString (GM)
- (void)addJustifiedAligment {
if (self.length == 0) {
return;
}
NSRange range = NSMakeRange(0, self.length);
NSDictionary *dic = [self attributesAtIndex:0 effectiveRange:&range];
NSMutableParagraphStyle *style = dic[NSParagraphStyleAttributeName];
if (!style) {
style = [[NSMutableParagraphStyle alloc] init];
}
style.alignment = NSTextAlignmentJustified;
style.firstLineHeadIndent = 0.001; // important: must have a value to make alignment work
[self addAttribute:NSBaselineOffsetAttributeName value:@0 range:range];
}
@end
@implementation NSString (Attributed)
- (NSString *)trimBothEnd {
NSMutableString *string = [[NSMutableString alloc] initWithString:self];
string = [string trimSpace];
return string;
}
- (NSString *)trimInnerReturn {
NSString *string = self;
string = [self stringByReplacingOccurrencesOfString:@"\r" withString:@""];
string = [self stringByReplacingOccurrencesOfString:@"\n" withString:@""];
return string;
}
- (NSMutableAttributedString *)fontSize:(NSInteger)size {
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:self attributes:@{NSFontAttributeName: GMFont(size)}];
return string;
}
@end
@implementation NSMutableAttributedString (Attributed)
- (NSMutableAttributedString *)addColor:(UIColor *)color {
[self addAttributes:@{NSForegroundColorAttributeName: color} range:NSMakeRange(0, self.length)];
return self;
}
- (NSMutableAttributedString *)addLineSpace:(NSInteger)space {
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.lineSpacing = space;
style.lineBreakMode = NSLineBreakByTruncatingTail;
[self addAttributes:@{NSParagraphStyleAttributeName: style} range:NSMakeRange(0, self.length)];
return self;
}
- (NSMutableAttributedString *)addShadow:(CGFloat)blur {
NSShadow *shadow = [NSShadow new];
shadow.shadowOffset = CGSizeZero;
shadow.shadowBlurRadius = blur;
[self addAttributes:@{NSShadowAttributeName: shadow} range:NSMakeRange(0, self.length)];
return self;
}
@end
\ No newline at end of file
//
// NSDate+DateFormat.h
// Gengmei
//
// Created by Sean Lee on 4/14/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSDate (DateFormat)
/**
* @author licong, 16-12-29 16:12:03
*
* 日期格式化成年月日型的字符串(eg:2015-4-14)
*
* @return 返回格式化后的字符串
*
* @since 5.8
*/
-(NSString *)dateFormatToString;
/**
* @author licong, 16-12-29 16:12:07
*
* 日期格式化成年月日星期型的字符串(eg:2015年4月14号 星期三)
*
* @return 返回格式化后的字符串
*
* @since 5.8
*/
-(NSString *)dateFormatToWeekString;
@end
//
// NSDate+DateFormat.m
// Gengmei
//
// Created by Sean Lee on 4/14/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "NSDate+DateFormat.h"
@implementation NSDate (DateFormat)
-(NSString *)dateFormatToString {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd"];
return [formatter stringFromDate:self];
}
- (NSString *)dateFormatToWeekString{
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *comps;
comps = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSWeekdayCalendarUnit) fromDate:self];
NSInteger nowYear = [comps year];
NSInteger nowMonth = [comps month];
NSInteger nowDay = [comps day];
NSInteger nowWeek = [comps weekday];
NSString *weekStr = [[NSString alloc] init];
switch (nowWeek) {
case 1:
weekStr = @"星期天";
break;
case 2:
weekStr = @"星期一";
break;
case 3:
weekStr = @"星期二";
break;
case 4:
weekStr = @"星期三";
break;
case 5:
weekStr = @"星期四";
break;
case 6:
weekStr = @"星期五";
break;
case 7:
weekStr = @"星期六";
break;
default:
break;
}
NSString * formatterDateString = [NSString stringWithFormat:@"%ld年%ld月%ld日",(long)nowYear,(long)nowMonth,(long)nowDay];
return formatterDateString;
}
@end
//
// NSFileManager+FolderSize.h
// ZhengXing
//
// Created by wangyang on 3/19/15.
// Copyright (c) 2015 Wanmei Creative. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSFileManager (FolderSize)
- (BOOL)getAllocatedSize:(unsigned long long *)size ofDirectoryAtURL:(NSURL *)directoryURL error:(NSError * __autoreleasing *)error;
@end
//
// NRFileManager.m
// NRFoundation
//
// Created by Nikolai Ruhe on 2015-02-22.
// Copyright (c) 2015 Nikolai Ruhe. All rights reserved.
//
#import "NSFileManager+FolderSize.h"
@implementation NSFileManager (FolderSize)
// This method calculates the accumulated size of a directory on the volume in bytes.
//
// As there's no simple way to get this information from the file system it has to crawl the entire hierarchy,
// accumulating the overall sum on the way. The resulting value is roughly equivalent with the amount of bytes
// that would become available on the volume if the directory would be deleted.
//
// Caveat: There are a couple of oddities that are not taken into account (like symbolic links, meta data of
// directories, hard links, ...).
- (BOOL)getAllocatedSize:(unsigned long long *)size ofDirectoryAtURL:(NSURL *)directoryURL error:(NSError * __autoreleasing *)error
{
NSParameterAssert(size != NULL);
NSParameterAssert(directoryURL != nil);
// We'll sum up content size here:
unsigned long long accumulatedSize = 0;
// prefetching some properties during traversal will speed up things a bit.
NSArray *prefetchedProperties = @[
NSURLIsRegularFileKey,
NSURLFileAllocatedSizeKey,
NSURLTotalFileAllocatedSizeKey,
];
// The error handler simply signals errors to outside code.
__block BOOL errorDidOccur = NO;
BOOL (^errorHandler)(NSURL *, NSError *) = ^(NSURL *url, NSError *localError) {
if (error != NULL)
*error = localError;
errorDidOccur = YES;
return NO;
};
// We have to enumerate all directory contents, including subdirectories.
NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtURL:directoryURL
includingPropertiesForKeys:prefetchedProperties
options:(NSDirectoryEnumerationOptions)0
errorHandler:errorHandler];
// Start the traversal:
for (NSURL *contentItemURL in enumerator) {
// Bail out on errors from the errorHandler.
if (errorDidOccur)
return NO;
// Get the type of this item, making sure we only sum up sizes of regular files.
NSNumber *isRegularFile;
if (! [contentItemURL getResourceValue:&isRegularFile forKey:NSURLIsRegularFileKey error:error])
return NO;
if (! [isRegularFile boolValue])
continue; // Ignore anything except regular files.
// To get the file's size we first try the most comprehensive value in terms of what the file may use on disk.
// This includes metadata, compression (on file system level) and block size.
NSNumber *fileSize;
if (! [contentItemURL getResourceValue:&fileSize forKey:NSURLTotalFileAllocatedSizeKey error:error])
return NO;
// In case the value is unavailable we use the fallback value (excluding meta data and compression)
// This value should always be available.
if (fileSize == nil) {
if (! [contentItemURL getResourceValue:&fileSize forKey:NSURLFileAllocatedSizeKey error:error])
return NO;
NSAssert(fileSize != nil, @"huh? NSURLFileAllocatedSizeKey should always return a value");
}
// We're good, add up the value.
accumulatedSize += [fileSize unsignedLongLongValue];
}
// Bail out on errors from the errorHandler.
if (errorDidOccur)
return NO;
// We finally got it.
*size = accumulatedSize;
return YES;
}
@end
//
// NSNull+Empty.h
// Gengmei
//
// Created by licong on 12/28/15.
// Copyright © 2015 Wanmeichuangyi. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSNull (Empty)
- (BOOL)isNonEmpty;
/**
* @brief 我们极力避免使用 string.length 的方式来判断空字符串。但是 string.length 除了用来判断,可能还会用在其它非判断的功能上。为了避免 [NSNull length] 而引起的 crash,还是给 NSNull 添加一个 length 方法比较安全。并且在 debug 情况下打印 log,以提示使用了 length 方法
*
* @return 0
*/
- (NSUInteger)length;
@end
//
// NSNull+Empty.m
// Gengmei
//
// Created by licong on 12/28/15.
// Copyright © 2015 Wanmeichuangyi. All rights reserved.
//
#import "NSNull+Empty.h"
@implementation NSNull (Empty)
- (BOOL) isNonEmpty {
return NO;
}
- (NSUInteger)length{
NSAssert(0, @"注意:向 NSNull 发送了 length 消息");
return 0;
}
@end
//
// UIViewController+KeyboardAnimation.h
// yingshibaokaoyan
//
// Created by wangyang on 7/17/14.
// Copyright (c) 2014 com.zkyj.yingshibao.kaoyao. All rights reserved.
//
#import <UIKit/UIKit.h>
/**
* 当键盘弹出或者收起时,会执行的block。在这个block里实现需要的UI变动
*
* @param offkeyboardFrameset 将键盘转换为视图所在坐标第后,整个键盘View的Frame
* @param duration 动画时间
* @param curve 动画效果
* @param notification 通知
*/
typedef void(^ShowKeyboardAnimation)(CGRect keyboardFrame, CGFloat duration, NSInteger curve, NSNotification *notifcation);
typedef void(^HideKeyboardAnimation)(CGFloat duration, NSInteger curve, NSNotification *notifcation);
/**
* 使用该类别,以方便的控制键盘事件
*/
@interface NSObject (KeyboardAnimation)
- (void)observeKeyboardForView:(UIView *)view showKeyboardAnimation:(ShowKeyboardAnimation)animation1 hideKeyboardAnimation:(HideKeyboardAnimation)animation2;
- (void)removeObservKeyboard;
@end
//
// UIViewController+KeyboardAnimation.m
// yingshibaokaoyan
//
// Created by wangyang on 7/17/14.
// Copyright (c) 2014 com.zkyj.yingshibao.kaoyao. All rights reserved.
//
#import "NSObject+KeyboardAnimation.h"
#import <objc/runtime.h>
static const char kwy_targetView;
static const char kwy_showAnimationblock;
static const char kwy_hideAnimationblock;
@interface NSObject ()
@property (nonatomic, strong) UIView *wy_targetView;
@property (nonatomic, copy) ShowKeyboardAnimation wy_showAnimationblock;
@property (nonatomic, copy) HideKeyboardAnimation wy_hideAnimationblock;
@end
@implementation NSObject (KeyboardAnimation)
#pragma mark - 属性get、set
- (ShowKeyboardAnimation)wy_showAnimationblock
{
return objc_getAssociatedObject(self, &kwy_showAnimationblock);
}
- (void)setWy_showAnimationblock:(ShowKeyboardAnimation)wy_showAnimationblock
{
objc_setAssociatedObject(self, &kwy_showAnimationblock, wy_showAnimationblock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (HideKeyboardAnimation)wy_hideAnimationblock
{
return objc_getAssociatedObject(self, &kwy_hideAnimationblock);
}
- (void)setWy_hideAnimationblock:(HideKeyboardAnimation)wy_hideAnimationblock
{
objc_setAssociatedObject(self, &kwy_hideAnimationblock, wy_hideAnimationblock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (UIView *)wy_targetView
{
return objc_getAssociatedObject(self, &kwy_targetView);
}
- (void)setWy_targetView:(UIView *)wy_targetView
{
objc_setAssociatedObject(self, &kwy_targetView, wy_targetView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark - 监听
- (void)observeKeyboardForView:(UIView *)view showKeyboardAnimation:(ShowKeyboardAnimation)animation1 hideKeyboardAnimation:(HideKeyboardAnimation)animation2
{
self.wy_showAnimationblock = animation1;
self.wy_targetView = view;
self.wy_hideAnimationblock = animation2;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)removeObservKeyboard
{
self.wy_showAnimationblock = nil;
self.wy_targetView = nil;
self.wy_hideAnimationblock = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
#pragma mark - 监听响应
- (void)keyboardWillShow:(NSNotification *)note{
// get keyboard size and loctaion
CGRect keyboardFrame;
[[note.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue: &keyboardFrame];
// Need to translate the bounds to account for rotation.
keyboardFrame = [[UIApplication sharedApplication].keyWindow convertRect:keyboardFrame toView:self.wy_targetView.superview];
NSNumber *duration = [note.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];
NSNumber *curve = [note.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey];
self.wy_showAnimationblock(keyboardFrame, [duration doubleValue], [curve integerValue], note);
}
- (void)keyboardWillHide:(NSNotification *)note{
NSNumber *duration = [note.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];
NSNumber *curve = [note.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey];
self.wy_hideAnimationblock([duration doubleValue], [curve integerValue], note);
}
@end
//
// NSString+DateFormat.h
// Gengmei
//
// Created by Sean Lee on 4/15/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSString (DateFormat)
/**
* @author licong, 16-12-29 16:12:14
*
* String类型的日期转为冒号式的NSDate
*
* @return 返回一个NSDate对象
*
* @since 5.8
*/
-(NSDate *)stringFormatToColonDate;
/**
* @author licong, 16-12-29 16:12:59
*
* String类型的日期转为破折号式的NSDate
*
* @return 返回一个NSDate对象
*
* @since 5.8
*/
-(NSDate *)stringformatToDashDate;
@end
//
// NSString+DateFormat.m
// Gengmei
//
// Created by Sean Lee on 4/15/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "NSString+DateFormat.h"
@implementation NSString (DateFormat)
-(NSDate *)stringFormatToColonDate{
NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"YYYY-MM-dd"];
return [formatter dateFromString:self];
}
- (NSDate *)stringformatToDashDate{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"YYYY:MM:dd"];
return [dateFormatter dateFromString:self];
}
@end
//
// NSString+Encrypt.h
// Gengmei
//
// Created by licong on 12/28/15.
// Copyright © 2015 Wanmeichuangyi. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* @brief
AES块长度 128 byte
加密模式CBC,cipher block chaining
数据长度不到128byte时需要填充(即no padding自定义填充)
填充方法s + (128 - len(s) % 128) * chr(128 - len(s) % 128) 将数字转成char字符拼接尾部
加密密钥 Up[K+ub%pliOnsO5UavFBd)cw5VcyHSX
初始向量需要附加在加密后的auth_user_id中 传给服务器,final auth_user_id = base64(iv+encrypted(user_id_auth))
*/
@interface NSString (Encrypt)
/**
* @brief AES256加密
*
* @param keyString 32位的密钥
*
* @return 返回加密后的密文
*/
- (NSString *)AES256EncryptedStringForKey:(NSString *)key;
/**
* @brief AES256解密
*
* @param keyString 32位的密钥
*
* @return 返回解密后的明文
*/
- (NSString *)AES256DecryptedStringForKey:(NSString *)key;
/**
* @brief MD5加密
*
* @return MD5加密后的String
*/
- (NSString *) stringFromMD5;
@end
//
// NSString+Encrypt.m
// Gengmei
//
// Created by licong on 12/28/15.
// Copyright © 2015 Wanmeichuangyi. All rights reserved.
//
#import "NSString+Encrypt.h"
#import <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonCryptor.h>
#import <Base64nl/Base64nl-umbrella.h>
@implementation NSString (Encrypt)
- (NSString *)AES256EncryptedStringForKey:(NSString *)key
{
//密钥
NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
//明文数据
NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
// Init cryptor
CCCryptorRef cryptor = NULL;
//IV:初始化16字节的随机向量
char iv[16];
for (int i = 0; i<16; i++) {
iv[i] = arc4random()%255;//一个字节长的随机数
}
//Create Cryptor
CCCryptorStatus create = CCCryptorCreateWithMode(kCCEncrypt,
kCCModeCBC, //CBC模式
kCCAlgorithmAES128, //分组密码块长度
ccNoPadding, //无填充模式
iv, // can be NULL, because null is full of zeros
keyData.bytes, //密钥
keyData.length, //密钥长度
NULL,
0,
0,
0, //这里参数只在CTR下有用,本初填0即可
&cryptor);
if (create == kCCSuccess){
size_t numBytesCrypted;
//自定义填充明文算法
NSUInteger dataLength = [data length];
int diff = 128 - (dataLength % 128);
unsigned long newSize = 0;
if(diff > 0)
newSize = dataLength + diff;
char dataPtr[newSize];
memcpy(dataPtr, [data bytes], [data length]);
for(int i = 0; i < diff; i++){
char character = diff;
dataPtr[i + dataLength] = character;
}
/*初始化加密后的data,并开辟好空间长度,查阅相关文档:对于分组密码,加密后的数据长度总是小于或者等于 加密前数据长度+单个分组密码块长度之和*/
NSMutableData *cipherData= [NSMutableData dataWithLength: sizeof(dataPtr)+kCCBlockSizeAES128];
/*Update Cryptor,得到加密后data以及我们需要的数据长度,这里可以看到cipherData的长度是小于或者等于outLength的
*/
CCCryptorStatus update = CCCryptorUpdate(cryptor,
dataPtr,
sizeof(dataPtr),
cipherData.mutableBytes,
cipherData.length,
&numBytesCrypted);
if (update == kCCSuccess){
//通过outLength截图我们需要的数据长度
cipherData.length = numBytesCrypted;
//Final Cryptor,最终生成最终的密文,装载给cipherData
CCCryptorStatus final = CCCryptorFinal(cryptor, //CCCryptorRef cryptorRef,
cipherData.mutableBytes, //void *dataOut,
cipherData.length, //size_t dataOutAvailable,
&numBytesCrypted); //size_t *dataOutMoved)
if (final == kCCSuccess){
//Release Cryptor
CCCryptorRelease(cryptor);
}
//最终结果= 初始向量+密文,这样服务器才可以拿到初始向量,用密钥解码
NSMutableData *resultData= [NSMutableData dataWithLength:0];
[resultData appendBytes:iv length:sizeof(iv)];
[resultData appendBytes:cipherData.bytes length:cipherData.length];
//最终结果再base64转码
NSString * resultStr = [resultData base64EncodedString];//[GTMBase64 stringByEncodingData:resultData];
return resultStr;
}
}
else{
NSLog(@"加密失败");
}
return nil;
}
- (NSString *)AES256DecryptedStringForKey:(NSString *)key {
//Key to Data
NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
// Init cryptor
CCCryptorRef cryptor = NULL;
//IV:获取密文里的随机向量
NSData *data = [self base64DecodedData];
NSMutableData * iv = [NSMutableData dataWithBytes:data.bytes length:kCCKeySizeAES256];
// Create Cryptor
CCCryptorStatus createDecrypt = CCCryptorCreateWithMode(kCCDecrypt, // operation
kCCModeCBC, // mode CTR
kCCAlgorithmAES, // Algorithm
ccNoPadding, // padding
iv.bytes, // can be NULL, because null is full of zeros
keyData.bytes, // key
keyData.length, // keylength
NULL, //const void *tweak
0, //size_t tweakLength,
0, //int numRounds,
0, //CCModeOptions options,
&cryptor); //CCCryptorRef *cryptorRef
if (createDecrypt == kCCSuccess)
{
// Alloc Data Out
NSMutableData * realData = [NSMutableData dataWithBytes:data.bytes + 16 length:data.length - 16];
NSMutableData *cipherDataDecrypt = [NSMutableData dataWithLength:realData.length + kCCBlockSizeAES128];
//alloc number of bytes written to data Out
size_t outLengthDecrypt;
//Update Cryptor
CCCryptorStatus updateDecrypt = CCCryptorUpdate(cryptor,
realData.bytes, //const void *dataIn,
realData.length, //size_t dataInLength,
cipherDataDecrypt.mutableBytes, //void *dataOut,
cipherDataDecrypt.length, // size_t dataOutAvailable,
&outLengthDecrypt); // size_t *dataOutMoved)
if (updateDecrypt == kCCSuccess)
{
//Cut Data Out with nedded length
cipherDataDecrypt.length = outLengthDecrypt;
//Final Cryptor
CCCryptorStatus final = CCCryptorFinal(cryptor, //CCCryptorRef cryptorRef,
cipherDataDecrypt.mutableBytes, //void *dataOut,
cipherDataDecrypt.length, // size_t dataOutAvailable,
&outLengthDecrypt); // size_t *dataOutMoved)
if (final == kCCSuccess){
CCCryptorRelease(cryptor); //CCCryptorRef cryptorRef
}
// Data to String
NSMutableString* cipherFinalDecrypt = [[NSMutableString alloc] initWithData:cipherDataDecrypt encoding:NSUTF8StringEncoding];
int diff = [cipherFinalDecrypt characterAtIndex:cipherDataDecrypt.length - 1]; // 128字节的明文(填充字符)中填充字符的个数
[cipherFinalDecrypt deleteCharactersInRange:NSMakeRange(cipherFinalDecrypt.length - diff, diff)];
return cipherFinalDecrypt;
}
}
else{
NSLog(@"解密密失败");
}
return nil;
}
- (NSString *)stringFromMD5
{
if(self == nil || [self length] == 0)
return nil;
const char *value = [self UTF8String];
unsigned char outputBuffer[CC_MD5_DIGEST_LENGTH];
CC_MD5(value, (uint32_t)strlen(value), outputBuffer);
NSMutableString *outputString = [[NSMutableString alloc] initWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for(NSInteger count = 0; count < CC_MD5_DIGEST_LENGTH; count++){
[outputString appendFormat:@"%02x",outputBuffer[count]];
}
return outputString;
}
+ (BOOL)validKey:(NSString*)key
{
if(key == nil || key.length != kCCKeySizeAES256){
return NO;
}
return YES;
}
@end
//
// NSString+GM.h
// Gengmei
//
// Created by licong on 12/28/15.
// Copyright © 2015 Wanmeichuangyi. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* 常用且按功能块比较难划分的扩展方法
*/
@interface NSString (GM)
/**
* @author licong, 16-12-28 12:12:13
*
* 判断一个字符串是否为空(包括nil/@""/nil)
*
* @return 布尔值,YES表示字符串不为空,NO表示字符串为空
*
* @since 5.8
*/
- (BOOL)isNonEmpty;
/*!
* @author zhaiguojun, 16-04-21
*
* @brief 把字典和数组转成string
*
* @param object
*
* @return 转换后的string
*
* @since 5.9.3
*/
+ (NSString*)dataToJsonString:(id)object;
/**
* @author licong, 16-12-28 12:12:16
*
* 得到非空string
*
* @return 返回一个非空的字符串
*
* @since 5.8
*/
+ (NSString *)safeStringWithString:(NSString *)string;
/**
* @author licong, 16-12-28 18:12:04
*
* 去除字符串中间和收尾的空格
*
* @return 无空格的字符串
*
* @since 5.8
*/
- (NSString*)trimSpace;
/**
* @brief 是否为纯数字
*
* @param string 要检查的字符串
*
*/
+ (BOOL)isPureInt:(NSString *)string;
/**
* @brief 是否为手机号
*
* @param 0~9 11位即可
*
*
* @return
*/
+ (BOOL)isMobileNumber:(NSString *)mobileNum;
@end
/**
*
* 将类似URL的query参数解析成字典
*
*/
@interface NSString (paresQuery)
/**
* @author licong, 16-12-28 18:12:14
*
* 解析url中的query,转成字典格式
*
* @return 解析好的字典
*
* @since 5.8
*/
- (NSDictionary*)urlQueryToDictionary;
/**
* @author licong, 16-12-28 18:12:11
*
* 解析query字符串为字典比如:a=b&c=d
*
* @return 解析好的字典
*
* @since 5.8
*/
- (NSDictionary*)queryToDictionary;
@end
/**
*
* 根据String的Font计算String的size
*
*/
@interface NSString (calculatorSize)
/**
* @author licong, 16-12-28 18:12:38
*
* 计算String当行间距为0的时候的高度和宽度
*
* @param font String的font
* @param size String限定的size
*
* @return String实际(如果是多行,则经过折行后)的size
*
* @since 5.8
*/
- (CGSize)sizeWithFont:(UIFont *)font boundSize:(CGSize)size;
/**
* @author licong, 16-12-28 18:12:05
*
* 计算String在某个行间距下,高度和宽度
*
* @param font String的font
* @param size String限定的size
* @param lineSpacing 设定的行间距
*
* @return String在设定的行间距下,实际(如果是多行,则经过折行后)的size
*
* @since 5.8
*/
- (CGSize)sizeWithFont:(UIFont *)font boundSize:(CGSize)size lineSpacing:(CGFloat)lineSpacing;
@end
@interface NSString (URLEncoding)
/**
* @author licong, 16-12-29 16:12:45
*
* URL编码
*
* @return 编码后的URL
*
* @since 5.8
*/
- (NSString*) URLEncodedString;
/**
* @author licong, 16-12-29 16:12:08
*
* URL解码
*
* @return 解码URL
*
* @since 5.8
*/
- (NSString*) URLDecodedString;
@end
//
// NSString+GM.m
// Gengmei
//
// Created by licong on 12/28/15.
// Copyright © 2015 Wanmeichuangyi. All rights reserved.
//
#import "NSString+GM.h"
#import "NSNull+Empty.h"
@implementation NSString (GM)
+ (NSString *)safeStringWithString:(NSString *)string{
if ([string isNonEmpty]) {
return string;
}else{
return @"";
}
}
- (BOOL)isNonEmpty {
NSMutableCharacterSet *emptyStringSet = [[NSMutableCharacterSet alloc] init];
[emptyStringSet formUnionWithCharacterSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
[emptyStringSet formUnionWithCharacterSet: [NSCharacterSet characterSetWithCharactersInString: @" "]];
if ([self length] == 0) {
return NO;
}
NSString* str = [self stringByTrimmingCharactersInSet:emptyStringSet];
return [str length] > 0;
}
+ (NSString*)dataToJsonString:(id)object
{
NSString *jsonString = nil;
NSError *error;
if (!object) {
return nil;
}
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:object
options:NSJSONWritingPrettyPrinted
error:&error];
if (! jsonData) {
NSLog(@"Got an error: %@", error);
} else {
jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
return jsonString;
}
- (NSString*) trimSpace {
NSMutableCharacterSet* emptyStringSet = [[NSMutableCharacterSet alloc] init];
[emptyStringSet formUnionWithCharacterSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
[emptyStringSet formUnionWithCharacterSet: [NSCharacterSet characterSetWithCharactersInString: @" "]];
return [self stringByTrimmingCharactersInSet: emptyStringSet];
}
+ (BOOL)isPureInt:(NSString *)string{
NSScanner* scan = [NSScanner scannerWithString:string];
int val;
return [scan scanInt:&val] && [scan isAtEnd];
}
+ (BOOL)isMobileNumber:(NSString *)mobileNum{
NSString *ALL = @"\\d{11}$";
NSPredicate *regextestall = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", ALL];
return [regextestall evaluateWithObject:mobileNum];
}
@end
@implementation NSString (paresQuery)
- (NSDictionary*)urlQueryToDictionary{
NSURL* url1 = [NSURL URLWithString:self];
if (!url1) {
return nil;
}
NSString* query = [url1 query];
return [query queryToDictionary];
}
- (NSDictionary*)queryToDictionary {
@try {
NSMutableDictionary* dict = [NSMutableDictionary dictionary];
NSArray* components = [self componentsSeparatedByString:@"&"];
for (NSString* component in components) {
NSArray* keyValue = [component componentsSeparatedByString:@"="];
if ([keyValue count] > 1) {
NSString * key = [[keyValue objectAtIndex:0] URLDecodedString];
NSString * value = [keyValue objectAtIndex:1];
//参数中依然包含超过2个“=”号(多数发生在common_webview后的url参数中),则后面的数组的元素需要拼接成一个字符串
if ([keyValue count]>2) {
for (int i=2; i<[keyValue count]; i++) {
value=[value stringByAppendingString:@"="];
value=[value stringByAppendingString:keyValue[i]];
}
}
//因为这种情况服务器和客户端都转义了一次,所以要两次反转义还原中文
while ([value rangeOfString:@"%"].length != 0) {
value = [value URLDecodedString];
}
[dict setObject: value forKey: key];
}
}
return dict;
}
@catch (NSException *exception) {}
}
@end
@implementation NSString (calculatorSize)
- (CGSize)sizeWithFont:(UIFont *)font boundSize:(CGSize)size{
return [self sizeWithFont:font boundSize:size lineSpacing:0];
}
- (CGSize)sizeWithFont:(UIFont *)font boundSize:(CGSize)size lineSpacing:(CGFloat)lineSpacing{
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc]init];
paragraphStyle.lineSpacing = lineSpacing;
NSDictionary *attributes = @{NSFontAttributeName:font, NSParagraphStyleAttributeName:paragraphStyle};
CGSize resultSize = [self boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine attributes:attributes context:nil].size;
return CGSizeMake(ceil(resultSize.width), ceil(resultSize.height));
}
@end
@implementation NSString (URLEncoding)
- (NSString *)URLEncodedString {
NSString *result = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
(CFStringRef)self,
NULL,
CFSTR("!*'();:@&=+$,/?%#[]"),
kCFStringEncodingUTF8));
return result;
}
- (NSString*)URLDecodedString {
NSString *result = (NSString *)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault,
(CFStringRef)self,
CFSTR(""),
kCFStringEncodingUTF8));
return result;
}
@end
Copyright (c) 2016 licong <1240690490@qq.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# GMFoundation
[![CI Status](http://img.shields.io/travis/licong/GMFoundation.svg?style=flat)](https://travis-ci.org/licong/GMFoundation)
[![Version](https://img.shields.io/cocoapods/v/GMFoundation.svg?style=flat)](http://cocoapods.org/pods/GMFoundation)
[![License](https://img.shields.io/cocoapods/l/GMFoundation.svg?style=flat)](http://cocoapods.org/pods/GMFoundation)
[![Platform](https://img.shields.io/cocoapods/p/GMFoundation.svg?style=flat)](http://cocoapods.org/pods/GMFoundation)
## Example
To run the example project, clone the repo, and run `pod install` from the Example directory first.
## Requirements
## Installation
GMFoundation is available through [CocoaPods](http://cocoapods.org). To install
it, simply add the following line to your Podfile:
```ruby
pod "GMFoundation"
```
## Author
licong, 1240690490@qq.com
## License
GMFoundation is available under the MIT license. See the LICENSE file for more info.
Copyright (c) 2016 北京更美互动信息科技有限公司
仅限北京更美互动信息科技有限公司内部使用
//
// UIDevice+Reso.m
// Simple UIDevice Category for handling different iOSs hardware resolutions
//
// Created by Alejandro Luengo on 29/09/14.
// (c) 2014 Intelygenz
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSUInteger, UIDeviceResolution) {
UnknowDevice = 0,
iPhone4 = 1,
iPhone5 = 2,
iPhone6 = 3,
iPhone6Plus = 4,
iPad = 5,
iPadRetina = 6
};
@interface UIDevice (Resolutions)
/**
* @author licong, 16-12-29 17:12:54
*
* 判断设备的类型(eg:当前设备是5还是6s)
*
* @return 返回设备类型
*
* @since 5.8
*/
+ (UIDeviceResolution)type;
/**
获取设备的deviceId,如果能去到idfa,就返回idfa,否则就返回idfv
@author zhaiguojun 16-10-31 in (null)
@return deviceId
@since 6.5.0
*/
+ (NSString *)deviceId;
/**
* @author licong, 16-12-29 16:12:13
*
* 获取设备的名字
*
* @return 返回设备名字
*
* @since 5.8
*/
+ (NSString*)deviceName;
/**
获取设备详细类型,目前只到6s,和6s plus
@author zhaiguojun 16-09-18 in (null)
@return model
@since 6.3.0
*/
+ (NSString*)deviceVersion;
@end
//
// UIDevice+Reso.m
// Simple UIDevice Category for handling different iOSs hardware resolutions
//
// Created by Alejandro Luengo on 29/09/14.
// (c) 2014 Intelygenz
// iPhone 6 Plus 736x414 points 2208x1242 pixels 3x scale
// iPhone 6 667x375 points 1334x750 pixels 2x scale
// iPhone 5 568x320 points 1136x640 pixels 2x scale
// iPhone 4s 480x320 points 960x640 pixels 2x scale
// iPad 1024x768 points 1024x768 pixels 1x scale
// iPad Retina 1024x768 points 2048x1536 pixels 2x scale
#import "UIDevice+Resolutions.h"
#import "sys/utsname.h"
#import <AdSupport/AdSupport.h>
@implementation UIDevice (Resolutions)
+ (NSString*)deviceName{
NSDictionary *devices = @{@"iPhone 4": @"960",
@"iPhone 5": @"1136",
@"iPhone 6": @"1334",
@"iPhone 6 Plus": @"2208",
@"iPad": @"1024",
@"iPad Retina": @"2048"
};
UIScreenMode *mode = [UIScreen mainScreen].preferredMode;
return [[devices allKeysForObject:[NSString stringWithFormat:@"%.f", mode.size.height]] firstObject];
}
+ (NSString *)deviceId {
NSString *idfa = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
NSString *idfv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
if (![idfa isEqualToString:@"00000000-0000-0000-0000-000000000000"]) {
return idfa;
}
return idfv;
}
+ (UIDeviceResolution)type
{
UIScreenMode *mode = [UIScreen mainScreen].preferredMode;
if (mode.size.height == 960) {
return iPhone4;
}
else if (mode.size.height == 1136) {
return iPhone5;
}
else if (mode.size.height == 1334) {
return iPhone6;
}
else if (mode.size.height == 2208) {
return iPhone6Plus;
}
else if (mode.size.height == 1024) {
return iPad;
}
else if (mode.size.height == 2048) {
return iPadRetina;
}
else
return UnknowDevice;
}
+ (NSString*)deviceVersion
{
struct utsname systemInfo;
uname(&systemInfo);
NSString *deviceString = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
//iPhone
if ([deviceString isEqualToString:@"iPhone3,1"]) return @"iPhone 4";
if ([deviceString isEqualToString:@"iPhone3,2"]) return @"Verizon iPhone 4";
if ([deviceString isEqualToString:@"iPhone4,1"]) return @"iPhone 4S";
if ([deviceString isEqualToString:@"iPhone5,1"]) return @"iPhone 5";
if ([deviceString isEqualToString:@"iPhone5,2"]) return @"iPhone 5";
if ([deviceString isEqualToString:@"iPhone5,3"]) return @"iPhone 5C";
if ([deviceString isEqualToString:@"iPhone5,4"]) return @"iPhone 5C";
if ([deviceString isEqualToString:@"iPhone6,1"]) return @"iPhone 5S";
if ([deviceString isEqualToString:@"iPhone6,2"]) return @"iPhone 5S";
if ([deviceString isEqualToString:@"iPhone7,1"]) return @"iPhone 6 Plus";
if ([deviceString isEqualToString:@"iPhone7,2"]) return @"iPhone 6";
if ([deviceString isEqualToString:@"iPhone8,1"]) return @"iPhone 6s";
if ([deviceString isEqualToString:@"iPhone8,2"]) return @"iPhone 6s Plus";
if ([deviceString isEqualToString:@"iPhone9,1"]) return @"iPhone 7";
if ([deviceString isEqualToString:@"iPhone9,2"]) return @"iPhone 7 Plus";
return @"iphone 7 ++";
}
@end
//
// UIImage+GM.h
// Gengmei
//
// Created by licong on 15/12/29. Rewrite by wangyang on 2016-7-4
// Copyright © 2016年 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIImage (GM)
/**
* @brief 通过颜色生成图片
*
* @param color 颜色
*/
+ (UIImage *)imageWithColor:(UIColor *)color;
/**
* @author licong, 16-12-29 18:12:48
*
* 将图片调整到设定大小
*
* @param size 设定的图片大小
*
* @return 得到设定大小后的图片
*
* @since 5.8
*/
- (UIImage *)resizedImageWithSize:(CGSize)size;
/**
* @author wangyang in 2016-7-1
*
* 参考rotatedWithTransform
*/
- (UIImage *)rotatedWithAngle:(double)angle;
/**
* @author wangyang in 2016-7-1
*
* 使用这个方法旋转图片,导出的图片的imageOrientation为Up,绘制时会同时考虑待旋转图片的原 imageOrientation 与参数 transform
@note
1. 使用这个方法进行旋转,导出的图片大小可能会发生变化。比如旋转45度,那么能容下原图的bounds肯定要大一些;比如旋转90度,图片的高宽就会互换。
2. 旋转角度为0时,可以用来将图片的imageOrientation重置为Up。
* @return 新图片
*/
- (UIImage *)rotatedWithTransform:(CGAffineTransform)transform;
/**
* @author wangyang in 2016-7-4
*
* 裁剪图片
*/
- (UIImage *)imageWithCropRect:(CGRect)rect;
@end
\ No newline at end of file
//
// UIImage+GM.m
// Gengmei
//
// Created by licong on 15/12/29. Rewrite by wangyang on 2016-7-4
// Copyright © 2016年 Wanmeichuangyi. All rights reserved.
//
#import "UIImage+GM.h"
@implementation UIImage (GM)
#pragma mark - 从颜色生成图片
+ (UIImage *)imageWithColor:(UIColor *)color {
CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
#pragma mark - 改变大小
- (UIImage *)resizedImageWithSize:(CGSize)size {
UIGraphicsBeginImageContext(size);
CGRect rect = CGRectMake(0, 0, size.width, size.height);
// CGContextDrawImage时需要额外考虑UIImage.imageOrientation,drawInRect可以自动帮我们做好这件事
[self drawInRect:rect];
// 另外,我们不需要指定使用 CGContextSetInterpolationQuality,使用默认值Hight就可以。参考http://stackoverflow.com/questions/5685884/imagequality-with-cgcontextsetinterpolationquality
UIImage *newImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImg;
}
#pragma mark - 旋转
- (UIImage *)rotatedWithAngle:(double)angle
{
CGAffineTransform transform = CGAffineTransformMakeRotation(angle * M_PI / 180);
return [self rotatedWithTransform:transform];
}
- (UIImage *)rotatedWithTransform:(CGAffineTransform)transform {
// 旋转后图片的大小
CGRect rotatedRect = CGRectApplyAffineTransform(CGRectMake(0, 0, self.size.width, self.size.height), transform);
rotatedRect.origin.x = 0;
rotatedRect.origin.y = 0;
CGSize rotatedSize = rotatedRect.size;
UIGraphicsBeginImageContextWithOptions(rotatedSize, YES, self.scale);
CGContextRef bitmap = UIGraphicsGetCurrentContext();
// 将 origin 图片的移动中间, 这样我们就可以绕着中心点旋转
CGContextTranslateCTM(bitmap, rotatedSize.width / 2, rotatedSize.height / 2);
// 旋转画布布
CGContextConcatCTM(bitmap, transform);
// 再将 origin 移回来
CGContextTranslateCTM(bitmap, rotatedSize.width / -2, rotatedSize.height / -2);
// drawInRect 会考虑图片的 orientation,也会考虑当前Context的变换
[self drawInRect:rotatedRect];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
#pragma mark - 剪切
- (UIImage *)imageWithCropRect:(CGRect)rect
{
CGImageRef croppedImage = CGImageCreateWithImageInRect(self.CGImage, rect);
UIImage *image = [UIImage imageWithCGImage:croppedImage];
CGImageRelease(croppedImage);
return image;
}
@end
//
// UILabel+CopyExtern.h
// CopyLabel
//
// Created by XingBo on 14-4-1.
// Copyright (c) 2014年 XingBo. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UILabel (CopyExtern)
- (void)attachTapHandler;
@end
//
// UILabel+CopyExtern.m
// CopyLabel
//
// Created by XingBo on 14-4-1.
// Copyright (c) 2014年 XingBo. All rights reserved.
//
#import "UILabel+CopyExtern.h"
@implementation UILabel (CopyExtern)
-(BOOL)canBecomeFirstResponder
{
return YES;
}
// 可以响应的方法
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
return (action == @selector(copyText:));
}
//针对于响应方法的实现
-(void)copyText:(id)sender
{
UIPasteboard *pboard = [UIPasteboard generalPasteboard];
/**
fix帖子详情页 楼主发帖复制崩溃bug
因为某些富文本label只设置了attributedText,导致self.text为nil崩溃
*/
if (self.text) {
pboard.string = self.text;
}else{
pboard.string = self.attributedText.string;
}
}
//UILabel默认是不接收事件的,我们需要自己添加touch事件
-(void)attachTapHandler{
self.userInteractionEnabled = YES; //用户交互的总开关
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
[self addGestureRecognizer:longPress];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIMenuControllerWillShowMenuNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIMenuControllerWillHideMenuNotification object:nil];
}
#pragma mark - notifation methods
- (void)menuShow:(NSNotification *)sender
{
if (self.isFirstResponder) {
self.backgroundColor = [UIColor grayColor];
}
}
- (void)menuHide:(NSNotification *)sender
{
self.backgroundColor = [UIColor clearColor];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
-(void)handleTap:(UIGestureRecognizer*) recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan) {
[self becomeFirstResponder];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(menuShow:) name:UIMenuControllerWillShowMenuNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(menuHide:) name:UIMenuControllerWillHideMenuNotification object:nil];
[[UIMenuController sharedMenuController] setMenuItems:nil];
UIMenuItem *copyLink = [[UIMenuItem alloc] initWithTitle:@"复制"
action:@selector(copyText:)];
[[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObjects:copyLink, nil]];
[[UIMenuController sharedMenuController] setTargetRect:self.frame inView:self.superview];
[[UIMenuController sharedMenuController] setMenuVisible:YES animated: YES];
// label复制选中时的背景颜色
self.backgroundColor = [UIColor clearColor];
}
}
@end
//
// UITextView+Keyboard.h
// ZhengXing
//
// Created by Tulipa on 14-8-22.
// Copyright (c) 2014年 Wanmei Creative. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UITextView (Keyboard)
@property (nonatomic, assign) BOOL hasCloseButton;
@end
@interface UITextField (Keyboard)
@property (nonatomic, assign) BOOL hasCloseButton;
@end
//
// UITextView+Keyboard.m
// ZhengXing
//
// Created by Tulipa on 14-8-22.
// Copyright (c) 2014年 Wanmei Creative. All rights reserved.
//
#import "UITextView+Keyboard.h"
#import "UIView+Layout.h"
@implementation UITextView (Keyboard)
- (BOOL)hasCloseButton
{
return self.inputAccessoryView != nil;
}
- (void)setHasCloseButton:(BOOL)hasCloseButton
{
if (hasCloseButton)
{
UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
[closeButton setBackgroundImage:[UIImage imageNamed:@"keyboard"] forState:UIControlStateNormal];
[closeButton sizeToFit];
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [[UIScreen mainScreen] bounds].size.width, closeButton.height)];
[view addSubview:closeButton];
[view setBackgroundColor:[UIColor clearColor]];
[closeButton setRight:view.width];
[closeButton setTop:0];
[closeButton addTarget:self action:@selector(resignFirstResponder) forControlEvents:UIControlEventTouchUpInside];
self.inputAccessoryView = view;
}
else
{
self.inputAccessoryView = nil;
}
}
@end
@implementation UITextField (Keyboard)
- (BOOL)hasCloseButton
{
return self.inputAccessoryView != nil;
}
- (void)setHasCloseButton:(BOOL)hasCloseButton
{
if (hasCloseButton)
{
UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
[closeButton setBackgroundImage:[UIImage imageNamed:@"keyboard"] forState:UIControlStateNormal];
[closeButton sizeToFit];
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [[UIScreen mainScreen] bounds].size.width, closeButton.height)];
[view addSubview:closeButton];
[view setBackgroundColor:[UIColor clearColor]];
[closeButton setRight:view.width];
[closeButton setTop:0];
[closeButton addTarget:self action:@selector(resignFirstResponder) forControlEvents:UIControlEventTouchUpInside];
self.inputAccessoryView = view;
}
else
{
self.inputAccessoryView = nil;
}
}
@end
//
// UIView+Layout.h
// Gengmei
//
// Created by licong on 15/12/29.
// Copyright © 2015年 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIView (Layout)
/**
* Shortcut for frame.origin.x.
*
* Sets frame.origin.x = left
*/
@property (nonatomic) CGFloat left;
/**
* Shortcut for frame.origin.y
*
* Sets frame.origin.y = top
*/
@property (nonatomic) CGFloat top;
/**
* Shortcut for frame.origin.x + frame.size.width
*
* Sets frame.origin.x = right - frame.size.width
*/
@property (nonatomic) CGFloat right;
/**
* Shortcut for frame.origin.y + frame.size.height
*
* Sets frame.origin.y = bottom - frame.size.height
*/
@property (nonatomic) CGFloat bottom;
/**
* Shortcut for frame.size.width
*
* Sets frame.size.width = width
*/
@property (nonatomic) CGFloat width;
/**
* Shortcut for frame.size.height
*
* Sets frame.size.height = height
*/
@property (nonatomic) CGFloat height;
/**
* Shortcut for center.x
*
* Sets center.x = centerX
*/
@property (nonatomic) CGFloat centerX;
/**
* Shortcut for center.y
*
* Sets center.y = centerY
*/
@property (nonatomic) CGFloat centerY;
/**
* Return the x coordinate on the screen.
*/
@property (nonatomic, readonly) CGFloat screenX;
/**
* Return the y coordinate on the screen.
*/
@property (nonatomic, readonly) CGFloat screenY;
/**
* Return the x coordinate on the screen, taking into account scroll views.
*/
@property (nonatomic, readonly) CGFloat screenViewX;
/**
* Return the y coordinate on the screen, taking into account scroll views.
*/
@property (nonatomic, readonly) CGFloat screenViewY;
/**
* Return the view frame on the screen, taking into account scroll views.
*/
@property (nonatomic, readonly) CGRect screenFrame;
/**
* Shortcut for frame.origin
*/
@property (nonatomic) CGPoint origin;
/**
* Shortcut for frame.size
*/
@property (nonatomic) CGSize size;
/**
* Return the width in portrait or the height in landscape.
*/
@property (nonatomic, readonly) CGFloat orientationWidth;
/**
* Return the height in portrait or the width in landscape.
*/
@property (nonatomic, readonly) CGFloat orientationHeight;
/**
* Finds the first descendant view (including this view) that is a member of a particular class.
*/
- (UIView*)descendantOrSelfWithClass:(Class)cls;
/**
* Finds the first ancestor view (including this view) that is a member of a particular class.
*/
- (UIView*)ancestorOrSelfWithClass:(Class)cls;
/**
* Removes all subviews.
*/
- (void)removeAllSubviews;
@end
@interface UIView (GestureAction)
/**
Attaches the given block for a single tap action to the receiver.
@param block The block to execute.
*/
- (void)setTapActionWithBlock:(void (^)(void))block;
//- (void)removeTapAction;
/**
Attaches the given block for a long press action to the receiver.
@param block The block to execute.
*/
- (void)setLongPressActionWithBlock:(void (^)(void))block;
@end
//
// UIView+Layout.m
// Gengmei
//
// Created by licong on 15/12/29.
// Copyright © 2015年 Wanmeichuangyi. All rights reserved.
//
#import "UIView+Layout.h"
#import <objc/runtime.h>
static char kDTActionHandlerTapBlockKey;
static char kDTActionHandlerTapGestureKey;
static char kDTActionHandlerLongPressBlockKey;
static char kDTActionHandlerLongPressGestureKey;
@implementation UIView (Layout)
- (CGFloat)left {
return self.frame.origin.x;
}
- (void)setLeft:(CGFloat)x {
CGRect frame = self.frame;
frame.origin.x = x;
self.frame = frame;
}
- (CGFloat)top {
return self.frame.origin.y;
}
- (void)setTop:(CGFloat)y {
CGRect frame = self.frame;
frame.origin.y = y;
self.frame = frame;
}
- (CGFloat)right {
return self.frame.origin.x + self.frame.size.width;
}
- (void)setRight:(CGFloat)right {
CGRect frame = self.frame;
frame.origin.x = right - frame.size.width;
self.frame = frame;
}
- (CGFloat)bottom {
return self.frame.origin.y + self.frame.size.height;
}
- (void)setBottom:(CGFloat)bottom {
CGRect frame = self.frame;
frame.origin.y = bottom - frame.size.height;
self.frame = frame;
}
- (CGFloat)centerX {
return self.center.x;
}
- (void)setCenterX:(CGFloat)centerX {
self.center = CGPointMake(centerX, self.center.y);
}
- (CGFloat)centerY {
return self.center.y;
}
- (void)setCenterY:(CGFloat)centerY {
self.center = CGPointMake(self.center.x, centerY);
}
- (CGFloat)width {
return self.frame.size.width;
}
- (void)setWidth:(CGFloat)width {
CGRect frame = self.frame;
frame.size.width = width;
self.frame = frame;
}
- (CGFloat)height {
return self.frame.size.height;
}
- (void)setHeight:(CGFloat)height {
CGRect frame = self.frame;
frame.size.height = height;
self.frame = frame;
}
- (CGFloat)screenX {
CGFloat x = 0.0f;
for (UIView* view = self; view; view = view.superview) {
x += view.left;
}
return x;
}
- (CGFloat)screenY {
CGFloat y = 0.0f;
for (UIView* view = self; view; view = view.superview) {
y += view.top;
}
return y;
}
- (CGFloat)screenViewX {
CGFloat x = 0.0f;
for (UIView* view = self; view; view = view.superview) {
x += view.left;
if ([view isKindOfClass:[UIScrollView class]]) {
UIScrollView* scrollView = (UIScrollView*)view;
x -= scrollView.contentOffset.x;
}
}
return x;
}
- (CGFloat)screenViewY {
CGFloat y = 0;
for (UIView* view = self; view; view = view.superview) {
y += view.top;
if ([view isKindOfClass:[UIScrollView class]]) {
UIScrollView* scrollView = (UIScrollView*)view;
y -= scrollView.contentOffset.y;
}
}
return y;
}
- (CGRect)screenFrame {
return CGRectMake(self.screenViewX, self.screenViewY, self.width, self.height);
}
- (CGPoint)origin {
return self.frame.origin;
}
- (void)setOrigin:(CGPoint)origin {
CGRect frame = self.frame;
frame.origin = origin;
self.frame = frame;
}
- (CGSize)size {
return self.frame.size;
}
- (void)setSize:(CGSize)size {
CGRect frame = self.frame;
frame.size = size;
self.frame = frame;
}
- (CGFloat)orientationWidth {
return UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)
? self.height : self.width;
}
- (CGFloat)orientationHeight {
return UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)
? self.width : self.height;
}
- (UIView*)descendantOrSelfWithClass:(Class)cls {
if ([self isKindOfClass:cls])
return self;
for (UIView* child in self.subviews) {
UIView* it = [child descendantOrSelfWithClass:cls];
if (it)
return it;
}
return nil;
}
- (UIView*)ancestorOrSelfWithClass:(Class)cls {
if ([self isKindOfClass:cls]) {
return self;
} else if (self.superview) {
return [self.superview ancestorOrSelfWithClass:cls];
} else {
return nil;
}
}
- (void)removeAllSubviews {
while (self.subviews.count) {
UIView* child = self.subviews.lastObject;
[child removeFromSuperview];
}
}
- (CGPoint)offsetFromView:(UIView*)otherView {
CGFloat x = 0.0f, y = 0.0f;
for (UIView* view = self; view && view != otherView; view = view.superview) {
x += view.left;
y += view.top;
}
return CGPointMake(x, y);
}
@end
@implementation UIView (GestureAction)
- (void)setTapActionWithBlock:(void (^)(void))block
{
UITapGestureRecognizer *gesture = objc_getAssociatedObject(self, &kDTActionHandlerTapGestureKey);
if (!gesture)
{
gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(__handleActionForTapGesture:)];
[self addGestureRecognizer:gesture];
objc_setAssociatedObject(self, &kDTActionHandlerTapGestureKey, gesture, OBJC_ASSOCIATION_RETAIN);
}
objc_setAssociatedObject(self, &kDTActionHandlerTapBlockKey, block, OBJC_ASSOCIATION_COPY);
}
- (void)__handleActionForTapGesture:(UITapGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateRecognized)
{
void(^action)(void) = objc_getAssociatedObject(self, &kDTActionHandlerTapBlockKey);
if (action)
{
action();
}
}
}
- (void)setLongPressActionWithBlock:(void (^)(void))block
{
UILongPressGestureRecognizer *gesture = objc_getAssociatedObject(self, &kDTActionHandlerLongPressGestureKey);
if (!gesture)
{
gesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(__handleActionForLongPressGesture:)];
[self addGestureRecognizer:gesture];
objc_setAssociatedObject(self, &kDTActionHandlerLongPressGestureKey, gesture, OBJC_ASSOCIATION_RETAIN);
}
objc_setAssociatedObject(self, &kDTActionHandlerLongPressBlockKey, block, OBJC_ASSOCIATION_COPY);
}
- (void)__handleActionForLongPressGesture:(UITapGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan)
{
void(^action)(void) = objc_getAssociatedObject(self, &kDTActionHandlerLongPressBlockKey);
if (action)
{
action();
}
}
}
@end
//
// UIView+LineWithAutolayout.h
// ZhengXing
//
// Created by wangyang on 11/6/14.
// Copyright (c) 2014 Wanmei Creative. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIView (LineWithAutolayout)
/**
* @author licong, 16-12-28 19:12:48
*
* alloc一个无固定大小的view,用作分割线
*
* @return 返回一个view
*
* @since 5.8
*/
- (UIView *)addLineWithoutFrame;
/**
* @author licong, 16-12-28 19:12:16
*
* 在指定view(就是self)的最上边缘,加一根与指定view宽度相等的头部边缘线
*
* @return 返回alloc的分割线
*
* @since 5.8
*/
- (UIView *)addTopLine;
/**
* @author licong, 16-12-28 19:12:16
*
* 在view(就是self)的最下边缘,加一根与指定view宽度相等的底部边缘线
*
* @return 返回alloc的分割线
*
* @since 5.8
*/
- (UIView *)addBottomLine;
/**
* @author licong, 16-12-28 19:13:30
*
* 给指定的view(就是self),加一跟中心水平线
*
* @param left 竖直线距离设定的view左边的距离
* @param right 竖直线距离设定的view右边的距离
*
* @return 返回alloc的中心竖直线
*
* @since 5.8
*/
- (UIView *)addCenterHorizontalLineWithLeft:(CGFloat)left right:(CGFloat)right;
/**
* @author licong, 16-12-28 19:12:40
*
* 给指定的view(就是self),加一跟中心竖直线
*
* @param top 竖直线距离设定的view上边的距离
* @param bottom 竖直线距离设定的view下边的距离
*
* @return 返回alloc的中心竖直线
*
* @since 5.8
*/
- (UIView *)addCenterVerticalLineWithTop:(CGFloat)top bottom:(CGFloat)bottom;
/**
* @author licong, 16-12-28 19:12:19
*
* 给指定的view(就是self)的最下边缘,加一根长度可设置的头部边缘线(最长不超过该view的宽度)
*
* @param left 头部边缘线距离设定view左边的距离
* @param right 头部边缘线距离设定view左边的距离
*
* @return 返回长度可设置的头部边缘线
*
* @since 5.8
*/
- (UIView *)addTopLineWithLeft:(CGFloat)left right:(CGFloat)right;
/**
* @author licong, 16-12-28 19:12:16
*
* 给指定的view(就是self)的最下边缘,加一根长度可设置的底部边缘线(最长不超过该view的宽度)
*
* @param left 底部边缘线距离设定view左边缘的距离
* @param right 底部边缘线距离设定view右边缘的距离
*
* @return 返回长度可设置的底部边缘线
*
* @since 5.8
*/
- (UIView *)addBottomLineWithLeft:(CGFloat)left right:(CGFloat)right;
/**
* @author licong, 16-12-29 11:12:02
*
* 给指定的view(就是self),加一根长度可设置的水平线,以Top为Y方向的基准点
*
* @param top 水平线距离设定view上边缘的距离
* @param left 水平线距离设定view左边缘的距离
* @param right 水平线距离设定view右边缘的距离
*
* @return 返回水平线
*
* @since 5.8
*/
- (UIView *)addHorizontalLineWithTop:(CGFloat)top left:(CGFloat)left right:(CGFloat)right;
/**
* @author licong, 16-12-29 11:12:48
*
* 给指定的view(就是self),加一根长度可设置的水平线,以Bottom为Y方向的基准点
*
* @param bottom 水平线距离设定view下边缘的距离
* @param left 水平线距离设定view左边缘的距离
* @param right 水平线距离设定view右边缘的距离
*
* @return 返回水平线
*
* @since 5.8
*/
- (UIView *)addHorizontalLineWithBottom:(CGFloat)bottom left:(CGFloat)left right:(CGFloat)right;
/**
* @author licong, 16-12-29 11:12:48
*
* 给指定的view(就是self),加一根长度可设置的竖直线,以Left为X方向的基准点
*
* @param left 竖直线距离设定view左边缘的距离
* @param top 竖直线距离设定view上边缘的距离
* @param bottom 竖直线距离设定view下边缘的距离
*
* @return 返回竖直线
*
* @since 5.8
*/
- (UIView *)addVerticalLineWithLeft:(CGFloat)left top:(CGFloat)top bottom:(CGFloat)bottom;
/**
* @author licong, 16-12-29 11:12:54
*
* 给指定的view(就是self),加一根长度可设置的竖直线,以Right为X方向的基准点
*
* @param right 竖直线距离设定view右边缘的距离
* @param top 竖直线距离设定view上边缘的距离
* @param bottom 竖直线距离设定view下边缘的距离
*
* @return 返回竖直线
*
* @since 58
*/
- (UIView *)addVerticalLineWithRight:(CGFloat)right top:(CGFloat)top bottom:(CGFloat)bottom;
@end
//
// UIView+LineWithAutolayout.m
// ZhengXing
//
// Created by wangyang on 11/6/14.
// Copyright (c) 2014 Wanmei Creative. All rights reserved.
//
#import "UIView+LineWithAutolayout.h"
#import <Masonry/Masonry.h>
#import "GMConstant.h"
#import "GMTheme.h"
@interface OnePixelLine : UIView
@end
@implementation OnePixelLine
@end
@implementation UIView (LineWithAutolayout)
- (UIView *)addLineWithoutFrame{
UIView *line = [OnePixelLine new];
line.backgroundColor = SEPARATOR_LINE_COLOR;
[self addSubview:line];
return line;
}
- (UIView *)addTopLine
{
UIView *line = [self addLineWithoutFrame];
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(0);
make.left.mas_equalTo(0);
make.right.mas_equalTo(0);
make.height.offset(ONE_PIXEL);
}];
return line;
}
- (UIView *)addBottomLine
{
UIView *line = [self addLineWithoutFrame];
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(0);
make.left.mas_equalTo(0);
make.right.mas_equalTo(0);
make.height.offset(ONE_PIXEL);
}];
return line;
}
- (UIView *)addCenterHorizontalLineWithLeft:(CGFloat)left right:(CGFloat)right{
UIView *line = [self addLineWithoutFrame];
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.mas_equalTo(0);
make.left.mas_equalTo(left);
make.right.mas_equalTo(right);
make.height.offset(ONE_PIXEL);
}];
return line;
}
- (UIView *)addCenterVerticalLineWithTop:(CGFloat)top bottom:(CGFloat)bottom{
UIView *line = [self addLineWithoutFrame];
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(top);
make.centerX.mas_equalTo(0);
make.bottom.mas_equalTo(bottom);
make.width.offset(ONE_PIXEL);
}];
return line;
}
- (UIView *)addTopLineWithLeft:(CGFloat)left right:(CGFloat)right{
UIView *line = [self addLineWithoutFrame];
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(0);
make.left.mas_equalTo(left);
make.right.mas_equalTo(right);
make.height.offset(ONE_PIXEL);
}];
return line;
}
- (UIView *)addBottomLineWithLeft:(CGFloat)left right:(CGFloat)right{
UIView *line = [self addLineWithoutFrame];
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(0);
make.left.mas_equalTo(left);
make.right.mas_equalTo(right);
make.height.offset(ONE_PIXEL);
}];
return line;
}
- (UIView *)addHorizontalLineWithTop:(CGFloat)top left:(CGFloat)left right:(CGFloat)right{
UIView *line = [self addLineWithoutFrame];
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(top);
make.left.mas_equalTo(left);
make.right.mas_equalTo(right);
make.height.offset(ONE_PIXEL);
}];
return line;
}
- (UIView *)addHorizontalLineWithBottom:(CGFloat)bottom left:(CGFloat)left right:(CGFloat)right{
UIView *line = [self addLineWithoutFrame];
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(bottom);
make.left.mas_equalTo(left);
make.right.mas_equalTo(right);
make.height.offset(ONE_PIXEL);
}];
return line;
}
- (UIView *)addVerticalLineWithLeft:(CGFloat)left top:(CGFloat)top bottom:(CGFloat)bottom{
UIView *line = [self addLineWithoutFrame];
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(top);
make.left.mas_equalTo(left);
make.bottom.mas_equalTo(bottom);
make.width.offset(ONE_PIXEL);
}];
return line;
}
- (UIView *)addVerticalLineWithRight:(CGFloat)right top:(CGFloat)top bottom:(CGFloat)bottom{
UIView *line = [self addLineWithoutFrame];
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(top);
make.right.mas_equalTo(right);
make.bottom.mas_equalTo(bottom);
make.width.offset(ONE_PIXEL);
}];
return line;
}
@end
\ No newline at end of file
//
// UIViewController+ChildSwitch.h
// segmentSwitch
//
// Created by wangyang on 5/13/15.
// Copyright (c) 2015 IGIU. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIViewController (ChildControllerSwitch)
@property (nonatomic, strong) IBOutlet UIView *childContainer;
@property (nonatomic, strong) UIViewController *currentController;
/**
* @brief 设置第一个要显示的controller
*
*/
- (void)setFirstController:(UIViewController *)controller;
- (void)setFirstController:(UIViewController *)controller otherController:(NSArray *)otherControllers;
- (void)switchToChildControllerAtIndex:(NSUInteger)index completion:(void (^)(void))completionBlock;
@end
//
// UIViewController+ChildSwitch.m
// segmentSwitch
//
// Created by wangyang on 5/13/15.
// Copyright (c) 2015 IGIU. All rights reserved.
//
#import "UIViewController+ChildControllerSwitch.h"
#import <objc/runtime.h>
@implementation UIViewController (ChildControllerSwitch)
/**
* set方法是为了适应IB,如果纯代码可以不用set方法
*/
- (void)setChildContainer:(UIView *)childContainer{
objc_setAssociatedObject(self, @selector(childContainer), childContainer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIView *)childContainer{
UIView *childContainer = objc_getAssociatedObject(self, _cmd);
if (!childContainer) {
childContainer = [UIView new];
objc_setAssociatedObject(self, _cmd, childContainer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return childContainer;
}
- (void)setCurrentController:(UIViewController *)currentController{
objc_setAssociatedObject(self, NSSelectorFromString(@"currentController"), currentController, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIViewController *)currentController{
return objc_getAssociatedObject(self, NSSelectorFromString(@"currentController"));
}
- (void)setFirstController:(UIViewController *)controller{
controller.view.frame = self.childContainer.bounds;
[self.childContainer addSubview:controller.view];
self.currentController = controller;
[self addChildViewController:controller];
[self.currentController didMoveToParentViewController:self];
}
- (void)setFirstController:(UIViewController *)controller otherController:(NSArray *)otherControllers;{
NSAssert(controller, @"不能设置nil为第一个controller");
NSAssert(self.childContainer.superview, @"self.childContainer must have a superview");
[self setFirstController:controller];
for (UIViewController *other in otherControllers) {
[self addChildViewController:other];
}
}
- (void)switchToChildControllerAtIndex:(NSUInteger)index completion:(void (^)(void))completionBlock
{
UIViewController *to = self.childViewControllers[index];
UIViewController *from = self.currentController;
if (to == from) {
if (completionBlock) {
completionBlock();
}
return;
}
[self.currentController willMoveToParentViewController:nil];
to.view.frame = self.childContainer.bounds;
to.view.alpha = 0;
[self transitionFromViewController:from toViewController:to duration:0.1 options:UIViewAnimationOptionCurveLinear animations:^{
from.view.alpha = 0;
to.view.alpha = 1;
} completion:^(BOOL finished) {
// didMoveToParentViewController必须在transition completion之后
[to didMoveToParentViewController:self];
self.currentController = to;
if (completionBlock) {
completionBlock();
}
}];
}
@end
// The MIT License (MIT)
//
// Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog )
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#import <UIKit/UIKit.h>
/// "UINavigation+FDFullscreenPopGesture" extends UINavigationController's swipe-
/// to-pop behavior in iOS 7+ by supporting fullscreen pan gesture. Instead of
/// screen edge, you can now swipe from any place on the screen and the onboard
/// interactive pop transition works seamlessly.
///
/// Adding the implementation file of this category to your target will
/// automatically patch UINavigationController with this feature.
@interface UINavigationController (FDFullscreenPopGesture)
/// The gesture recognizer that actually handles interactive pop.
@property (nonatomic, strong, readonly) UIPanGestureRecognizer *fd_fullscreenPopGestureRecognizer;
/// A view controller is able to control navigation bar's appearance by itself,
/// rather than a global way, checking "fd_prefersNavigationBarHidden" property.
/// Default to YES, disable it if you don't want so.
@property (nonatomic, assign) BOOL fd_viewControllerBasedNavigationBarAppearanceEnabled;
@end
/// Allows any view controller to disable interactive pop gesture, which might
/// be necessary when the view controller itself handles pan gesture in some
/// cases.
@interface UIViewController (FDFullscreenPopGesture)
/// Whether the interactive pop gesture is disabled when contained in a navigation
/// stack.
@property (nonatomic, assign) BOOL fd_interactivePopDisabled;
/// Indicate this view controller prefers its navigation bar hidden or not,
/// checked when view controller based navigation bar's appearance is enabled.
/// Default to NO, bars are more likely to show.
@property (nonatomic, assign) BOOL fd_prefersNavigationBarHidden;
/// Max allowed initial distance to left edge when you begin the interactive pop
/// gesture. 0 by default, which means it will ignore this limit.
@property (nonatomic, assign) CGFloat fd_interactivePopMaxAllowedInitialDistanceToLeftEdge;
@end
// The MIT License (MIT)
//
// Copyright (c) 2015-2016 forkingdog ( https://github.com/forkingdog )
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#import "UINavigationController+FDFullscreenPopGesture.h"
#import <objc/runtime.h>
@interface _FDFullscreenPopGestureRecognizerDelegate : NSObject <UIGestureRecognizerDelegate>
@property (nonatomic, weak) UINavigationController *navigationController;
@end
@implementation _FDFullscreenPopGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer
{
// Ignore when no view controller is pushed into the navigation stack.
if (self.navigationController.viewControllers.count <= 1) {
return NO;
}
// Ignore when the active view controller doesn't allow interactive pop.
UIViewController *topViewController = self.navigationController.viewControllers.lastObject;
if (topViewController.fd_interactivePopDisabled) {
return NO;
}
// Ignore when the beginning location is beyond max allowed initial distance to left edge.
CGPoint beginningLocation = [gestureRecognizer locationInView:gestureRecognizer.view];
CGFloat maxAllowedInitialDistance = topViewController.fd_interactivePopMaxAllowedInitialDistanceToLeftEdge;
if (maxAllowedInitialDistance > 0 && beginningLocation.x > maxAllowedInitialDistance) {
return NO;
}
// Ignore pan gesture when the navigation controller is currently in transition.
if ([[self.navigationController valueForKey:@"_isTransitioning"] boolValue]) {
return NO;
}
// Prevent calling the handler when the gesture begins in an opposite direction.
CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view];
if (translation.x <= 0) {
return NO;
}
return YES;
}
@end
typedef void (^_FDViewControllerWillAppearInjectBlock)(UIViewController *viewController, BOOL animated);
@interface UIViewController (FDFullscreenPopGesturePrivate)
@property (nonatomic, copy) _FDViewControllerWillAppearInjectBlock fd_willAppearInjectBlock;
@end
@implementation UIViewController (FDFullscreenPopGesturePrivate)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(fd_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)fd_viewWillAppear:(BOOL)animated
{
// Forward to primary implementation.
[self fd_viewWillAppear:animated];
if (self.fd_willAppearInjectBlock) {
self.fd_willAppearInjectBlock(self, animated);
}
}
- (_FDViewControllerWillAppearInjectBlock)fd_willAppearInjectBlock
{
return objc_getAssociatedObject(self, _cmd);
}
- (void)setFd_willAppearInjectBlock:(_FDViewControllerWillAppearInjectBlock)block
{
objc_setAssociatedObject(self, @selector(fd_willAppearInjectBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
@implementation UINavigationController (FDFullscreenPopGesture)
+ (void)load
{
// Inject "-pushViewController:animated:"
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(pushViewController:animated:);
SEL swizzledSelector = @selector(fd_pushViewController:animated:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)fd_pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if (![self.interactivePopGestureRecognizer.view.gestureRecognizers containsObject:self.fd_fullscreenPopGestureRecognizer]) {
// Add our own gesture recognizer to where the onboard screen edge pan gesture recognizer is attached to.
[self.interactivePopGestureRecognizer.view addGestureRecognizer:self.fd_fullscreenPopGestureRecognizer];
// Forward the gesture events to the private handler of the onboard gesture recognizer.
NSArray *internalTargets = [self.interactivePopGestureRecognizer valueForKey:@"targets"];
id internalTarget = [internalTargets.firstObject valueForKey:@"target"];
SEL internalAction = NSSelectorFromString(@"handleNavigationTransition:");
self.fd_fullscreenPopGestureRecognizer.delegate = self.fd_popGestureRecognizerDelegate;
[self.fd_fullscreenPopGestureRecognizer addTarget:internalTarget action:internalAction];
// Disable the onboard gesture recognizer.
self.interactivePopGestureRecognizer.enabled = NO;
}
// Handle perferred navigation bar appearance.
[self fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:viewController];
// Forward to primary implementation.
if (![self.viewControllers containsObject:viewController]) {
[self fd_pushViewController:viewController animated:animated];
}
}
- (void)fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:(UIViewController *)appearingViewController
{
if (!self.fd_viewControllerBasedNavigationBarAppearanceEnabled) {
return;
}
__weak typeof(self) weakSelf = self;
_FDViewControllerWillAppearInjectBlock block = ^(UIViewController *viewController, BOOL animated) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf setNavigationBarHidden:viewController.fd_prefersNavigationBarHidden animated:animated];
}
};
// Setup will appear inject block to appearing view controller.
// Setup disappearing view controller as well, because not every view controller is added into
// stack by pushing, maybe by "-setViewControllers:".
appearingViewController.fd_willAppearInjectBlock = block;
UIViewController *disappearingViewController = self.viewControllers.lastObject;
if (disappearingViewController && !disappearingViewController.fd_willAppearInjectBlock) {
disappearingViewController.fd_willAppearInjectBlock = block;
}
}
- (_FDFullscreenPopGestureRecognizerDelegate *)fd_popGestureRecognizerDelegate
{
_FDFullscreenPopGestureRecognizerDelegate *delegate = objc_getAssociatedObject(self, _cmd);
if (!delegate) {
delegate = [[_FDFullscreenPopGestureRecognizerDelegate alloc] init];
delegate.navigationController = self;
objc_setAssociatedObject(self, _cmd, delegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return delegate;
}
- (UIPanGestureRecognizer *)fd_fullscreenPopGestureRecognizer
{
UIPanGestureRecognizer *panGestureRecognizer = objc_getAssociatedObject(self, _cmd);
if (!panGestureRecognizer) {
panGestureRecognizer = [[UIPanGestureRecognizer alloc] init];
panGestureRecognizer.maximumNumberOfTouches = 1;
objc_setAssociatedObject(self, _cmd, panGestureRecognizer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return panGestureRecognizer;
}
- (BOOL)fd_viewControllerBasedNavigationBarAppearanceEnabled
{
NSNumber *number = objc_getAssociatedObject(self, _cmd);
if (number) {
return number.boolValue;
}
self.fd_viewControllerBasedNavigationBarAppearanceEnabled = YES;
return YES;
}
- (void)setFd_viewControllerBasedNavigationBarAppearanceEnabled:(BOOL)enabled
{
SEL key = @selector(fd_viewControllerBasedNavigationBarAppearanceEnabled);
objc_setAssociatedObject(self, key, @(enabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
@implementation UIViewController (FDFullscreenPopGesture)
- (BOOL)fd_interactivePopDisabled
{
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
- (void)setFd_interactivePopDisabled:(BOOL)disabled
{
objc_setAssociatedObject(self, @selector(fd_interactivePopDisabled), @(disabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)fd_prefersNavigationBarHidden
{
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
- (void)setFd_prefersNavigationBarHidden:(BOOL)hidden
{
objc_setAssociatedObject(self, @selector(fd_prefersNavigationBarHidden), @(hidden), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (CGFloat)fd_interactivePopMaxAllowedInitialDistanceToLeftEdge
{
#if CGFLOAT_IS_DOUBLE
return [objc_getAssociatedObject(self, _cmd) doubleValue];
#else
return [objc_getAssociatedObject(self, _cmd) floatValue];
#endif
}
- (void)setFd_interactivePopMaxAllowedInitialDistanceToLeftEdge:(CGFloat)distance
{
SEL key = @selector(fd_interactivePopMaxAllowedInitialDistanceToLeftEdge);
objc_setAssociatedObject(self, key, @(MAX(0, distance)), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
//
// GMAnnotation.h
// Gengmei
//
// Created by Thierry on 4/25/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
@interface GMAnnotation : NSObject<MKAnnotation>
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;
-(id) initWithCoordinate:(CLLocationCoordinate2D) coords;
@end
//
// GMAnnotation.m
// Gengmei
//
// Created by Thierry on 4/25/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMAnnotation.h"
@implementation GMAnnotation
-(id) initWithCoordinate:(CLLocationCoordinate2D) coords
{
if (self = [super init]) {
self.coordinate = coords;
}
return self;
}
@end
//
// GMButton.h
// Gengmei
//
// Created by Thierry on 12/26/14.
// Copyright (c) 2014 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
//见设计稿(UI组件化->组件_按钮.sketch)
typedef NS_ENUM(NSUInteger, GMButtonType) {
GMButtonTypeGreen, //绿色矩形(见左1)
GMButtonTypeGreenRound, //绿色带6px的圆角矩形(见左2)
GMButtonTypeGreenSemicircle, //绿色半圆矩形(见左3)
GMButtonTypeGreenBorder, //绿色边框矩形,只有两种状态(正常、失效,见左4)
GMButtonTypeRed, //红色矩形(见右1)
GMButtonTypeRedRound, //红色色带6px的圆角矩形(见右2)
GMButtonTypeRedSemicircle, //红色半圆矩形(见右3)
GMButtonTypeRedBorder, //红色边框矩形,只有两种状态(正常、失效、见右4)
GMButtonTypeGrayBorderSemicircle, //灰色边框半圆矩形,只有两种状态(正常、失效、见右5)
};
@interface GMButton : UIButton
/**
* @author licong, 16-12-30 17:12:10
*
* button高亮时的背景色
*
* @since 5.8
*/
@property (nonatomic, readonly) UIColor *highlightBackgroundColor;
/**
* @author licong, 16-12-30 17:12:31
*
* button在normal时候的背景色
*
* @since 5.8
*/
@property (nonatomic, readonly) UIColor *normalBackgroundColor;
/**
* @author licong, 16-12-30 17:12:59
*
* button 在选中状态时候的背景色
*
* @since 5.8
*/
@property (nonatomic, readonly) UIColor *selectedBackgroundColor;
/**
* @author licong, 16-12-30 17:12:55
*
* 是否支持Button自适应热区,Apple人机交互指南建议,可点击控件的热区最好是44*44,默认是NO,不支持
*
* @since 5.8
*/
@property (nonatomic, assign) BOOL enableAdaptive;
/**
* @author licong, 16-12-30 17:12:31
*
* 自适应热区宽,默认是 44
*
* @since 5.8
*/
@property (nonatomic, assign) float adaptiveHotAreaWidth;
/**
* @author licong, 16-12-30 17:12:09
*
* 自适应热区高,默认是 44
*
* @since 5.8
*/
@property (nonatomic, assign) float adaptiveHotAreaHeight;
/**
* @author licong, 16-12-30 17:12:30
*
* 初始化方法
*
* @since 5.8
*/
- (void)setup __attribute__((objc_requires_super));
/**
* @author licong, 16-12-30 17:12:46
*
* 创建一个button,并设置好它的title、backgroundColor、size、titleColor
*
* @param title title
* @param bgColor backgroundColor
* @param size 大小
* @param titleColor title标题
*
* @return 返回创建好的button
*
* @since 5.8
*/
+ (instancetype)buttonWithTitle:(nullable NSString *)title
backgroundColor:(nullable UIColor *)bgColor
titleFontSize:(CGFloat)size
titleColor:(nullable UIColor *)titleColor;
/**
* @author licong
*
* UI组件化后自定制Button
*
* @param customType 工厂模式下的button类型
*
* @return 返回创建好的button
*
* @since 6.2.0
*/
+ (instancetype)buttonWithCustomType:(GMButtonType)customType;
/**
* @author licong, 16-12-30 17:12:29
*
* 根据Button的状态设置对应的背景颜色
*
* @param backgroundColor button的背景颜色
* @param state button的controlState
*
* @since 5.8
*/
- (void)setBackgroundColor:(UIColor *)backgroundColor forState:(UIControlState)state;
@end
NS_ASSUME_NONNULL_END
\ No newline at end of file
//
// GMButton.m
// Gengmei
//
// Created by Thierry on 12/26/14.
// Copyright (c) 2014 Wanmeichuangyi. All rights reserved.
//
#import "GMButton.h"
#import "GMFont.h"
#import "GMTheme.h"
#import "GMConstant.h"
@interface GMButton ()
@property (nonatomic, strong) UIColor *highlightBackgroundColor;
@property (nonatomic, strong) UIColor *normalBackgroundColor;
@property (nonatomic, strong) UIColor *selectedBackgroundColor;
@property (nonatomic, strong) UIColor *disableBackgroundColor;
@property (nonatomic, assign) GMButtonType customType;
@property (nonatomic, strong) UIColor *normalBorderColor;
@property (nonatomic, strong) UIColor *disableBorderColor;
@end
@implementation GMButton
#pragma mark - 初始化
-(id)init
{
self = [super init];
if (self) {
[self setup];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)awakeFromNib{
[super awakeFromNib];
[self setup];
}
- (void)setup{
_adaptiveHotAreaWidth = 44.0;
_adaptiveHotAreaHeight = 44.0;
_normalBackgroundColor = [UIColor clearColor];
}
#pragma mark - 实例化方法
+ (instancetype)buttonWithTitle:(NSString *)title
backgroundColor:(UIColor *)bgColor
titleFontSize:(CGFloat)size
titleColor:(UIColor *)titleColor{
GMButton *button = [GMButton buttonWithType:UIButtonTypeCustom];
if (bgColor) {
button.backgroundColor = bgColor;
}
if (title) {
[button setTitle:title forState:UIControlStateNormal];
}
if (size > 0) {
button.titleLabel.font = GMFont(14);
}
if (titleColor) {
[button setTitleColor:titleColor forState:UIControlStateNormal];
}
return button;
}
+ (instancetype)buttonWithCustomType:(GMButtonType)customType{
GMButton *button = [GMButton buttonWithType:UIButtonTypeCustom];
button.clipsToBounds = YES;
button.customType = customType;
[button setTitle:@"按钮" forState:UIControlStateNormal];
switch (customType) {
case GMButtonTypeGreen:
[button setGreenAppearance];
break;
case GMButtonTypeGreenRound:
[button setGreenAppearance];
button.layer.cornerRadius = 6 * ONE_PIXEL;
break;
case GMButtonTypeGreenSemicircle:
[button setGreenAppearance];
break;
case GMButtonTypeGreenBorder:
button.layer.borderWidth = ONE_PIXEL;
button.layer.cornerRadius = 6 * ONE_PIXEL;
button.normalBorderColor = MAIN_VISUAL_COLOR;
button.disableBorderColor = DISABLE_COLOR;
[button setBackgroundColor: [UIColor clearColor] forState:UIControlStateNormal];
[button setTitleColor:MAIN_VISUAL_COLOR forState:UIControlStateNormal];
[button setTitleColor:DISABLE_COLOR forState:UIControlStateDisabled];
button.enabled = YES;
break;
case GMButtonTypeRed:
[button setRedAppearance];
break;
case GMButtonTypeRedRound:
[button setRedAppearance];
button.layer.cornerRadius = 6 * ONE_PIXEL;
break;
case GMButtonTypeRedSemicircle:
[button setRedAppearance];
break;
case GMButtonTypeRedBorder:
button.layer.borderWidth = ONE_PIXEL;
button.layer.cornerRadius = 6 * ONE_PIXEL;
button.normalBorderColor = SECONDARY_VISUAL_COLOR;
button.disableBorderColor = DISABLE_COLOR;
[button setBackgroundColor: [UIColor clearColor] forState:UIControlStateNormal];
[button setTitleColor:SECONDARY_VISUAL_COLOR forState:UIControlStateNormal];
[button setTitleColor:DISABLE_COLOR forState:UIControlStateDisabled];
button.enabled = YES;
break;
case GMButtonTypeGrayBorderSemicircle:
button.layer.borderColor = SEPARATOR_LINE_COLOR.CGColor;
button.layer.borderWidth = ONE_PIXEL;
[button setBackgroundColor: BACKGROUND_COLOR forState:UIControlStateNormal];
[button setBackgroundColor: SEPARATOR_LINE_COLOR forState:UIControlStateHighlighted];
[button setTitleColor:BODY_TEXT_COLOR forState:UIControlStateNormal];
default:
break;
}
return button;
}
//为半圆的设置半径
- (void)layoutSubviews{
[super layoutSubviews];
if (_customType == GMButtonTypeGreenSemicircle || _customType == GMButtonTypeRedSemicircle || _customType == GMButtonTypeGrayBorderSemicircle) {
self.layer.cornerRadius = MIN(self.frame.size.height, self.frame.size.width)/2;
}
}
#pragma mark - Button Appearance
- (void)setGreenAppearance{
[self setTitleColor:WHITE_COLOR forState:UIControlStateNormal];
[self setBackgroundColor:BUTTON_NOMARL_GREEN_COLOR forState:UIControlStateNormal];
[self setBackgroundColor:BUTTON_HIGHLIGHT_GREEN_COLOR forState:UIControlStateHighlighted];
[self setBackgroundColor:BUTTON_DISABLE_COLOR forState:UIControlStateDisabled];
}
- (void)setRedAppearance{
[self setTitleColor:WHITE_COLOR forState:UIControlStateNormal];
[self setBackgroundColor:BUTTON_NOMARL_RED_COLOR forState:UIControlStateNormal];
[self setBackgroundColor:BUTTON_HIGHLIGHT_RED_COLOR forState:UIControlStateHighlighted];
[self setBackgroundColor:BUTTON_DISABLE_COLOR forState:UIControlStateDisabled];
}
#pragma mark - Background Color
- (void)setBackgroundColor:(UIColor *)backgroundColor forState:(UIControlState)state{
if (state == UIControlStateNormal) {
_normalBackgroundColor = backgroundColor;
[self setBackgroundColor:_normalBackgroundColor];
}else if (state == UIControlStateHighlighted){
_highlightBackgroundColor = backgroundColor;
}else if (state == UIControlStateSelected){
_selectedBackgroundColor = backgroundColor;
}else if (state == UIControlStateDisabled) {
_disableBackgroundColor = backgroundColor;
}
}
#pragma mark - Setter
- (void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];
if (_highlightBackgroundColor && highlighted) {
self.backgroundColor = _highlightBackgroundColor;
}else{
if (self.selected && _selectedBackgroundColor) {
self.backgroundColor = _selectedBackgroundColor;
}else{
self.backgroundColor = _normalBackgroundColor;
}
}
}
- (void)setSelected:(BOOL)selected{
[super setSelected:selected];
if (_selectedBackgroundColor && selected) {
self.backgroundColor = _selectedBackgroundColor;
}else{
self.backgroundColor = _normalBackgroundColor;
}
}
- (void)setBackgroundColor:(UIColor *)backgroundColor{
[super setBackgroundColor:backgroundColor];
// 容错处理,只在为normal状态,才设置_normalBackgroundColor。
// 比如在controller里只调用了setBackgroundColor,为了保证在normal时回复到normal颜色,必须设置好_normalBackgroundColor
if(self.state == UIControlStateNormal){
_normalBackgroundColor = backgroundColor;
}
}
- (void)setEnabled:(BOOL)enabled{
[super setEnabled:enabled];
if (enabled) {
self.backgroundColor = _normalBackgroundColor;
if (_customType == GMButtonTypeGreenBorder || _customType == GMButtonTypeRedBorder) {
self.layer.borderColor = _normalBorderColor.CGColor;
}
}else{
self.backgroundColor = _disableBackgroundColor;
if (_customType == GMButtonTypeGreenBorder || _customType == GMButtonTypeRedBorder) {
self.layer.borderColor = _disableBorderColor.CGColor;
}
}
}
#pragma mark - 支持自适应热区
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
//默认不支持放大热区
if (!_enableAdaptive) {
return [super pointInside:point withEvent:event];
}
CGRect bounds = self.bounds;
//若原热区小于 _adaptiveHotAreaWidth x _adaptiveHotAreaHeight,则放大热区,否则保持原大小不变
CGFloat widthDelta = MAX(_adaptiveHotAreaWidth - bounds.size.width, 0);
CGFloat heightDelta = MAX(_adaptiveHotAreaHeight - bounds.size.height, 0);
bounds = CGRectInset(bounds, -0.5 * widthDelta, -0.5 * heightDelta);
return CGRectContainsPoint(bounds, point);
}
@end
//
// GMCollectionView
// Gengmei
//
// Created by Thierry on 1/9/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface GMCollectionView : UICollectionView
@end
//
// GMCollectionView
// Gengmei
//
// Created by Thierry on 1/9/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMCollectionView.h"
@implementation GMCollectionView
@end
//
// GMSimpleCell.h
// Gengmei
// 简单的Collection Cell(图片+文字)
// Created by Thierry on 1/7/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "GMImageView.h"
#import "GMLabel.h"
@interface GMCollectionViewCell : UICollectionViewCell
@property (nonatomic,retain) GMImageView *imageView;
@property (nonatomic,retain) GMLabel *label;
- (void)setup;
@end
//
// GMSimpleCell.m
// Gengmei
//
// Created by Thierry on 1/7/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMCollectionViewCell.h"
#import "GMFont.h"
#import "GMConstant.h"
#import "GMTheme.h"
#define CELL_HEIGHT self.contentView.frame.size.height
#define CELL_WIDTH self.contentView.frame.size.width
@implementation GMCollectionViewCell
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)awakeFromNib{
[super awakeFromNib];
[self setup];
}
- (void)setup{
self.clipsToBounds = YES;
_imageView = [[GMImageView alloc] initWithFrame:CGRectMake((CELL_WIDTH-45)/2, 10, 45, 45)];
_label = [[GMLabel alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(_imageView.frame)+6, CELL_WIDTH, 13)];
_label.textColor = HEADLINE_TEXT_COLOR;
_label.font = GMFont(12);
[_label setTextAlignment:NSTextAlignmentCenter];
[self.contentView addSubview:_imageView];
[self.contentView addSubview:_label];
}
@end
//
// GMControl.h
// Gengmei
//
// Created by wangyang on 9/14/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface GMControl : UIControl
/**
* @brief 不需要重写 init 方法,如果代码里调用了 init 方法,SDK 会自动调用 initWithFrame
*
*/
- (void)setup __attribute__((objc_requires_super));
@end
//
// GMControl.m
// Gengmei
//
// Created by wangyang on 9/14/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMControl.h"
@implementation GMControl
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
[self setup];
return self;
}
- (void)setup{
}
@end
//
// GMDatePicker.h
// ZhengXing
//
// Created by Sean Lee on 11/19/14.
// Copyright (c) 2014 Wanmei Creative. All rights reserved.
//
#import <UIKit/UIKit.h>
@protocol GMDatePickerDelegate <NSObject>
/**
* @author licong, 16-01-08 15:01:24
*
* 弹出DatePicker
*
* @since 5.8.0
*/
- (void)openPicker;
@end
@interface GMDatePickerView : UIView<UIGestureRecognizerDelegate>
@property (nonatomic, strong) UIView * contentView;
@property (nonatomic, strong) UIDatePicker *picker;
@property (nonatomic, assign) id<GMDatePickerDelegate> delegate;
@property (nonatomic, strong) NSString * leftButtonTitle;
@property (nonatomic, strong) NSString * titleLabelTitle;
@property (nonatomic, copy) void (^ confirmButtonClickBolck) (NSDate * selecteDate);
-(void)show;
-(void)hide;
@end
//
// GMDatePicker.m
// ZhengXing
//
// Created by Sean Lee on 11/19/14.
// Copyright (c) 2014 Wanmei Creative. All rights reserved.
//
#import "GMDatePickerView.h"
#import "GMFont.h"
#import <Masonry/Masonry.h>
#import "GMConstant.h"
#import "GMTheme.h"
#define DATAPICKER_HEIGHT
@interface GMDatePickerView ()
@property (nonatomic,strong)UIButton * leftButton;
@property (nonatomic,strong)UILabel * titleLabel;
@property (nonatomic,strong)UIButton * rightButton;
@end
@implementation GMDatePickerView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor colorWithWhite:0 alpha:.6];
self.hidden = YES;
[self createDatePicker];
}
return self;
}
- (void)createDatePicker{
_contentView = [[UIView alloc]initWithFrame:CGRectZero];
_contentView.frame = CGRectMake(0, [[UIScreen mainScreen] bounds].size.width, self.frame.size.width, 255);
_contentView.backgroundColor = [UIColor whiteColor];
[self addSubview:_contentView];
_picker = [[UIDatePicker alloc]initWithFrame:CGRectMake(0, 39, [[UIScreen mainScreen] bounds].size.width, 216)];
_picker.backgroundColor = [UIColor whiteColor];
_picker.datePickerMode = UIDatePickerModeDate;
//2037-12-31 23:59:59的时间戳
_picker.maximumDate = [NSDate dateWithTimeIntervalSince1970:2145887999];
[_picker setDate:[NSDate date] animated:NO];
[_picker addTarget:self action:@selector(didPickerSelected:) forControlEvents:UIControlEventValueChanged];
[_contentView addSubview:_picker];
_picker.layer.borderWidth = 1;
_picker.layer.borderColor = SEPARATOR_LINE_COLOR.CGColor;
_leftButton = [UIButton buttonWithType:UIButtonTypeCustom];
_leftButton.tag = 1;
_leftButton.hidden = YES;
[_contentView addSubview:_leftButton];
[_leftButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(_contentView.mas_left).with.offset(16);
make.top.equalTo(_contentView.mas_top).offset(4);
}];
[_leftButton.titleLabel setFont:GMFont(16)];
[_leftButton setTitleColor:MAIN_VISUAL_COLOR forState:UIControlStateNormal];
[_leftButton addTarget:self action:@selector(confirmButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
_titleLabel = [[UILabel alloc]init];
_titleLabel.hidden = YES;
_titleLabel.font = GMFont(17);
[_titleLabel setTextColor:[UIColor blackColor]];
[_contentView addSubview:_titleLabel];
[_titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(_contentView);
make.top.equalTo(_leftButton.mas_top).with.offset(6);
}];
_rightButton = [UIButton buttonWithType:UIButtonTypeCustom];
_rightButton.tag = 2;
[_contentView addSubview:_rightButton];
[_rightButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(_contentView.mas_right).with.offset(-16);
make.top.equalTo(_contentView.mas_top).offset(4);
}];
[_rightButton setTitleColor:MAIN_VISUAL_COLOR forState:UIControlStateNormal];
[_rightButton setTitle:@"确定" forState:UIControlStateNormal];
[_rightButton.titleLabel setFont:GMFont(16)];
[_rightButton addTarget:self action:@selector(confirmButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
tapGesture.delegate = self;
[self addGestureRecognizer:tapGesture];
}
- (void)setLeftButtonTitle:(NSString *)leftButtonTitle{
_leftButtonTitle= leftButtonTitle;
_leftButton.hidden = NO;
[_leftButton setTitle:leftButtonTitle forState:UIControlStateNormal];
}
- (void)setTitleLabelTitle:(NSString *)titleLabelTitle{
_titleLabelTitle = titleLabelTitle;
_titleLabel.hidden = NO;
_titleLabel.text = titleLabelTitle;
}
- (void)didPickerSelected:(id)sender{
if (self.confirmButtonClickBolck) {
self.confirmButtonClickBolck(_picker.date);
}
}
- (void)confirmButtonClicked:(id)sender{
UIButton * button = (UIButton *)sender;
if (button.tag ==1) {
if (self.confirmButtonClickBolck) {
self.confirmButtonClickBolck(nil);
}
}else if(button.tag == 2){
if (self.confirmButtonClickBolck) {
self.confirmButtonClickBolck(_picker.date);
}
}
[self hide];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]])
{
CGPoint touch = [gestureRecognizer locationInView:self];
if(!CGRectContainsPoint(_contentView.frame, touch))
{
return YES;
}
return NO;
}
return YES;
}
- (void)tap:(UITapGestureRecognizer *)recognizer
{
if ([self.delegate respondsToSelector:@selector(openPicker)]) {
[self.delegate openPicker];
}
}
-(void)show
{
[_picker setDate:[NSDate date] animated:NO];
[UIView animateWithDuration:0.2 delay:0.0f options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.contentView.frame = CGRectMake(0, MAINSCREEN_HEIGHT - 255, MAINSCREEN_WIDTH, self.contentView.frame.size.height);
} completion:^(BOOL finished) {
}];
}
-(void)hide
{
[UIView animateWithDuration:0.2 delay:0.0f options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.contentView.frame = CGRectMake(0, MAINSCREEN_HEIGHT, MAINSCREEN_WIDTH, self.contentView.frame.size.height);
self.backgroundColor = [UIColor colorWithWhite:0 alpha:0];
} completion:^(BOOL finished) {
self.hidden = YES;
self.backgroundColor = [UIColor colorWithWhite:0 alpha:.3];
}];
}
@end
//
// GMEmptyView.h
// Gengmei
//
// Created by Terminator on 15/10/16.
// Copyright © 2015年 Wanmeichuangyi. All rights reserved.
//
#import "GMView.h"
#import "GMImageView.h"
#import "GMLabel.h"
#import "GMButton.h"
@interface GMEmptyView : GMView
@property (nonatomic, strong) GMImageView *imageView;
@property (nonatomic, strong) GMLabel *promoptLabel;
@property (nonatomic, strong) GMButton *actionButton;
@end
//
// GMEmptyView.m
// Gengmei
//
// Created by Terminator on 15/10/16.
// Copyright © 2015年 Wanmeichuangyi. All rights reserved.
//
#import "GMEmptyView.h"
#import "GMFont.h"
#import <Masonry/Masonry.h>
#import "GMConstant.h"
#import "GMTheme.h"
@implementation GMEmptyView
- (void)setup {
[super setup];
_imageView = [[GMImageView alloc] init];
[self addSubview:_imageView];
_promoptLabel = [[GMLabel alloc] init];
_promoptLabel.textAlignment = NSTextAlignmentCenter;
_promoptLabel.font = GMFont(14);
_promoptLabel.textColor = SECONDARY_TEXT_COLOR;
[self addSubview:_promoptLabel];
_actionButton = [[GMButton alloc] init];
_actionButton.titleLabel.font = GMFont(14);
_actionButton.layer.borderWidth = ONE_PIXEL;
_actionButton.layer.borderColor = SECONDARY_TEXT_COLOR.CGColor;
_actionButton.titleLabel.textColor = SECONDARY_TEXT_COLOR;
[_actionButton setTitleColor:SECONDARY_TEXT_COLOR forState:UIControlStateNormal];
[self addSubview:_actionButton];
}
- (void)updateConstraints {
[super updateConstraints];
[_imageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(122);
make.centerX.mas_equalTo(0);
}];
[_promoptLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(0);
make.top.equalTo(_imageView.mas_bottom).offset(15);
}];
[_actionButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(0);
make.top.equalTo(_promoptLabel.mas_bottom).offset(15);
make.width.mas_equalTo(120);
}];
}
@end
//
// GMHightlightLabel.h
// Gengmei
//
// Created by wangyang on 6/11/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <GMKit/GMLabel.h>
/**
* @brief 设置font,textColor,及GMHightlightLabel特有的lineHeight、hightlightColor以配置要显示的字符串属性
*/
@interface GMHighlightLabel : GMLabel
/**
* @brief 对齐方式
*/
@property (nonatomic, assign) NSTextAlignment aligment;
/**
* @brief 行高
*/
@property (nonatomic, assign) CGFloat lineHeight;
/**
* @brief 默认是 MAIN_VISUL_COLOR
*/
@property (nonatomic, strong) UIColor *hightlightColor;
/**
* @brief 默认是 MAIN_VISUL_COLOR
*/
@property (nonatomic, strong) UIColor *normalColor;
/**
* @brief 带高亮标志的原始字符串
*
*/
@property (nonatomic, copy) NSString *originalHightlightText;
/**
* @author licong, 16-12-30 18:12:58
*
* 创建一个高亮lable
*
* @param color label颜色
* @param font title的font大小
*
* @return 一个高亮lable
*
* @since 5.8
*/
+ (GMHighlightLabel *)hightLabelTextColor:(UIColor *)color andFont:(CGFloat)font;
@end
//
// GMHightlightLabel.m
// Gengmei
//
// Created by wangyang on 6/11/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMHighlightLabel.h"
#import "GMFont.h"
#import "GMConstant.h"
#import "GMTheme.h"
@implementation GMHighlightLabel
- (void)setOriginalHightlightText:(NSString *)originalHightlightText{
_originalHightlightText = originalHightlightText;
if (_originalHightlightText.length == 0) {
self.attributedText = nil;
}
if (!_hightlightColor) {
_hightlightColor = MAIN_VISUAL_COLOR;
}
if (!_normalColor) {
_normalColor = BODY_TEXT_COLOR;
}
NSMutableAttributedString *attString = [self parseString:_originalHightlightText];
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.lineSpacing = _lineHeight;
style.lineBreakMode = NSLineBreakByTruncatingTail;
style.alignment = _aligment;
// 如果有高亮,attString不为空,那只加一个NSParagraphStyleAttributeName属性
if (self.lineHeight > 0 && attString.length > 0) {
[attString addAttribute:NSParagraphStyleAttributeName value:style range:NSMakeRange(0, attString.length)];
}
// 如果没有走到正则里面,说明没有需要高亮的内容,设置普通颜色就好
else if (attString.length == 0) {
NSAttributedString *subString = [[NSAttributedString alloc] initWithString:_originalHightlightText attributes:@{NSFontAttributeName : self.font, NSForegroundColorAttributeName:_normalColor, NSParagraphStyleAttributeName:style}];
[attString appendAttributedString:subString];
}
self.attributedText = attString;
}
- (NSMutableAttributedString *)parseString:(NSString *)originalString{
NSError *error;
NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:@"([\\w\\W]*)<ems>([\\w\\W]+)<\\/ems>([\\w\\W]*)" options:0 error:&error];
if (error) {
self.attributedText = nil;
}
NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] init];
[expression enumerateMatchesInString:originalString options:0 range:NSMakeRange(0, originalString.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
// 根据NSTextCheckingResult的rangeAtIndex方法文档,result至少会有一个range,这个range是匹配它自身全部的,所以正则匹配的其它range是从1开始的
// <ems></ems>将string分隔为string1, string2, string3,分别对应前中后
NSString *string1 = [originalString substringWithRange:[result rangeAtIndex:1]];
NSAttributedString *attrString1 = [self parseString:string1];
if (attrString1.length > 0) {
[attString appendAttributedString:attrString1];
}else{
NSAttributedString *subString = [[NSAttributedString alloc] initWithString:string1 attributes:@{NSFontAttributeName : self.font, NSForegroundColorAttributeName:_normalColor}];
[attString appendAttributedString:subString];
}
NSString *string2 = [originalString substringWithRange:[result rangeAtIndex:2]];
NSAttributedString *attrString2 = [self parseString:string2];
if (attrString2.length > 0) {
[attString appendAttributedString:attrString2];
}else{
NSAttributedString *subString = [[NSAttributedString alloc] initWithString:string2 attributes:@{NSFontAttributeName : self.font, NSForegroundColorAttributeName:_hightlightColor}];
[attString appendAttributedString:subString];
}
NSString *string3 = [originalString substringWithRange:[result rangeAtIndex:3]];
NSAttributedString *attrString3 = [self parseString:string3];
if (attrString3.length > 0) {
[attString appendAttributedString:attrString3];
}else{
NSAttributedString *subString = [[NSAttributedString alloc] initWithString:string3 attributes:@{NSFontAttributeName : self.font, NSForegroundColorAttributeName:_normalColor}];
[attString appendAttributedString:subString];
}
}];
return attString;
}
+ (GMHighlightLabel *)hightLabelTextColor:(UIColor *)color andFont:(CGFloat)font {
GMHighlightLabel *label = [[GMHighlightLabel alloc] init];
label.backgroundColor = [UIColor whiteColor];
label.textColor = color;
label.font = GMFont(font);
label.hightlightColor = MAIN_VISUAL_COLOR;
return label;
}
@end
//
// GMHorizontalLayoutButton.h
// Gengmei
//
// Created by wangyang on 8/17/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMButton.h"
/**
* @author licong, 15-09-27 17:09:12
*
* Button同时存在图片和文字时候的排列样式
*
* @since 5.2.0
*/
typedef NS_ENUM(NSInteger, GMButtonImageArrangeType){
//图片居左,文字居右
GMButtonImageArrangeLeftType = 1,
//文字居左,图片居右
GMButtonImageArrangeRightType = 2,
};
__deprecated_msg("使用GMKit-Swift库中的AllLayoutButton.swift代替,会在9月份被删除掉")
@interface GMHorizontalLayoutButton : GMButton
/**
* @brief 设置button image 和 title的间距
*/
@property (nonatomic, assign) CGFloat imageAndTitleSpace;
@property (nonatomic, assign) GMButtonImageArrangeType imageArrangeType;
@end
//
// GMHorizontalLayoutButton.m
// Gengmei
//
// Created by wangyang on 8/17/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMHorizontalLayoutButton.h"
#import "GMFont.h"
#import "NSString+GM.h"
@interface GMHorizontalLayoutButton ()
@property(nonatomic, assign)CGFloat finalSpace;
@end
@implementation GMHorizontalLayoutButton
- (void)setImageArrangeType:(GMButtonImageArrangeType)imageArrangeType{
_imageArrangeType = imageArrangeType;
[self arrangeByImageArrangeType:imageArrangeType];
}
- (void)arrangeByImageArrangeType:(GMButtonImageArrangeType)imageArrangeType{
if (imageArrangeType == GMButtonImageArrangeLeftType) {
_finalSpace = self.imageAndTitleSpace/2.0;
self.titleEdgeInsets = UIEdgeInsetsMake(0, _finalSpace, 0, 0);
self.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, _finalSpace);
}else{
CGFloat titleWidth = [self.currentTitle sizeWithFont:self.titleLabel.font boundSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)].width;
_finalSpace = self.imageAndTitleSpace * 2 + titleWidth;
CGFloat imageWidth = self.currentImage.size.width * [UIScreen mainScreen].scale;
self.titleEdgeInsets = UIEdgeInsetsMake(0, -(imageWidth + self.imageAndTitleSpace * 2), 0, 0);
self.imageEdgeInsets = UIEdgeInsetsMake(0, _finalSpace, 0, 0);
}
}
- (void)updateConstraints{
[self arrangeByImageArrangeType:self.imageArrangeType];
[super updateConstraints];
}
- (CGSize)intrinsicContentSize{
CGSize size = [super intrinsicContentSize];
size.width += self.imageAndTitleSpace;
return size;
}
@end
//
// GMImageView.h
// Gengmei
//
// Created by Thierry on 2/12/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface GMImageView : UIImageView
/**
* @author licong, 16-12-31 15:12:46
*
* 将图片裁剪成圆形
*
* @since 5.8
*/
- (void)makeRoundImage;
/**
* @author licong, 16-12-31 12:12:36
*
* 获取图片,并渐现效果
*
* @param urlString 图片源路径
* @param placeHolder 默认的placeHolder
*
* @since 5.8
*/
- (void)setImageWithUrlString:(NSString *)urlString placeHolder:(NSString *)placeHolder;
@end
//
// GMImageView.m
// Gengmei
//
// Created by Thierry on 2/12/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMImageView.h"
#import <SDWebImage/UIImageView+WebCache.h>
#import "UIView+Layout.h"
#import "GMConstant.h"
#import "GMTheme.h"
@implementation GMImageView
- (void)makeRoundImage{
CGFloat cornerRadius = MIN(self.width, self.height)/2;
self.layer.cornerRadius = cornerRadius;
self.layer.masksToBounds = YES;
self.layer.borderWidth = 0.5;
self.layer.borderColor = SEPARATOR_LINE_COLOR.CGColor;
}
- (void)setImageWithUrlString:(NSString *)urlString placeHolder:(NSString *)placeHolder{
NSURL *url = [NSURL URLWithString:urlString];
if (placeHolder!=nil && ![placeHolder isEqualToString:@""]) {
[self setImage:[UIImage imageNamed:placeHolder]];
[self sd_setImageWithURL:url placeholderImage:[UIImage imageNamed:placeHolder] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (cacheType==SDImageCacheTypeNone) {
[GMImageView fadeInTransition:self];
}
}];
}else{
[self sd_setImageWithURL:url completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (cacheType==SDImageCacheTypeNone) {
[GMImageView fadeInTransition:self];
}
}];
}
}
+ (void)fadeInTransition:(UIImageView *)view{
CATransition *transtion = [CATransition animation];
transtion.duration = 0.5;
[transtion setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[transtion setType:kCATransitionFade];
[transtion setSubtype:kCATransitionFromRight];
[view.layer addAnimation:transtion forKey:@"transtionKey"];
}
@end
//
// GMInfiniteScrollView.h
//
//
// Created by wangyang on 10/12/15.
// Copyright (c) 2015年 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "GMView.h"
@class GMInfiniteScrollView;
@protocol GMInfinitelViewDelegate <NSObject>
@optional
- (void)infiniteScrollView:(GMInfiniteScrollView *)infiniteScrollView didSelectIndex:(NSInteger)index;
@end
@interface GMInfiniteScrollView : GMView <UIScrollViewDelegate>{
UIScrollView *_scrollView;
NSInteger _currentPage;
}
@property (nonatomic, assign) id<GMInfinitelViewDelegate> delegate;
/**
* @author licong, 16-12-31 18:12:50
*
* 轮播图片的填充模式
*
* @since 5.8
*/
@property (nonatomic, assign) UIViewContentMode imageContentMode;
// 存放所有需要滚动的图片URL NSString
/**
* @author licong, 16-12-31 17:12:26
*
* 需要轮播的数据源
*
* @since 5.8
*/
@property (nonatomic, strong) NSArray *imageArray;
/**
* @author licong, 16-12-31 18:12:11
*
* 轮播的时间间隔
*
* @since 5.8
*/
@property (nonatomic, assign) NSTimeInterval timeInterval;
/**
* @author licong, 16-12-31 18:12:42
*
* pageControl
*
* @since 5.8
*/
@property (nonatomic, strong) UIPageControl *pageControl;
/**
* @author licong, 16-12-31 18:12:09
*
* 是否允许手动滚动
*
* @since 5.8
*/
@property (nonatomic, assign) BOOL enableRolling;
/**
* @author licong, 16-12-31 18:12:34
*
* 开始轮播
*
* @since 5.8
*/
- (void)startRolling;
/**
* @author licong, 16-12-31 18:12:48
*
* 停止轮播
*
* @since 5.8
*/
- (void)stopRolling;
@end
//
// GMInfinitelView.m
//
//
// Created by wangyang on 10/12/15.
// Copyright (c) 2015年 Wanmeichuangyi. All rights reserved.
//
#import "GMInfiniteScrollView.h"
#import "GMImageView.h"
#define IMAGE_TAG 1000
@implementation GMInfiniteScrollView
- (void)setup{
[super setup];
// 显示的是图片数组里的第一张图片
// 和数组是+1关系
_currentPage = 0;
_scrollView = [[UIScrollView alloc] initWithFrame:self.bounds];
_scrollView.backgroundColor = [UIColor whiteColor];
_scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_scrollView.showsHorizontalScrollIndicator = NO;
_scrollView.showsVerticalScrollIndicator = NO;
_scrollView.pagingEnabled = YES;
_scrollView.delegate = self;
_scrollView.scrollsToTop = NO;
[self addSubview:_scrollView];
self.pageControl = [UIPageControl new];
self.pageControl.currentPage = 0;
self.pageControl.hidesForSinglePage = YES;
[self addSubview:self.pageControl];
self.imageContentMode = UIViewContentModeScaleAspectFill;
// 在应用后台时关闭播放,前台时开启
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(stopRolling) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(startRolling) name:UIApplicationWillEnterForegroundNotification object:nil];
}
- (void)layoutSubviews{
[super layoutSubviews];
self.pageControl.frame = CGRectMake(0, self.frame.size.height-15, self.frame.size.width, 15);
self.pageControl.center = CGPointMake(self.frame.size.width / 2, self.pageControl.center.y);
}
- (void)dealloc
{
self.delegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(rollingScrollAction) object:nil];
}
#pragma mark - Setter
- (void)setImageArray:(NSArray *)imageArray{
_imageArray = imageArray;
if(_imageArray.count == 0) {
return;
}
// 如果只有一个,不允许滑动
_scrollView.scrollEnabled = _imageArray.count == 1 ? NO : YES;
_scrollView.contentSize = CGSizeMake(_scrollView.frame.size.width * 3,
_scrollView.frame.size.height);
for (NSInteger i = 0; i < 3; i++){
CGRect frame = CGRectOffset(_scrollView.bounds, _scrollView.frame.size.width * i, 0);
GMImageView *imageView = [[GMImageView alloc] initWithFrame:frame];
imageView.userInteractionEnabled = YES;
imageView.clipsToBounds = YES;
imageView.contentMode = self.imageContentMode;
imageView.tag = i + IMAGE_TAG;
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
[imageView addGestureRecognizer:singleTap];
[_scrollView addSubview:imageView];
}
self.pageControl.numberOfPages = self.self.imageArray.count;
_currentPage = 1;
[self refreshScrollView];
}
#pragma mark - Custom Method
- (void)refreshScrollView{
NSArray *imageArray = [self getDisplayImagesWithPageIndex:_currentPage];
for (NSInteger i = 0; i < 3; i++){
GMImageView *imageView = (GMImageView *)[_scrollView viewWithTag:i + IMAGE_TAG];
NSString *url = imageArray[i];
[imageView setImageWithUrlString:url placeHolder:nil];
}
// 水平滚动
_scrollView.contentOffset = CGPointMake(_scrollView.frame.size.width, 0);
self.pageControl.currentPage = _currentPage-1;
}
- (NSArray *)getDisplayImagesWithPageIndex:(NSInteger)page{
NSInteger pre = [self getPageIndex:_currentPage-1];
NSInteger last = [self getPageIndex:_currentPage+1];
NSMutableArray *images = [NSMutableArray arrayWithCapacity:0];
[images addObject:[self.imageArray objectAtIndex:pre-1]];
[images addObject:[self.imageArray objectAtIndex:_currentPage-1]];
[images addObject:[self.imageArray objectAtIndex:last-1]];
return images;
}
- (NSInteger)getPageIndex:(NSInteger)index{
// value=1为第一张,value = 0为前面一张
if (index == 0)
{
index = self.imageArray.count;
}
if (index == self.imageArray.count + 1)
{
index = 1;
}
return index;
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSInteger x = scrollView.contentOffset.x;
//取消已加入的延迟线程
if (self.enableRolling)
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(rollingScrollAction) object:nil];
}
// 往下翻一张
if (x >= 2 * _scrollView.frame.size.width)
{
_currentPage = [self getPageIndex:_currentPage+1];
[self refreshScrollView];
}
if (x <= 0)
{
_currentPage = [self getPageIndex:_currentPage-1];
[self refreshScrollView];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
_scrollView.contentOffset = CGPointMake(_scrollView.frame.size.width, 0);
if (self.enableRolling)
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(rollingScrollAction) object:nil];
[self performSelector:@selector(rollingScrollAction) withObject:nil afterDelay:self.timeInterval inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
}
}
#pragma mark - Action
- (void)startRolling{
if (self.imageArray.count == 0 || self.imageArray.count == 1){
return;
}
[self stopRolling];
self.enableRolling = YES;
[self performSelector:@selector(rollingScrollAction) withObject:nil afterDelay:self.timeInterval];
}
- (void)stopRolling{
self.enableRolling = NO;
//取消已加入的延迟线程
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(rollingScrollAction) object:nil];
}
- (void)rollingScrollAction{
// 滑动动画由我们自己完成
[UIView animateWithDuration:0.25 animations:^{
_scrollView.contentOffset = CGPointMake(1.99*_scrollView.frame.size.width, 0);
} completion:^(BOOL finished) {
if (finished)
{
_currentPage = [self getPageIndex:_currentPage+1];
[self refreshScrollView];
if (self.enableRolling)
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(rollingScrollAction) object:nil];
[self performSelector:@selector(rollingScrollAction) withObject:nil afterDelay:self.timeInterval inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
}
}
}];
}
- (void)handleTap:(UITapGestureRecognizer *)tap{
if ([self.delegate respondsToSelector:@selector(infiniteScrollView:didSelectIndex:)]){
[self.delegate infiniteScrollView:self didSelectIndex:_currentPage-1];
}
}
@end
//
// GMLabel.h
// Gengmei
//
// Created by Thierry on 12/26/14.
// Copyright (c) 2014 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface GMLabel : UILabel
typedef NS_ENUM(NSInteger, GMLabelVerticalAlignment){
GMLabelVerticalAlignmentTop = 0, // 垂直方向顶部对齐
GMLabelVerticalAlignmentMiddle, // 垂直方向中间对齐
GMLabelVerticalAlignmentBottom, // 垂直方向底部对齐
};
/**
* @brief 垂直方向对齐。默认是 GMLabelVerticalAlignmentMiddle。因为有这个属性,所以需要重写 drawTextInRect
*/
@property (nonatomic,assign) GMLabelVerticalAlignment verticalAlignment;
/**
* @brief 文字与label边框的padding。因为有这个方法,所以重写了 intrinsicContentSize
*/
@property (nonatomic, assign) UIEdgeInsets paddingEdge;
/**
* @author licong, 16-12-30 17:12:50
*
* 创建一个UILabel
*
* @param TextColor 色值
* @param fontSize font大小
*
* @return 一个新的UILabel
*
* @since 5.8
*/
+ (GMLabel *)labelWithTextColor:(UIColor *)color fontSize:(CGFloat)fontSize;
/**
* @brief 创建一个UILabel
*
* @param textAlignment 对齐方式
* @param backgroundColor 背景颜色
* @param textColor 文字颜色
* @param fontSize 字号
*/
+ (GMLabel *)labelWithTextAlignment:(NSTextAlignment)textAlignment backgroundColor:(UIColor*)backgroundColor textColor:(UIColor*)textColor fontSize:(CGFloat )fontSize;
/**
* @brief 设置label属性
*
* @param textAlignment 对齐方式
* @param backgroundColor 背景颜色
* @param textColor 文字颜色
* @param fontSize 字号
*/
- (void)setTextAlignment:(NSTextAlignment)textAlignment backgroundColor:(UIColor*)backgroundColor textColor:(UIColor*)textColor fontSize:(CGFloat )fontSize;
@end
//
// GMUILabel.m
// Gengmei
//
// Created by Thierry on 12/26/14.
// Copyright (c) 2014 Wanmeichuangyi. All rights reserved.
//
#import <GMKit/GMLabel.h>
#import "GMFont.h"
#import "GMConstant.h"
@implementation GMLabel
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)awakeFromNib{
[super awakeFromNib];
[self setup];
}
- (void)setup{
_verticalAlignment = GMLabelVerticalAlignmentMiddle;
}
+ (GMLabel *)labelWithTextColor:(UIColor *)color fontSize:(CGFloat)fontSize{
GMLabel *lable = [GMLabel new];
lable.textColor = color;
lable.font = GMFont(fontSize);
return lable;
}
+ (GMLabel *)labelWithTextAlignment:(NSTextAlignment)textAlignment backgroundColor:(UIColor*)backgroundColor textColor:(UIColor*)textColor fontSize:(CGFloat )fontSize{
GMLabel *lable = [GMLabel new];
lable.textAlignment = textAlignment;
lable.textColor = textColor;
lable.backgroundColor = backgroundColor;
lable.font = GMFont(fontSize);
return lable;
}
- (void)setTextAlignment:(NSTextAlignment)textAlignment backgroundColor:(UIColor*)backgroundColor textColor:(UIColor*)textColor fontSize:(CGFloat )fontSize{
self.textAlignment = textAlignment;
self.textColor = textColor;
self.backgroundColor = backgroundColor;
self.font = GMFont(fontSize);
}
- (void)setVerticalAlignment:(GMLabelVerticalAlignment)verticalAlignment {
_verticalAlignment = verticalAlignment;
[self setNeedsDisplay];
}
/**
* @author wangyang, 16-01-08 10:01:26
*
* @brief 这个方法在每次设置新的text值时会调用一次,并且只在最后调用一次。也就是说此时已经有了正确的size
* @note 不经重写的 UILabel
先调用 intrinsicContentSize
再调用 textRectForBounds:limitedToNumberOfLines
最后调用drawTextInRect
* @param requestedRect 等于self.bounds
* @since 5.9.0
*/
-(void)drawTextInRect:(CGRect)requestedRect {
// 先去除paddingEdge,剩下的rect就是用来放 string 的 rect。但注意:未必是正好能容纳下string的大小的rect
CGRect contentRect = UIEdgeInsetsInsetRect(requestedRect, self.paddingEdge);
// 在contentRect内,string所需要的text rect
CGRect textRect = [self textRectForBounds:contentRect limitedToNumberOfLines:self.numberOfLines];
// 将坐标信息清0,由我们自己控制
textRect.origin.x = 0;
textRect.origin.y = 0;
switch (self.verticalAlignment) {
case GMLabelVerticalAlignmentTop: {
textRect.origin.y = 0;
break;
}
case GMLabelVerticalAlignmentBottom: {
textRect.origin.y = requestedRect.size.height - textRect.size.height;
break;
}
case GMLabelVerticalAlignmentMiddle:
default: {
textRect.origin.y = (requestedRect.size.height - textRect.size.height)/2;
break;
}
}
switch (self.textAlignment) {
case NSTextAlignmentRight: {
textRect.origin.x = requestedRect.size.width - textRect.size.width;
break;
}
case NSTextAlignmentCenter: {
textRect.origin.x = (requestedRect.size.width - textRect.size.width)/2;
break;
}
case NSTextAlignmentNatural:
case NSTextAlignmentLeft:
default: {
textRect.origin.x = 0;
break;
}
}
[super drawTextInRect:textRect];
}
// 因为有paddingEdge,需要重写 intrinsicContentSize 方法。如果在约束里指定了size,该方法会失效
- (CGSize)intrinsicContentSize {
CGSize size = [super intrinsicContentSize];
CGSize newSize = CGSizeMake(ceilf(size.width + self.paddingEdge.left + self.paddingEdge.right), ceilf(size.height + self.paddingEdge.top + self.paddingEdge.bottom));
return newSize;
}
@end
//
// GMLoadingView.h
// loadingAnimation
//
// Created by wangyang on 12/23/14.
// Copyright (c) 2014 WY. All rights reserved.
//
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSUInteger, GMLoadingViewType) {
GMLoadingViewTypeGray,
GMLoadingViewTypeGreenLarge,
GMLoadingViewTypeGreenSmall
};
@interface GMLoadingView : UIView
+ (instancetype)loadingViewWithType:(GMLoadingViewType)type;
- (void)begingAnimation;
- (void)endAnimation;
@end
//
// GMLoadingView.m
// loadingAnimation
//
// Created by wangyang on 12/23/14.
// Copyright (c) 2014 WY. All rights reserved.
//
#import "GMLoadingView.h"
@interface GMLoadingView ()
@property (nonatomic, strong) UIImageView *foregroundImageView;
@end
@implementation GMLoadingView
+ (instancetype)loadingViewWithType:(GMLoadingViewType)type
{
UIImage *backImage;
UIImage *foreImage;
if (type == GMLoadingViewTypeGreenLarge) {
backImage = [UIImage imageNamed:@"loaing2_diceng"];
foreImage = [UIImage imageNamed:@"loaing2"];
}else if (type == GMLoadingViewTypeGray){
backImage = [UIImage imageNamed:@"loaing1_diceng"];
foreImage = [UIImage imageNamed:@"loaing1"];
}else if (type == GMLoadingViewTypeGreenSmall){
backImage = nil;
foreImage = [UIImage imageNamed:@"GreenLoaingSmall"];
}else{
backImage = nil;
foreImage = [UIImage imageNamed:@"GreenLoaingSmall"];
}
// 背景
UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:backImage];
// 前景
UIImageView *foregroundImageView = [[UIImageView alloc] initWithImage:foreImage];
GMLoadingView *view = [[GMLoadingView alloc] initWithFrame:foregroundImageView.bounds];
view.backgroundColor = [UIColor clearColor];
[view addSubview:backgroundImageView];
[view addSubview:foregroundImageView];
view.foregroundImageView = foregroundImageView;
return view;
}
- (void)begingAnimation
{
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
animation.fromValue = @0.0f;
animation.toValue = @(2*M_PI);
animation.duration = .8f; // this might be too fast
animation.repeatCount = HUGE_VALF; // HUGE_VALF is defined in math.h so import it
[_foregroundImageView.layer addAnimation:animation forKey:@"rotation"];
}
- (void)endAnimation
{
[_foregroundImageView.layer removeAllAnimations];
}
@end
//
// GMMarkStarView.h
// ZhengXing
//
// Created by Tulipa on 14-8-21.
// Copyright (c) 2014年 Wanmei Creative. All rights reserved.
//
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, GMStarUserType) {
GMStarUserTypeEdit = 0,//可编辑,显示大星星
GMStarUserTypeShow = 1,//纯展示,显示小星星
GMStarUserTypesSmallShow
};
typedef NS_ENUM(NSInteger, GMStarColor) {
GMStarColorYellow = 0,
GMStarColorRed = 1,
GMStarColorSmallYellow
};
@interface GMMarkStarView : UIView
/* 可控的星星间距 */
@property (nonatomic, assign) NSInteger space;
@property (nonatomic, assign) GMStarColor starColor;
@property (nonatomic, assign) NSInteger mark;
/* 从5.7开始支持半颗星 */
@property (nonatomic, assign) float number;
/* 如果用作可以编辑个数的星星,就用大星星,纯展示的就用小星星 */
@property (nonatomic , assign) GMStarUserType type;
@property (nonatomic, copy) void(^markChange)(NSInteger mark);
- (id)initWithUseType:(GMStarUserType)type;
@end
//
// GMMarkStarView.m
// ZhengXing
//
// Created by Tulipa on 14-8-21.
// Copyright (c) 2014年 Wanmei Creative. All rights reserved.
//
#import "GMMarkStarView.h"
#import "UIView+Layout.h"
@interface GMMarkStarView ()
@end
@implementation GMMarkStarView{
NSMutableArray *buttons;
}
- (id)initWithUseType:(GMStarUserType)type
{
_type = type;
self = [self init];
return self;
}
- (id)init{
self = [super init];
if (self){
buttons = [[NSMutableArray alloc] initWithCapacity:5];
for (int i = 1; i < 6; i ++){
UIButton *button = [self createButton];
[self addSubview:button];
[buttons addObject:button];
button.tag = i;
}
}
return self;
}
- (UIButton *)createButton{
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
UIImage *normalImage;
UIImage *editImage;
UIImage *hightlighImage;
if (_type == GMStarUserTypeEdit) {//用大星星
normalImage = [UIImage imageNamed:@"star_normal"];
editImage = [UIImage imageNamed:@"star_light_new"];
hightlighImage = [UIImage imageNamed:@"star_light_new"];
}else if (_type == GMStarUserTypeShow){//用小星星
normalImage = [UIImage imageNamed:@"star_normal_small_red"];
editImage = [UIImage imageNamed:@"star_light_new_small_red"];
hightlighImage = [UIImage imageNamed:@"star_half_red"];
}else if (_type == GMStarUserTypesSmallShow) {
normalImage = [UIImage imageNamed:@"show_small_gray_star"];
editImage = [UIImage imageNamed:@"show_small_star"];
hightlighImage = [UIImage imageNamed:@"show_small_half_star"];
}
[button setImage:editImage forState:UIControlStateSelected];
[button setImage:hightlighImage forState:UIControlStateHighlighted];
[button setImage:normalImage forState:UIControlStateNormal];
button.size = CGSizeMake(18, 18);
if (_type == GMStarUserTypesSmallShow) {
button.size = CGSizeMake(12, 12);
}
return button;
}
- (void)buttonClicked:(UIButton *)button{
NSInteger tag = button.tag;
self.mark = tag;
if (self.markChange){
self.markChange(tag);
}
}
- (void)setMark:(NSInteger)tag{
_mark = tag;
[buttons enumerateObjectsUsingBlock:^(UIButton *btn, NSUInteger idx, BOOL *stop) {
[btn setSelected:btn.tag <= tag];
[btn setHighlighted:NO];
}];
}
/* 通过向上、向下取整,获取到哪个位置应该显示半颗星 */
- (void)setNumber:(float)number
{
_number = number;
float big = ceilf(number);
float small = floorf(number);
[buttons enumerateObjectsUsingBlock:^(UIButton *btn, NSUInteger idx, BOOL *stop) {
if (big != small) {
if (idx+1<=small) {
[btn setSelected:YES];
[btn setHighlighted:NO];
}else if(idx+1 == big){
[btn setSelected:NO];
[btn setHighlighted:YES];
}else{
[btn setSelected:NO];
[btn setHighlighted:NO];
}
}else{
[btn setSelected:btn.tag <= number];
[btn setHighlighted:NO];
}
}];
}
- (void)layoutSubviews{
[super layoutSubviews];
UIView *view = buttons[0];
CGFloat space = self.space==0?16:self.space;
CGFloat insert = space + view.width;
__weak __typeof(self)weakSelf = self;
[buttons enumerateObjectsUsingBlock:^(UIButton *btn, NSUInteger idx, BOOL *stop) {
[btn setLeft:(insert * idx)+ 10];
[btn setCenterY:weakSelf.height / 2.0f];
}];
}
@end
//
// GMPlaceholderTextContainer.h
// Gengmei
//
// Created by wangyang on 16/1/5.
// Copyright © 2016年 Wanmeichuangyi. All rights reserved.
//
#import "GMView.h"
#import "GMLabel.h"
/**
* @author wangyang, 16-01-05 14:01:02
*
* @brief 提供一个有placeholderLable的textView。因为直接在UITextView上添加subview可能会导致crash(这应该是SDK的bug),在高版本sdk不会crash,所以只能提供这样一个view,分别包含UITextVie和Label
* @since 5.8.0
*/
@interface GMPlaceholderTextContainer : GMView
@property (nonatomic, strong) GMLabel *placeholderLabel;
@property (nonatomic, strong) UITextView *textView;
@property (nonatomic, assign) CGPoint placeholderOrigin;
// 等于 textView.text
@property (nonatomic, copy) NSString *text;
// 等于 placeholderLabel.text
@property (nonatomic, copy) NSString *placeholder;
// 等于 textView.resignFirstResponder
- (BOOL)resignFirstResponder;
// 等于 textView.becomeFirstResponder
- (BOOL)becomeFirstResponder;
@end
//
// GMPlaceholderTextContainer.m
// Gengmei
//
// Created by wangyang on 16/1/5.
// Copyright © 2016年 Wanmeichuangyi. All rights reserved.
//
#import "GMPlaceholderTextContainer.h"
#import <Masonry/Masonry.h>
#import "NSString+GM.h"
#import "GMFont.h"
#import "GMTheme.h"
#import "GMConstant.h"
@implementation GMPlaceholderTextContainer
- (void)setup
{
[super setup];
_placeholderOrigin = CGPointMake(6, 7);
_textView = [UITextView new];
_textView.font = GMFont(15);
[self addSubview:_textView];
_placeholderLabel = [GMLabel labelWithTextColor: DISABLE_COLOR fontSize:15];
_placeholderLabel.backgroundColor = [UIColor clearColor];
[self addSubview:_placeholderLabel];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textViewDidChange) name:UITextViewTextDidChangeNotification object:nil];
}
- (void)updateConstraints{
[_textView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.insets(UIEdgeInsetsZero);
}];
[_placeholderLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_textView.mas_top).offset(_placeholderOrigin.y);
make.left.equalTo(_textView.mas_left).offset(_placeholderOrigin.x);
}];
[super updateConstraints];
}
- (void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)textViewDidChange{
_placeholderLabel.hidden = [_textView.text isNonEmpty];
}
#pragma mark - 重写
- (BOOL)becomeFirstResponder{
[_textView becomeFirstResponder];
return YES;
}
- (BOOL)resignFirstResponder{
[_textView resignFirstResponder];
return [super resignFirstResponder];
}
#pragma mark - Setter、Getter
- (NSString *)text{
return _textView.text;
}
- (void)setText:(NSString *)text {
_textView.text = text;
[self textViewDidChange];
}
- (NSString *)placeholder {
return _placeholderLabel.text;
}
- (void)setPlaceholder:(NSString *)placeholder {
_placeholderLabel.text = placeholder;
}
@end
\ No newline at end of file
//
// PreviewCell.h
// Gengmei
//
// Created by Sean Lee on 4/7/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <SDWebImage/UIButton+WebCache.h>
#import "GMButton.h"
#import "GMLabel.h"
@class GMPreviewCell;
@protocol GMPreviewCellDelegate<NSObject>
@optional
- (void)prepareRetryUploadPreviewCell:(GMPreviewCell *)cell;
- (void)prepareAddPreviewCell:(GMPreviewCell *)cell;
- (void)prepareDeletePreviewCell:(GMPreviewCell *)cell;
- (void)didSelectedPreviewCell:(GMPreviewCell *)cell;
@end
@interface GMPreviewCell : UIButton
@property (nonatomic,assign) NSInteger index;
@property (nonatomic,strong) GMButton *deleteButton;
@property (nonatomic,strong) UIImage *image;
@property (nonatomic,strong) GMButton *retryButton;
@property (nonatomic,strong) GMLabel *dayLabel;
@property (nonatomic,strong) UIActivityIndicatorView *indicatorView;
@property (nonatomic, assign) id<GMPreviewCellDelegate>delegate;
- (id)initWithFrame:(CGRect)frame image:(UIImage *)image;
- (void)setButtonImageWithUrl:(NSString *)url forState:(UIControlState)state completed:(SDWebImageCompletionBlock)completedBlock;
@end
//
// PreviewCell.m
// Gengmei
//
// Created by Sean Lee on 4/7/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMPreviewCell.h"
#import <Masonry/Masonry.h>
@implementation GMPreviewCell
- (id)initWithFrame:(CGRect)frame image:(UIImage *)image
{
self = [super initWithFrame:frame];
if (self) {
_image = image;
self.retryButton = [GMButton buttonWithType:UIButtonTypeCustom];
[self addSubview:self.retryButton];
[self.retryButton setImage:[UIImage imageNamed:@"Refresh"] forState:UIControlStateNormal];
[self.retryButton addTarget:self action:@selector(retryAction:) forControlEvents:UIControlEventTouchUpInside];
[self.retryButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self);
}];
self.indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.indicatorView.color = [UIColor blackColor];
[self addSubview:self.indicatorView];
[self.indicatorView mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self);
}];
self.deleteButton = [GMButton buttonWithType:UIButtonTypeCustom];
[self addSubview:self.deleteButton];
[self.deleteButton setImage:[UIImage imageNamed:@"image_delete"] forState:UIControlStateNormal];
[self.deleteButton addTarget:self action:@selector(deleteAction:) forControlEvents:UIControlEventTouchUpInside];
[self.deleteButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.mas_top).offset(3);
make.right.equalTo(self.mas_right).offset(-3);
}];
self.deleteButton.hidden = YES;
self.retryButton.hidden = YES;
self.indicatorView.hidden = YES;
[self setBackgroundImage:_image forState:UIControlStateNormal];
[self addTarget:self action:@selector(didSelectedAction:) forControlEvents:UIControlEventTouchUpInside];
_dayLabel = [GMLabel labelWithTextAlignment:NSTextAlignmentLeft backgroundColor:[UIColor clearColor] textColor:[UIColor whiteColor] fontSize:10];
_dayLabel.hidden = YES;
[self addSubview:_dayLabel];
[_dayLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(5);
make.bottom.mas_equalTo(-5);
}];
}
return self;
}
- (void)setButtonImageWithUrl:(NSString *)url forState:(UIControlState)state completed:(SDWebImageCompletionBlock)completedBlock{
[self sd_setBackgroundImageWithURL:[NSURL URLWithString:url] forState:state completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
completedBlock(image,error,cacheType,imageURL);
}];
}
/*** @brief 重新上传图片*/
- (void)retryAction:(UIButton *)button{
if ([self.delegate respondsToSelector:@selector(prepareRetryUploadPreviewCell:)]) {
[self.delegate prepareRetryUploadPreviewCell:self];
}
}
/*** @brief 删除图片*/
- (void)deleteAction:(UIButton *)button{
if ([self.delegate respondsToSelector:@selector(prepareDeletePreviewCell:)]) {
[self.delegate prepareDeletePreviewCell:self];
}
}
/*** @brief 添加/大图浏览图片*/
- (void)didSelectedAction:(UIButton *)sender
{
if (sender.tag == 1000) {
//添加图片
if ([self.delegate respondsToSelector:@selector(prepareAddPreviewCell:)]) {
[self.delegate prepareAddPreviewCell:self];
}
}else{
//大图模式
if ([self.delegate respondsToSelector:@selector(didSelectedPreviewCell:)]) {
[self.delegate didSelectedPreviewCell:self];
}
}
}
@end
//
// PreListView.h
// Gengmei
//
// Created by Sean Lee on 4/7/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "GMPreviewCell.h"
#import "GMView.h"
#define PREVIRE_CELL_HEIGHT PREVIRE_CELL_WIDTH
#define PREVIRE_CELL_WIDTH (MAINSCREEN_WIDTH - 30 - PREVIRE_CELL_SPACE * 3)/4 // 每一个unitCell的默认宽度
#define PREVIRE_CELL_SPACE 5 // unitCell之间的间距
#define DURATION 0.5 // 动画执行时间
#define DEFAULT_VISIBLE_COUNT 4 //默认显示的unitCell的个数
@protocol GMPreviewListViewDelegate <NSObject>
@optional
- (void)addPreviewCell;
- (void)retryUploadPreviewCell:(GMPreviewCell *)cell;
- (void)deletePreviewCell:(NSInteger )index;
- (void)fullScreenImage:(GMPreviewCell *)cell;
@end
@interface GMPreviewListView : GMView
@property (nonatomic, strong) NSMutableArray *previewList;
@property (nonatomic,assign)id<GMPreviewListViewDelegate>delegate;
/**
* @brief 添加一个预览图,并返回生成好的cell的位置
*
* @param image cell里装载的图片
*/
- (GMPreviewCell * ) addImage:(UIImage *)image;
/*** @brief 开始上传,显示loading */
- (void)showLoadingAtIndex:(NSInteger)index;
/*** @brief 上传成功,隐藏loading */
- (void)hideLoadingAtIndex:(NSInteger)index;
/*** @brief 重新上传loading */
- (void)retryLoadingAtIndex:(NSInteger)index;
@end
//
// PreListView.m
// Gengmei
//
// Created by Sean Lee on 4/7/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMPreviewListView.h"
#import <Masonry/Masonry.h>
#import "GMConstant.h"
@interface GMPreviewListView ()<GMPreviewCellDelegate, UIScrollViewDelegate>
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) GMPreviewCell *defaultCell;
@property (nonatomic, assign) BOOL hasDelete;
@property (nonatomic, assign) BOOL frontMove;
@property (nonatomic, assign) int moveCount;
@end
@implementation GMPreviewListView
- (void)setup{
[super setup];
self.backgroundColor = [UIColor whiteColor];
_previewList = [[NSMutableArray alloc] init];
_hasDelete = NO;
_moveCount = 0;
_scrollView = [[UIScrollView alloc] init];
_scrollView.delegate = self;
_scrollView.backgroundColor = [UIColor whiteColor];
_scrollView.scrollEnabled = YES;
_scrollView.directionalLockEnabled = YES;
_scrollView.showsVerticalScrollIndicator = NO;
_scrollView.showsHorizontalScrollIndicator = YES;
_scrollView.alwaysBounceHorizontal = YES;
_scrollView.contentSize = [self contentSizeForUIScrollView:0];
[self addSubview:_scrollView];
_defaultCell = [[GMPreviewCell alloc] initWithFrame:CGRectMake(0, 0, PREVIRE_CELL_WIDTH, PREVIRE_CELL_WIDTH) image:[UIImage imageNamed:@"camera"]];
_defaultCell.tag = 1000;
_defaultCell.delegate = self;
[_scrollView addSubview:_defaultCell];
[self scrollViewAbleScroll];
}
- (void)updateConstraints{
[_scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(00);
make.top.mas_equalTo(00);
make.right.mas_equalTo(0);
make.bottom.mas_equalTo(0);
}];
[super updateConstraints];
}
/*
* @brief 根据index获取UIScrollView的ContentSize
*/
- (CGSize)contentSizeForUIScrollView:(NSInteger)index
{
float width = (PREVIRE_CELL_SPACE + PREVIRE_CELL_WIDTH) * index;
if (width < _scrollView.bounds.size.width)
width = _scrollView.bounds.size.width;
return CGSizeMake(width, PREVIRE_CELL_HEIGHT);
}
- (void)scrollViewAbleScroll
{
_scrollView.contentSize = [self contentSizeForUIScrollView:(_previewList.count + 1)];
[_scrollView scrollRectToVisible:CGRectMake(_scrollView.contentSize.width - PREVIRE_CELL_WIDTH, 0, PREVIRE_CELL_WIDTH, self.frame.size.height) animated:YES];
}
- (GMPreviewCell * ) addImage:(UIImage *)image{
GMPreviewCell * newPreviewCell = nil;
CGFloat x = (_previewList.count) * (PREVIRE_CELL_SPACE + PREVIRE_CELL_WIDTH);
newPreviewCell = [[GMPreviewCell alloc] initWithFrame:CGRectMake(x, 0, PREVIRE_CELL_WIDTH, PREVIRE_CELL_WIDTH) image:image];
newPreviewCell.alpha = 0.1;
newPreviewCell.delegate = self;
[_previewList addObject:newPreviewCell];
[_scrollView addSubview:newPreviewCell];
[self scrollViewAbleScroll];
_defaultCell.alpha = 0.5;
[UIView animateWithDuration:DURATION animations:^(){
CGRect rect = _defaultCell.frame;
rect.origin.x += (PREVIRE_CELL_SPACE + PREVIRE_CELL_WIDTH);
_defaultCell.frame = rect;
_defaultCell.alpha = 1.0;
newPreviewCell.alpha = 0.8;
} completion:^(BOOL finished){
newPreviewCell.alpha = 1.0;
}];
return newPreviewCell;
}
#pragma -- mark PreviewCellDelegate
- (void)prepareAddPreviewCell:(GMPreviewCell *)cell{
if ([self.delegate respondsToSelector:@selector(addPreviewCell)]) {
[self.delegate addPreviewCell];
}
}
- (void)prepareDeletePreviewCell:(GMPreviewCell *)cell{
NSInteger index = [_previewList indexOfObject:cell];
//设置相关Cell的透明度
cell.alpha = 0.8;
// 判断其余cell的移动方向(从前向后移动/从后向前移动)
_frontMove = NO;
if (_previewList.count - 1 > DEFAULT_VISIBLE_COUNT
&& (_previewList.count - index - 1) <= DEFAULT_VISIBLE_COUNT) {
_frontMove = YES;
}
if (index == _previewList.count - 1 && !_frontMove)
_defaultCell.alpha = 0.5;
[UIView animateWithDuration:DURATION animations:^(){
//其余defautlCell依次移动
if (_frontMove)
{
//前面的向后移动
for (int i = 0; i < index; i++) {
GMPreviewCell *cell = _previewList[i];
CGRect rect = cell.frame;
rect.origin.x += (PREVIRE_CELL_SPACE + PREVIRE_CELL_WIDTH);
cell.frame = rect;
}
_moveCount++;
}
else
{
//后面的向前移动
for (NSInteger i = index + 1; i < _previewList.count; i++) {
GMPreviewCell *cell = _previewList[i];
CGRect rect = cell.frame;
rect.origin.x -= (PREVIRE_CELL_SPACE + PREVIRE_CELL_WIDTH);
cell.frame = rect;
}
//defautlCell向前移动
CGRect rect = _defaultCell.frame;
rect.origin.x -= (PREVIRE_CELL_SPACE + PREVIRE_CELL_WIDTH);
_defaultCell.frame = rect;
_defaultCell.alpha = 1.0;
}
cell.alpha = 0.0;
} completion:^(BOOL finished){
if ([self.delegate respondsToSelector:@selector(deletePreviewCell:)]) {
[self.delegate deletePreviewCell:index];
}
//删除被点击的cell
[cell removeFromSuperview];
[_previewList removeObject:cell];
if (_previewList.count <= DEFAULT_VISIBLE_COUNT){
[_scrollView setContentOffset:CGPointMake(0, 0) animated:NO];
_scrollView.contentSize = [self contentSizeForUIScrollView:0];
}
if (_frontMove) {
[self isNeedResetFrame];
}
}];
_hasDelete = YES;
}
/**大图模式*/
- (void)didSelectedPreviewCell:(GMPreviewCell *)cell{
if ([self.delegate respondsToSelector:@selector(fullScreenImage:)]) {
[self.delegate fullScreenImage:cell];
}
}
- (void)prepareRetryUploadPreviewCell:(GMPreviewCell *)cell{
if ([self.delegate respondsToSelector:@selector(retryUploadPreviewCell:)]) {
[self.delegate retryUploadPreviewCell:cell];
}
}
/**
* @brief 当删除操作是前面的unitCell向后移动时
* 做滚动操作或者添加操作需要重新设置每个unitCell的frame
*/
- (void)isNeedResetFrame
{
if (_frontMove && _moveCount > 0) {
for (int i = 0; i < _previewList.count; i++) {
GMPreviewCell *cell = [_previewList objectAtIndex:(NSUInteger) i];
CGRect rect = cell.frame;
rect.origin.x -= (PREVIRE_CELL_SPACE + PREVIRE_CELL_WIDTH) * _moveCount;
cell.frame = rect;
}
CGRect rect = _defaultCell.frame;
rect.origin.x -= (PREVIRE_CELL_SPACE + PREVIRE_CELL_WIDTH) * _moveCount;
_defaultCell.frame = rect;
_frontMove = NO;
_moveCount = 0;
}
if (_hasDelete)
{
_scrollView.contentSize = [self contentSizeForUIScrollView:(_previewList.count + 1)];
_hasDelete = !_hasDelete;
}
}
/*** @brief 开始上传,显示loading */
- (void)showLoadingAtIndex:(NSInteger)index{
GMPreviewCell * cell = _previewList[index];
[cell.retryButton setHidden:YES];
[cell.deleteButton setHidden:YES];
[cell.indicatorView setHidden:NO];
[cell.indicatorView startAnimating];
}
/*** @brief 上传成功,隐藏loading */
- (void)hideLoadingAtIndex:(NSInteger)index{
GMPreviewCell * cell = _previewList[index];
[cell.retryButton setHidden:YES];
[cell.deleteButton setHidden:NO];
[cell.indicatorView setHidden:YES];
[cell.indicatorView stopAnimating];
}
/*** @brief 重新上传loading */
- (void)retryLoadingAtIndex:(NSInteger)index{
if (index > _previewList.count - 1){
return;
}
GMPreviewCell * cell = _previewList[index];
[cell.retryButton setHidden:NO];
[cell.deleteButton setHidden:NO];
[cell.indicatorView setHidden:YES];
[cell.indicatorView stopAnimating];
}
@end
//
// GMRateView.h
// Gengmei
//
// Created by Thierry on 1/22/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface GMRateView : UIView
@property (nonatomic,assign) NSInteger rate;
- (void)initView;
@end
//
// GMRateView.m
// Gengmei
//
// Created by Thierry on 1/22/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMRateView.h"
#import <Masonry/Masonry.h>
@implementation GMRateView
- (void)initView
{
for (int i=0; i<5; i++) {
UIImageView *rateImage = [[UIImageView alloc] init];
[rateImage setImage:[UIImage imageNamed:@"star_empty"]];
[self addSubview:rateImage];
[rateImage mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.mas_left).offset(i*19);
make.top.equalTo(self.mas_top);
make.size.mas_equalTo(CGSizeMake(15, 15));
}];
}
}
- (void)layoutSubviews
{
[super layoutSubviews];
for (int i=0; i<(_rate > 5 ? 5 : _rate); i++) {
UIImageView *rateImage = self.subviews[i];
[rateImage setImage:[UIImage imageNamed:@"star_full"]];
}
}
@end
//
// GMScrollView.h
// Gengmei
//
// Created by wangyang on 15/12/3.
// Copyright © 2015年 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, GMScrollViewLayout) {
GMScrollViewLayoutHorizontal,
GMScrollViewLayoutVertical
};
@interface GMScrollView : UIScrollView
/**
* @author licong, 16-12-30 18:12:22
*
* 初始化方法
*
* @since 5.8
*/
- (void)setup;
#pragma mark - 便捷的添加subview的方法
@property (nonatomic, strong) UIView *container;
@property(nonatomic) UIEdgeInsets contentInset;
/**
* @author wangyang, 16-12-31 16:12:37
*
* @brief subview的排布方向,默认是 GMScrollViewLayoutHorizontal
* @since 5.8.0
*/
@property (nonatomic, assign) GMScrollViewLayout layout;
@property (nonatomic, assign) CGSize itemSize;
@property (nonatomic, assign) CGFloat innerSpace;
// 点击某一个item
@property (nonatomic, copy) void (^itemClickBlock)(NSInteger index);
/**
* @author licong, 16-12-30 18:12:38
*
* 在scrollview上用约束进行布局,加上一个container,比较好布局
*
* @param view 将要在contaner上的view
*
* @since 5.8
*/
- (void)containerAppendSubview:(UIView *)view;
/**
* @author wangyang, 16-12-31 16:12:12
*
* @brief 给 subview 添加约束
* @since 5.8.0
*/
- (void)containerLayoutSubview;
/**
* @author licong, 16-12-30 18:12:28
*
* 删除container上所有的子视图
*
* @since 5.8
*/
- (void)containerRemoveAllSubviews;
@end
//
// GMScrollView.m
// Gengmei
//
// Created by wangyang on 15/12/3.
// Copyright © 2015年 Wanmeichuangyi. All rights reserved.
//
#import "GMScrollView.h"
#import <Masonry/Masonry.h>
@interface GMScrollView () {
}
@end
@implementation GMScrollView
@dynamic contentInset;
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)awakeFromNib{
[super awakeFromNib];
[self setup];
}
- (void)setup{
}
- (void)containerAppendSubview:(UIView *)view{
[self.container addSubview:view];
}
- (void)containerLayoutSubview {
UIView *preView;
for (NSInteger i = 0; i < self.container.subviews.count ; i ++) {
UIView *currentView = self.container.subviews[i];
if (self.layout == GMScrollViewLayoutHorizontal) {
[currentView mas_makeConstraints:^(MASConstraintMaker *make) {
if ( i == 0) {
make.left.mas_equalTo(0);
}else{
make.left.equalTo(preView.mas_right).offset(self.innerSpace);
}
make.top.mas_equalTo(0);
make.bottom.mas_equalTo(0);
if (self.itemSize.width != 0) {
make.width.mas_equalTo(self.itemSize.width);
}
if (self.itemSize.height != 0) {
make.height.mas_equalTo(self.itemSize.height);
}
if (i == self.container.subviews.count - 1) {
make.right.mas_equalTo(0);
}
}];
}else{
[currentView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(0);
make.right.mas_equalTo(0);
if (self.itemSize.width != 0) {
make.width.mas_equalTo(self.itemSize.width);
}
if (self.itemSize.height != 0) {
make.height.mas_equalTo(self.itemSize.height);
}
if ( i == 0) {
make.top.mas_equalTo(0);
}else{
make.top.equalTo(preView.mas_bottom).offset(self.innerSpace);
}
if (i == self.container.subviews.count - 1) {
make.bottom.mas_equalTo(0);
}
}];
}
currentView.tag = i;
currentView.userInteractionEnabled = true;
UITapGestureRecognizer *tapGest = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(itemClicked:)];
[currentView addGestureRecognizer:tapGest];
preView = currentView;
}
}
- (void)itemClicked:(UITapGestureRecognizer *)guester
{
if (self.itemClickBlock) {
self.itemClickBlock([guester view].tag);
}
}
- (void)containerRemoveAllSubviews {
[self.container.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
}
- (UIView *)container{
if (!_container) {
_container = [UIView new];
_container.backgroundColor = [UIColor clearColor];
[self addSubview:_container];
[_container mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.insets(UIEdgeInsetsZero);
}];
}
return _container;
}
@end
//
// GMBaseTableView.h
// Gengmei
//
// Created by 翟国钧 on 15/1/14.
// Copyright (c) 2015年 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "GMTableViewCell.h"
@interface GMTableView : UITableView
/**
* @author licong, 16-12-30 11:12:34
*
* 默认的初始化方法
*
* @since 5.8
*/
- (void)setup;
@end
@interface GMTableView (FixWierdTableHeaderAnimation)
/**
* @author licong, 16-12-30 11:12:47
*
* 这个方法修复了一个 bug:下拉刷新,刷新完成后,table view 会立即复位,而 section header 却自己做了一个动画移动到原位置。应该是跟随 table view 立即复位
*
*
* @since 5.8
*/
- (void)fixWierdTableHeaderAnimationWhenRefreshFinish;
@end
//
// GMBaseTableView.m
// Gengmei
//
// Created by 翟国钧 on 15/1/14.
// Copyright (c) 2015年 Wanmeichuangyi. All rights reserved.
//
#import "GMTableView.h"
#import "UIView+Layout.h"
@implementation GMTableView
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self setup];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style{
self = [super initWithFrame:frame style:style];
if (self) {
[self setup];
}
return self;
}
- (void)setup{
self.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
self.separatorStyle = UITableViewCellSeparatorStyleNone;
}
@end
@implementation GMTableView (FixWierdTableHeaderAnimation)
- (void)fixWierdTableHeaderAnimationWhenRefreshFinish{
if (self.tableHeaderView.height > 0) {
return;
}
// 我们希望在拖动时 section header 可以浮在上面,所以使用 scrollView.dragging 来区分。
if (self.dragging) {
// 在手动拖拽时,立即设置 contentInset 为 UIEdgeInsetsZero,就可以保证 section header 浮在上面。
self.contentInset = UIEdgeInsetsZero;
}else{
// 下拉时, 这两个分支保证在下拉过程及刷新后, section 及 table 能回复到正确位置
CGFloat sectionHeaderHeight = 64;
// 下拉刷新结束,scrollView 自动复位,那么此时也立即改变 UIEdgeInsetsMake 以保证 section header 能跟随 scroll view 一起动
if (self.contentOffset.y < sectionHeaderHeight && self.contentOffset.y >= 0) {
self.contentInset = UIEdgeInsetsMake(-self.contentOffset.y, 0, 0, 0);
}
// 当 table view 恢复到原来的位置,其实 contentInset,contentOffset 已经被我们改变,要恢复到初始状态
else if (self.contentOffset.y == sectionHeaderHeight) {
self.contentInset = UIEdgeInsetsZero;
self.contentOffset = CGPointZero;
}
}
}
@end
\ No newline at end of file
//
// GMBaseTableViewCell.h
// Gengmei
//
// Created by 翟国钧 on 15/1/14.
// Copyright (c) 2015年 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface GMTableViewCell : UITableViewCell{
UIImageView *_detailDisclosure;
}
@property (nonatomic, strong) UIImageView *detailDisclosure;
/** @brief Cell统一下边框。不需要时需要隐藏 */
@property (nonatomic , strong) UIView *bottomLine;
/**
* @brief 代替 UITableViewCellAccessoryDetailDisclosureButton,默认 NO,不显示。控制的是 _detailDisclosure。
*/
@property (nonatomic, assign) BOOL showArrow;
/**
* @brief 重写的起点。如果想在cell里添加息定义的view,或者改变bottomLine,从这里开始。
* 必须调用super
*/
- (void)setup __attribute__((objc_requires_super));
#pragma mark - Autolayout
/**
* @author wangyang, 15-11-02 11:11:48
*
* @brief 在 lifeCycle 里调用。里面为固定不变的约束
* @since 5.4.0
*/
- (void)cellConstraints;
/**
* @author wangyang, 15-11-02 11:11:48
*
* @brief 在 lifeCycle 里调用。需要变化的约束写在这里
* @since 5.4.0
*/
- (void)updateCellConstraints;
@end
//
// GMBaseTableViewCell.m
// Gengmei
//
// Created by 翟国钧 on 15/1/14.
// Copyright (c) 2015年 Wanmeichuangyi. All rights reserved.
//
#import "GMTableViewCell.h"
#import "UIView+LineWithAutolayout.h"
#import <Masonry/Masonry.h>
#import "GMConstant.h"
#import "GMTheme.h"
@implementation GMTableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self lifeCycle];
}
return self;
}
- (void)awakeFromNib{
[super awakeFromNib];
[self lifeCycle];
}
- (void)lifeCycle{
[self setup];
// 保证在 cell layout 前有“全约束”
[self cellConstraints];
[self updateCellConstraints];
}
- (void)setup{
// 初始一个较大的 bounds,就不会出现在刚刚约束时就出现的 subView 与 contentView 的约束冲突
self.contentView.bounds = [UIScreen mainScreen].bounds;
self.selectionStyle = UITableViewCellSelectionStyleNone;
self.accessoryType = UITableViewCellAccessoryNone;
_bottomLine = [self.contentView addBottomLine];
_bottomLine.backgroundColor = BACKGROUND_COLOR;
_detailDisclosure = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"arrow"]];
_detailDisclosure.hidden = YES;
[self.contentView addSubview:_detailDisclosure];
[_detailDisclosure mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(-15);
make.centerY.mas_equalTo(0);
make.size.sizeOffset(CGSizeMake(7, 11));
}];
}
- (void)prepareForReuse{
[super prepareForReuse];
// 在 cell 被复用时,也先设置一个较大的 bounds。因为如果即将 layout 的 subview 超出 contentView时,会发出的约束警告,有了这个 bounds,警告就不会出现了
self.contentView.bounds = [UIScreen mainScreen].bounds;
}
// 的iOS8下发现不重写这个 seperator 不生效
- (UIEdgeInsets)layoutMargins
{
return UIEdgeInsetsZero;
}
- (void)setShowArrow:(BOOL)showArrow{
_showArrow = showArrow;
_detailDisclosure.hidden = !_showArrow;
}
- (void)cellConstraints{
}
- (void)updateCellConstraints{
}
@end
//
// GMTextField.h
// Gengmei
//
// Created by wangyang on 1/23/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface GMTextField : UITextField
@end
//
// GMTextField.m
// Gengmei
//
// Created by wangyang on 1/23/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMTextField.h"
@implementation GMTextField
- (CGRect)textRectForBounds:(CGRect)bounds{
CGRect rect = [super textRectForBounds:bounds];
return UIEdgeInsetsInsetRect(rect, UIEdgeInsetsMake(3, 8, 0, 8));
}
- (CGRect)placeholderRectForBounds:(CGRect)bounds{
return [self textRectForBounds:bounds];
}
- (CGRect)editingRectForBounds:(CGRect)bounds{
return [self textRectForBounds:bounds];
}
@end
//
// GMTopSearchButton.h
// Gengmei
//
// Created by wangyang on 4/27/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "GMButton.h"
@interface GMTopSearchButton : GMButton
@end
//
// GMTopSearchButton.m
// Gengmei
//
// Created by wangyang on 4/27/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMTopSearchButton.h"
#import "GMFont.h"
#import "GMTheme.h"
@implementation GMTopSearchButton
- (void)setup{
[super setup];
self.backgroundColor = [UIColor clearColor];
[self setTitle:@"搜索项目、商品、专家" forState:UIControlStateNormal];
[self setImage:[UIImage imageNamed:@"search_button"] forState:UIControlStateNormal];
[self setTitleColor:RGBCOLOR_HEX(0xcccccc) forState:UIControlStateNormal];
[self setTitle:@"搜索项目、商品、专家" forState:UIControlStateHighlighted];
[self setImage:[UIImage imageNamed:@"search_button"] forState:UIControlStateHighlighted];
[self setTitleColor:RGBCOLOR_HEX(0xcccccc) forState:UIControlStateHighlighted];
self.titleLabel.font = GMFont(13);
// 微调位置,以后搜索页的导航placeholder位置一模一样
self.titleEdgeInsets = UIEdgeInsetsMake(-2, 8, 0, 0);
self.imageEdgeInsets = UIEdgeInsetsMake(-1, -7, 0, 0);
}
@end
//
// GMImageTitleLabelButton.h
// Gengmei
//
// Created by wangyang on 15/10/15.
// Copyright © 2015年 Wanmeichuangyi. All rights reserved.
//
#import "GMButton.h"
/**
* @author wangyang, 15-10-15 15:10:38
*
* @brief 默认有一个约束,图片在上,标题在下。
可以重写updateConstraints 方法以重新布局。
可以重写 setup 方法以自定义样式及图片
* @since 5.2.0
*/
__deprecated_msg("使用GMKit-Swift库中的AllLayoutButton.swift代替,,会在9月份被删除掉")
@interface GMVerticalLayoutButton : GMButton
@property (nonatomic, strong) UILabel *buttonLabel;
@property (nonatomic, strong) UIImageView *buttonImageView;
@end
//
// GMImageTitleLabelButton.m
// Gengmei
//
// Created by wangyang on 15/10/15.
// Copyright © 2015年 Wanmeichuangyi. All rights reserved.
//
#import "GMVerticalLayoutButton.h"
#import "GMFont.h"
#import <Masonry/Masonry.h>
#import "GMTheme.h"
@implementation GMVerticalLayoutButton
- (void)setup{
[super setup];
_buttonLabel = [UILabel new];
_buttonLabel.font = GMFont(10);
_buttonLabel.textColor = BODY_TEXT_COLOR;
[self addSubview:_buttonLabel];
_buttonImageView = [UIImageView new];
[self addSubview:_buttonImageView];
[self setBackgroundColor:RGBCOLOR_HEX(0xfafafa) forState:UIControlStateHighlighted];
[self setBackgroundColor:[UIColor whiteColor] forState:UIControlStateNormal];
}
- (void)updateConstraints{
[self.buttonLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(-10);
make.centerX.mas_equalTo(0);
}];
[self.buttonImageView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(10);
make.centerX.mas_equalTo(0);
}];
[super updateConstraints];
}
@end
//
// GMView.h
// Gengmei
//
// Created by wangyang on 9/14/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import <UIKit/UIKit.h>
@import GMOCConstant;
@import Masonry;
@import GMFoundation;
@interface GMView : UIView
/**
* @brief 不需要重写 init 方法,如果代码里调用了 init 方法,SDK 会自动调用 initWithFrame
*
*/
- (void)setup __attribute__((objc_requires_super));
@end
//
// GMView.m
// Gengmei
//
// Created by wangyang on 9/14/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#import "GMView.h"
@implementation GMView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)awakeFromNib{
[super awakeFromNib];
[self setup];
}
- (void)setup{
}
@end
//
// WYSegmentView.h
// GengMei
//
// Created by wangyang on 13-8-9.
// Copyright (c) 2015 Gengmei. All rights reserved.
//
#import "GMView.h"
typedef NS_ENUM(NSUInteger, WYSegmentViewWidthStyle) {
WYSegmentViewWidthStyleFixed, // Segment width is fixed
WYSegmentViewWidthStyleDynamic, // Segment width will only be as big as the text width (including inset)
};
@interface WYSegmentView : GMView
/**
* 数组的内容是要显示的 button title,设置该属性后,button 将会被自动添加,详见其 set 方法
*/
@property (nonatomic, strong) NSArray *sectionTitles;
@property (nonatomic, readonly) NSInteger selectedSegmentIndex;
@property (nonatomic, copy) void (^didSelectedBlock)(NSUInteger index);
- (void)layoutSegments;
- (void)setSelectedSegmentIndex:(NSInteger)selectedSegmentIndex animated:(BOOL)animated;
#pragma mark - 自定义UI
// 没有使用 GMScrollView是想把这个 segment view做得更独立一些
@property (nonatomic, readonly) UIScrollView *scrollView;
@property (nonatomic, readonly) UIView *bottomLine;
/**
* @author wangyang, 16-02-17 15:02:53
*
* @brief 默认是Dynamic
* @since 1.0
*/
@property (nonatomic, assign) WYSegmentViewWidthStyle widthStyle;
/**
* @author wangyang, 16-02-15 18:02:32
*
* @brief button之间的间距
* @since 1.0
*/
@property (nonatomic, assign) CGFloat innerSpace;
/**
* @author wangyang, 16-02-16 10:02:51
*
* @brief button在计算宽度时会将这个这个padding算在内,左右各加一个 itemHorizonalPadding,即buttonWidth = buttonTitleWidth + 2 * itemHorizonalPadding
* @since 1.0
*/
@property (nonatomic, assign) CGFloat itemHorizonalPadding;
@property (nonatomic, strong) NSDictionary *titleTextAttributes;
@property (nonatomic, strong) NSDictionary *selectedTitleTextAttributes;
/**
* @author wangyang, 16-02-17 11:02:24
*
* @brief 实现这个block,以便继续自定义button的UI。比如添加背景色,边框等
* @since 1.0
*/
@property (nonatomic, copy) void (^customButtonUI) (UIButton *button);
/**
* @author wangyang, 16-02-15 18:02:19
*
* @brief 是否显示指示器。默认为NO
* @since 1.0
*/
@property (nonatomic, assign) BOOL showIndicator;
@property (nonatomic, strong) UIColor *indicatorColor;
/**
* @author wangyang, 16-02-17 16:02:38
*
* @brief 默认为0,此时indicator.width与button的标题一样长。
* @since 1.0
*/
@property (nonatomic, assign) CGFloat indicatorHorizonalPadding;
/**
* @author wangyang, 16-02-17 15:02:17
*
* @brief 自定义 indicator 的位置
* @param indicator 指示符
* @param button 待指示的button
* @since 1.0
*/
@property (nonatomic, copy) void (^customIndicatorConstraint) (UIView *indicator, UIButton *button);
@end
//
// WYSegmentView.h
// GengMei
//
// Created by wangyang on 13-8-9.
// Copyright (c) 2015 Gengmei. All rights reserved.
//
#import "WYSegmentView.h"
#import <QuartzCore/QuartzCore.h>
#import "Masonry.h"
#import "UIView+Layout.h"
#import "GMConstant.h"
#import "UIView+LineWithAutolayout.h"
#import "GMFont.h"
#import "GMTheme.h"
#define BUTTON_TAG 1010
@interface WYSegmentView ()
{
UIView *_indicatorView;
UIView *_container;
}
@property (nonatomic, assign) NSInteger selectedSegmentIndex;
@end
@implementation WYSegmentView
- (void)setup
{
[super setup];
_bottomLine = [self addBottomLine];
// scroll view
_scrollView = [UIScrollView new];
[self addSubview:_scrollView];
[_scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.insets(UIEdgeInsetsZero);
}];
// container
_container = [UIView new];
[_scrollView addSubview:_container];
[_container mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.insets(UIEdgeInsetsZero);
}];
// 属性初始化
_scrollView.showsHorizontalScrollIndicator = NO;
_scrollView.scrollsToTop = NO;
self.innerSpace = 0;
self.indicatorHorizonalPadding = 7.5 * ONE_PIXEL;
self.titleTextAttributes = @{NSFontAttributeName : GMFont(14),
NSForegroundColorAttributeName : [UIColor whiteColor]};
self.selectedTitleTextAttributes = @{NSFontAttributeName : GMFont(14),
NSForegroundColorAttributeName : [UIColor greenColor]};
self.showIndicator = NO;
self.indicatorColor = MAIN_VISUAL_COLOR;
self.widthStyle = WYSegmentViewWidthStyleDynamic;
_selectedSegmentIndex = 0;
// 添加指示器
[self setupIndicatorView];
}
- (void)setupIndicatorView {
_indicatorView = [UIView new];
_indicatorView.backgroundColor = self.indicatorColor;
[_scrollView insertSubview:_indicatorView atIndex:0];
__weak __typeof(self)weakSelf = self;
self.customIndicatorConstraint = ^(UIView *indicator, UIButton *button) {
// 在 _sectionTitles 还没有数据时,button 并没有被添加,会是 nil, 所以添加如下判断
if (button == nil || indicator == nil) {
return;
}
// 改变indicator
[indicator mas_remakeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(0);
make.width.equalTo(button.titleLabel.mas_width).offset(weakSelf.indicatorHorizonalPadding);
make.centerX.equalTo(button);
make.height.mas_equalTo(2);//和设计沟通这里要的是2pt不是2px
}];
};
}
- (void)setupSegments {
UIView *preView;
[_container removeAllSubviews];
for (int i = 0; i < _sectionTitles.count; i++) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.tag = i + BUTTON_TAG;
[button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
// 标题
NSString *title = _sectionTitles[i];
NSAttributedString *attriTitle = [[NSAttributedString alloc] initWithString:title attributes:self.titleTextAttributes];
[button setAttributedTitle:attriTitle forState:UIControlStateNormal];
NSAttributedString *seletedAttriTitle = [[NSAttributedString alloc] initWithString:title attributes:self.selectedTitleTextAttributes];
[button setAttributedTitle:seletedAttriTitle forState:UIControlStateSelected];
// 约束
[_container addSubview:button];
// 每个button的约束。这里考虑了可能设置 self.contentInset的情况
CGSize titleSize = [attriTitle size];
[button mas_makeConstraints:^(MASConstraintMaker *make) {
if (i == 0) {
make.left.mas_equalTo(0);
}else{
make.left.equalTo(preView.mas_right).offset(self.innerSpace);
}
make.top.equalTo(_container).offset(_scrollView.contentInset.top);
make.bottom.equalTo(_container).offset(-_scrollView.contentInset.bottom);
make.height.equalTo(_scrollView).offset(-_scrollView.contentInset.top - _scrollView.contentInset.bottom);
if (self.widthStyle == WYSegmentViewWidthStyleDynamic) {
make.width.mas_equalTo(ceilf(titleSize.width + _itemHorizonalPadding * 2));
}else {
make.width.equalTo(_scrollView).multipliedBy(1.0/_sectionTitles.count).offset(0);
}
if (i == _sectionTitles.count-1) {
make.right.mas_equalTo(0);
}
}];
if (self.customButtonUI) {
self.customButtonUI(button);
}
preView = button;
}
}
#pragma mark - Setter
/**
* 清除原来的 button,并依据 title array 重新添加 button
*
* @param buttonTitleArray 待显示的 title array
*/
- (void)setSectionTitles:(NSArray *)sectionTitles
{
if (sectionTitles.count == 0) {
return;
}
_sectionTitles = sectionTitles;
[_container.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
}
- (void)setShowIndicator:(BOOL)showIndicator {
_showIndicator = showIndicator;
_indicatorView.hidden = !_showIndicator;
}
- (void)setIndicatorColor:(UIColor *)indicatorColor {
_indicatorColor = indicatorColor;
_indicatorView.backgroundColor = _indicatorColor;
}
#pragma mark - Layout Views
- (void)layoutSegments
{
[self setupSegments];
[self setSelectedSegmentIndex:_selectedSegmentIndex animated:NO];
}
#pragma mark - Animation
- (void)animateSelectedButtonBackgroundAtIndex:(NSUInteger)index animate:(BOOL)animate
{
// 将 _indicatorView 移动到相应 button 位置
UIButton *currentButton = (UIButton *)[_container viewWithTag:index + BUTTON_TAG];
// 改变indicator的坐标
if (self.customIndicatorConstraint) {
self.customIndicatorConstraint(_indicatorView, currentButton);
}
[UIView animateWithDuration:(animate ? 0.25 : 0.0) animations:^{
[self layoutIfNeeded];
// 改变offset
// 当rect的中心点大于scrollview的中心点,那就可以把这个button往中间移一下
if (CGRectGetMidX(currentButton.frame) > self.width / 2) {
// 下面这个算法是基于offset为0的情况
CGFloat offset = CGRectGetMidX(currentButton.frame) - self.width / 2;
CGFloat maxOffset = _scrollView.contentSize.width - self.width + _scrollView.contentInset.right;
if (offset > maxOffset) {
offset = maxOffset;
}
_scrollView.contentOffset = CGPointMake(offset, 0);
}else {
_scrollView.contentOffset = CGPointMake(-_scrollView.contentInset.left, 0);
}
}];
}
- (void)setSelectedSegmentIndex:(NSInteger)selectedSegmentIndex animated:(BOOL)animated {
// 通过使用 selectedIndex 的 set 方法来执行button选择状态的 UI 变化,详见其 set 方法
[self changeStatusForePreButton:self.selectedSegmentIndex newButton:selectedSegmentIndex];
// 改变属性值
_selectedSegmentIndex = selectedSegmentIndex;
// 调用动画
[self animateSelectedButtonBackgroundAtIndex:selectedSegmentIndex animate:animated];
}
- (void)changeStatusForePreButton:(NSInteger)preButtonIndex newButton:(NSInteger)newButtonIndex{
// 取消先前 button 的状态
UIButton *previousbutton = (UIButton *)[_container viewWithTag:preButtonIndex + BUTTON_TAG];
previousbutton.selected = NO;
// 处理新的 button
UIButton *currentButton = (UIButton *)[_container viewWithTag:newButtonIndex + BUTTON_TAG];
currentButton.selected = YES;
// 立即变更button状态,以防之后的动画影响button title的改变
[self layoutIfNeeded];
}
#pragma mark - Action
- (void)buttonClick:(UIButton *)button
{
[self setSelectedSegmentIndex:button.tag - BUTTON_TAG animated:YES];
if (_didSelectedBlock) {
_didSelectedBlock(self.selectedSegmentIndex);
}
}
@end
# GMKit
[![CI Status](http://img.shields.io/travis/licong/GMKit.svg?style=flat)](https://travis-ci.org/licong/GMKit)
[![Version](https://img.shields.io/cocoapods/v/GMKit.svg?style=flat)](http://cocoapods.org/pods/GMKit)
[![License](https://img.shields.io/cocoapods/l/GMKit.svg?style=flat)](http://cocoapods.org/pods/GMKit)
[![Platform](https://img.shields.io/cocoapods/p/GMKit.svg?style=flat)](http://cocoapods.org/pods/GMKit)
## Usage
To run the example project, clone the repo, and run `pod install` from the Example directory first.
## Requirements
## Installation
To install it, simply add the following line to your Podfile:
```
use_frameworks!
platform :ios, '8.0'
source 'http://git.gengmei.cc/gengmeiios/GMSpecs.git'
pod "GMKit"
```
## Author
iOS Team
## License
仅限北京更美互动信息科技有限公司内部使用
\ No newline at end of file
//
// GMConstant.h
// Gengmei
//
// Created by licong on 15/12/31.
// Copyright © 2015年 Wanmeichuangyi. All rights reserved.
//
#ifndef GMConstant_h
#define GMConstant_h
//设备屏幕大小
#define MAINSCREEN_FRAME [[UIScreen mainScreen] bounds]
//设备屏幕宽
#define MAINSCREEN_WIDTH MAINSCREEN_FRAME.size.width
//设备屏幕高
#define MAINSCREEN_HEIGHT MAINSCREEN_FRAME.size.height
#define ONE_PIXEL (1/[UIScreen mainScreen].scale)
//获取NSUserdefault
#define USERDEFAULT [NSUserDefaults standardUserDefaults]
//分页每一页的数据数量
#define PAGE_COUNT 10
// version check
#define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options: NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options: NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options: NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options: NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options: NSNumericSearch] != NSOrderedDescending)
//沙盒目录
#define PATH_OF_APP_HOME NSHomeDirectory()
#define PATH_OF_TEMP NSTemporaryDirectory()
#define PATH_OF_BUNDLE [NSBundle mainBundle].resourcePath
#define PATH_OF_DOCUMENT [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]
//App版本
#define APP_VERSION [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]
#define APP_BUILD_VERSION [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]
#endif /* GMConstant_h */
//
// GMOCConstaintIgnore.h
// Pods
//
// Created by wangyang on 16/9/30.
//
//
#import <Foundation/Foundation.h>
// 该类只是支撑起这个库,让其成为一个Module,让这个库使用起来更方便
@interface GMOCConstaintIgnore : NSObject
@end
//
// GMOCConstaintIgnore.m
// Pods
//
// Created by wangyang on 16/9/30.
//
//
#import "GMOCConstaintIgnore.h"
@implementation GMOCConstaintIgnore
@end
//
// GMTheme.h
// Gengmei
// 主题
// Created by Thierry on 1/7/15.
// Copyright (c) 2015 Wanmeichuangyi. All rights reserved.
//
#ifndef Gengmei_GMTheme_h
#define Gengmei_GMTheme_h
#define RGBCOLOR_HEX(hexColor) [UIColor colorWithRed: (((hexColor >> 16) & 0xFF))/255.0f \
green: (((hexColor >> 8) & 0xFF))/255.0f \
blue: ((hexColor & 0xFF))/255.0f \
alpha: 1]
//用户版色值
#ifdef IS_USER
//颜色规范
#define MAIN_VISUAL_COLOR RGBCOLOR_HEX(0x3ADBD2) //主视觉颜色
#define SECONDARY_VISUAL_COLOR RGBCOLOR_HEX(0xFF7690) //辅助颜色
#define MAIN_TIP_COLOR RGBCOLOR_HEX(0x9D704F) //提示文字主色(黄背景)
#define SECONDARY_TIP_COLOR RGBCOLOR_HEX(0xC19474)//提示文字辅助色(黄背景)
#define DISABLE_COLOR RGBCOLOR_HEX(0xD5D5D5) //失效颜色
#define SEPARATOR_LINE_COLOR RGBCOLOR_HEX(0xE5E5E5) //分割线颜色、边框颜色
#define BACKGROUND_COLOR RGBCOLOR_HEX(0xF5F5F5) //背景颜色
#define BACKGROUND_TIP_COLOR RGBCOLOR_HEX(0xF8F4DD) //提示背景颜色(黄)
#define BLACK_COLOR RGBCOLOR_HEX(0x000000) //纯黑颜色
#define WHITE_COLOR RGBCOLOR_HEX(0xFFFFFF) //纯白颜色
//文字颜色
#define HEADLINE_TEXT_COLOR RGBCOLOR_HEX(0x333333) //一级标题字、主要内容
#define BODY_TEXT_COLOR RGBCOLOR_HEX(0x666666) //正文、普通文字内容
#define SECONDARY_TEXT_COLOR RGBCOLOR_HEX(0x999999) //辅助文字色
//按钮颜色
#define BUTTON_NOMARL_GREEN_COLOR RGBCOLOR_HEX(0x3ADBD2) //目前和MAIN_VISUAL_COLOR一样
#define BUTTON_HIGHLIGHT_GREEN_COLOR RGBCOLOR_HEX(0x1FB2A7)
#define BUTTON_NOMARL_RED_COLOR RGBCOLOR_HEX(0xFF7690) //目前和SECONDARY_VISUAL_COLOR一样
#define BUTTON_HIGHLIGHT_RED_COLOR RGBCOLOR_HEX(0xE75873)
#define BUTTON_DISABLE_COLOR RGBCOLOR_HEX(0xD5D5D5) //目前和DISABLE_COLOR一样
//医生版色值,目前未支持
#elif IS_DOCTOR
//颜色规范
/* 医生版主视觉颜色,暂时改为红色,用来区分 */
#define MAIN_VISUAL_COLOR RGBCOLOR_HEX(0x3ADBD2) //医生版主视觉颜色,暂时改为红色,用来区分
#define SECONDARY_VISUAL_COLOR RGBCOLOR_HEX(0xFF7690) //医生版辅助颜色
#define MAIN_TIP_COLOR RGBCOLOR_HEX(0x9D704F) //医生版提示文字主色(黄背景)
#define SECONDARY_TIP_COLOR RGBCOLOR_HEX(0xC19474) //医生版提示文字辅助色(黄背景)
#define DISABLE_COLOR RGBCOLOR_HEX(0xD5D5D5) //医生版失效颜色
#define SEPARATOR_LINE_COLOR RGBCOLOR_HEX(0xE5E5E5) //医生版分割线颜色、边框颜色
#define BACKGROUND_COLOR RGBCOLOR_HEX(0xF5F5F5) //医生版背景颜色
#define BACKGROUND_TIP_COLOR RGBCOLOR_HEX(0xF8F4DD) //医生版提示背景颜色(黄)
#define BLACK_COLOR RGBCOLOR_HEX(0x000000) //医生版纯黑颜色
#define WHITE_COLOR RGBCOLOR_HEX(0xFFFFFF) //医生版纯白颜色
//文字颜色
#define HEADLINE_TEXT_COLOR RGBCOLOR_HEX(0x333333) //医生版一级标题字、主要内容
#define BODY_TEXT_COLOR RGBCOLOR_HEX(0x666666) //医生版正文、普通文字内容
#define SECONDARY_TEXT_COLOR RGBCOLOR_HEX(0x999999) //医生版辅助文字色
//按钮颜色
#define BUTTON_NOMARL_GREEN_COLOR RGBCOLOR_HEX(0x3ADBD2) //目前和MAIN_VISUAL_COLOR一样
#define BUTTON_HIGHLIGHT_GREEN_COLOR RGBCOLOR_HEX(0x1FB2A7)
#define BUTTON_NOMARL_RED_COLOR RGBCOLOR_HEX(0xFF7690) //目前和SECONDARY_VISUAL_COLOR一样
#define BUTTON_HIGHLIGHT_RED_COLOR RGBCOLOR_HEX(0xE75873)
#define BUTTON_DISABLE_COLOR RGBCOLOR_HEX(0xD5D5D5) //目前和DISABLE_COLOR一样
#else //这里的默认色值可以随意,默认填的是用户板的色值
//颜色规范
#define MAIN_VISUAL_COLOR RGBCOLOR_HEX(0x3ADBD2)
#define SECONDARY_VISUAL_COLOR RGBCOLOR_HEX(0xFF7690) //默认辅助颜色
#define MAIN_TIP_COLOR RGBCOLOR_HEX(0x9D704F) //默认提示文字主色(黄背景)
#define SECONDARY_TIP_COLOR RGBCOLOR_HEX(0xC19474) //默认提示文字辅助色(黄背景)
#define DISABLE_COLOR RGBCOLOR_HEX(0xD5D5D5) //默认失效颜色
#define SEPARATOR_LINE_COLOR RGBCOLOR_HEX(0xE5E5E5) //默认分割线颜色、边框颜色
#define BACKGROUND_COLOR RGBCOLOR_HEX(0xF5F5F5) //默认背景颜色
#define BACKGROUND_TIP_COLOR RGBCOLOR_HEX(0xF8F4DD) //默认提示背景颜色(黄)
#define BLACK_COLOR RGBCOLOR_HEX(0x000000) //默认纯黑颜色
#define WHITE_COLOR RGBCOLOR_HEX(0xFFFFFF) //默认纯白颜色
//文字颜色
#define HEADLINE_TEXT_COLOR RGBCOLOR_HEX(0x333333) //默认一级标题字、主要内容
#define BODY_TEXT_COLOR RGBCOLOR_HEX(0x666666) //默认正文、普通文字内容
#define SECONDARY_TEXT_COLOR RGBCOLOR_HEX(0x999999) //默认辅助文字色
//按钮颜色
#define BUTTON_NOMARL_GREEN_COLOR RGBCOLOR_HEX(0x3ADBD2) //目前和MAIN_VISUAL_COLOR一样
#define BUTTON_HIGHLIGHT_GREEN_COLOR RGBCOLOR_HEX(0x1FB2A7)
#define BUTTON_NOMARL_RED_COLOR RGBCOLOR_HEX(0xFF7690) //目前和SECONDARY_VISUAL_COLOR一样
#define BUTTON_HIGHLIGHT_RED_COLOR RGBCOLOR_HEX(0xE75873)
#define BUTTON_DISABLE_COLOR RGBCOLOR_HEX(0xD5D5D5) //目前和DISABLE_COLOR一样
#endif
#endif
Copyright (c) 2016 licong <1240690490@qq.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# GMOCConstant
[![CI Status](http://img.shields.io/travis/licong/GMOCConstant.svg?style=flat)](https://travis-ci.org/licong/GMOCConstant)
[![Version](https://img.shields.io/cocoapods/v/GMOCConstant.svg?style=flat)](http://cocoapods.org/pods/GMOCConstant)
[![License](https://img.shields.io/cocoapods/l/GMOCConstant.svg?style=flat)](http://cocoapods.org/pods/GMOCConstant)
[![Platform](https://img.shields.io/cocoapods/p/GMOCConstant.svg?style=flat)](http://cocoapods.org/pods/GMOCConstant)
## Example
To run the example project, clone the repo, and run `pod install` from the Example directory first.
## Requirements
## Installation
GMOCConstant is available through [CocoaPods](http://cocoapods.org). To install
it, simply add the following line to your Podfile:
```ruby
pod "GMOCConstant"
```
## Author
licong, 1240690490@qq.com
## License
GMOCConstant is available under the MIT license. See the LICENSE file for more info.
......@@ -20,9 +20,6 @@
"dependencies": {
"GMCache": [
"~> 0.1.1"
],
"GMKit": [
]
},
"libraries": "z"
......
PODS:
- Base64nl (1.2)
- GMCache (0.1.1):
- TMCache (~> 2.1.0)
- GMFoundation (0.0.2):
- Base64nl (= 1.2)
- GMKit (0.7.11):
- GMFoundation
- GMOCConstant
- Masonry (= 1.0.1)
- SDWebImage (= 3.7.6)
- GMOCConstant (0.0.3)
- GMPhobos (0.2.26):
- GMCache (~> 0.1.1)
- GMKit
- Masonry (1.0.1)
- SDWebImage (3.7.6):
- SDWebImage/Core (= 3.7.6)
- SDWebImage/Core (3.7.6)
- TMCache (2.1.0)
DEPENDENCIES:
......@@ -27,14 +13,8 @@ EXTERNAL SOURCES:
:path: "../"
SPEC CHECKSUMS:
Base64nl: a497bdcd1c01ea793d36b399016195a8713c0e95
GMCache: 73855b613b9d7e34f4f37ad425e8b8153b760c04
GMFoundation: 08b2e6e12c211ed37aa5dce3588f645a133b9165
GMKit: 04a30d67c6b5468f07c8d9f60d0f8b12dd90b162
GMOCConstant: 39371248b4d8d54929391bfcd2c5883776436c4b
GMPhobos: 853ef8239bee6c908286c9cbb3d8b39fa1c70e97
Masonry: a1a931a0d08870ed8ae415a2ea5ea68ebcac77df
SDWebImage: c325cf02c30337336b95beff20a13df489ec0ec9
GMPhobos: 613c57b5b302f8238db03a6090b9022524abc710
TMCache: 95ebcc9b3c7e90fb5fd8fc3036cba3aa781c9bed
PODFILE CHECKSUM: 281ed5ce2f0e8e2e49cc13b1e4726e6686ac6095
......
Copyright (c) 2011-2012 Masonry Team - https://github.com/Masonry
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
\ No newline at end of file
//
// MASCompositeConstraint.h
// Masonry
//
// Created by Jonas Budelmann on 21/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASConstraint.h"
#import "MASUtilities.h"
/**
* A group of MASConstraint objects
*/
@interface MASCompositeConstraint : MASConstraint
/**
* Creates a composite with a predefined array of children
*
* @param children child MASConstraints
*
* @return a composite constraint
*/
- (id)initWithChildren:(NSArray *)children;
@end
//
// MASCompositeConstraint.m
// Masonry
//
// Created by Jonas Budelmann on 21/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASCompositeConstraint.h"
#import "MASConstraint+Private.h"
@interface MASCompositeConstraint () <MASConstraintDelegate>
@property (nonatomic, strong) id mas_key;
@property (nonatomic, strong) NSMutableArray *childConstraints;
@end
@implementation MASCompositeConstraint
- (id)initWithChildren:(NSArray *)children {
self = [super init];
if (!self) return nil;
_childConstraints = [children mutableCopy];
for (MASConstraint *constraint in _childConstraints) {
constraint.delegate = self;
}
return self;
}
#pragma mark - MASConstraintDelegate
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
NSUInteger index = [self.childConstraints indexOfObject:constraint];
NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
[self.childConstraints replaceObjectAtIndex:index withObject:replacementConstraint];
}
- (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
id<MASConstraintDelegate> strongDelegate = self.delegate;
MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
newConstraint.delegate = self;
[self.childConstraints addObject:newConstraint];
return newConstraint;
}
#pragma mark - NSLayoutConstraint multiplier proxies
- (MASConstraint * (^)(CGFloat))multipliedBy {
return ^id(CGFloat multiplier) {
for (MASConstraint *constraint in self.childConstraints) {
constraint.multipliedBy(multiplier);
}
return self;
};
}
- (MASConstraint * (^)(CGFloat))dividedBy {
return ^id(CGFloat divider) {
for (MASConstraint *constraint in self.childConstraints) {
constraint.dividedBy(divider);
}
return self;
};
}
#pragma mark - MASLayoutPriority proxy
- (MASConstraint * (^)(MASLayoutPriority))priority {
return ^id(MASLayoutPriority priority) {
for (MASConstraint *constraint in self.childConstraints) {
constraint.priority(priority);
}
return self;
};
}
#pragma mark - NSLayoutRelation proxy
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attr, NSLayoutRelation relation) {
for (MASConstraint *constraint in self.childConstraints.copy) {
constraint.equalToWithRelation(attr, relation);
}
return self;
};
}
#pragma mark - attribute chaining
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
[self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
return self;
}
#pragma mark - Animator proxy
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
- (MASConstraint *)animator {
for (MASConstraint *constraint in self.childConstraints) {
[constraint animator];
}
return self;
}
#endif
#pragma mark - debug helpers
- (MASConstraint * (^)(id))key {
return ^id(id key) {
self.mas_key = key;
int i = 0;
for (MASConstraint *constraint in self.childConstraints) {
constraint.key([NSString stringWithFormat:@"%@[%d]", key, i++]);
}
return self;
};
}
#pragma mark - NSLayoutConstraint constant setters
- (void)setInsets:(MASEdgeInsets)insets {
for (MASConstraint *constraint in self.childConstraints) {
constraint.insets = insets;
}
}
- (void)setOffset:(CGFloat)offset {
for (MASConstraint *constraint in self.childConstraints) {
constraint.offset = offset;
}
}
- (void)setSizeOffset:(CGSize)sizeOffset {
for (MASConstraint *constraint in self.childConstraints) {
constraint.sizeOffset = sizeOffset;
}
}
- (void)setCenterOffset:(CGPoint)centerOffset {
for (MASConstraint *constraint in self.childConstraints) {
constraint.centerOffset = centerOffset;
}
}
#pragma mark - MASConstraint
- (void)activate {
for (MASConstraint *constraint in self.childConstraints) {
[constraint activate];
}
}
- (void)deactivate {
for (MASConstraint *constraint in self.childConstraints) {
[constraint deactivate];
}
}
- (void)install {
for (MASConstraint *constraint in self.childConstraints) {
constraint.updateExisting = self.updateExisting;
[constraint install];
}
}
- (void)uninstall {
for (MASConstraint *constraint in self.childConstraints) {
[constraint uninstall];
}
}
@end
//
// MASConstraint+Private.h
// Masonry
//
// Created by Nick Tymchenko on 29/04/14.
// Copyright (c) 2014 cloudling. All rights reserved.
//
#import "MASConstraint.h"
@protocol MASConstraintDelegate;
@interface MASConstraint ()
/**
* Whether or not to check for an existing constraint instead of adding constraint
*/
@property (nonatomic, assign) BOOL updateExisting;
/**
* Usually MASConstraintMaker but could be a parent MASConstraint
*/
@property (nonatomic, weak) id<MASConstraintDelegate> delegate;
/**
* Based on a provided value type, is equal to calling:
* NSNumber - setOffset:
* NSValue with CGPoint - setPointOffset:
* NSValue with CGSize - setSizeOffset:
* NSValue with MASEdgeInsets - setInsets:
*/
- (void)setLayoutConstantWithValue:(NSValue *)value;
@end
@interface MASConstraint (Abstract)
/**
* Sets the constraint relation to given NSLayoutRelation
* returns a block which accepts one of the following:
* MASViewAttribute, UIView, NSValue, NSArray
* see readme for more details.
*/
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation;
/**
* Override to set a custom chaining behaviour
*/
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute;
@end
@protocol MASConstraintDelegate <NSObject>
/**
* Notifies the delegate when the constraint needs to be replaced with another constraint. For example
* A MASViewConstraint may turn into a MASCompositeConstraint when an array is passed to one of the equality blocks
*/
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint;
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute;
@end
//
// MASConstraint.h
// Masonry
//
// Created by Jonas Budelmann on 22/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASUtilities.h"
/**
* Enables Constraints to be created with chainable syntax
* Constraint can represent single NSLayoutConstraint (MASViewConstraint)
* or a group of NSLayoutConstraints (MASComposisteConstraint)
*/
@interface MASConstraint : NSObject
// Chaining Support
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, NSLayoutAttributeRight
*/
- (MASConstraint * (^)(MASEdgeInsets insets))insets;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeWidth, NSLayoutAttributeHeight
*/
- (MASConstraint * (^)(CGSize offset))sizeOffset;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeCenterX, NSLayoutAttributeCenterY
*/
- (MASConstraint * (^)(CGPoint offset))centerOffset;
/**
* Modifies the NSLayoutConstraint constant
*/
- (MASConstraint * (^)(CGFloat offset))offset;
/**
* Modifies the NSLayoutConstraint constant based on a value type
*/
- (MASConstraint * (^)(NSValue *value))valueOffset;
/**
* Sets the NSLayoutConstraint multiplier property
*/
- (MASConstraint * (^)(CGFloat multiplier))multipliedBy;
/**
* Sets the NSLayoutConstraint multiplier to 1.0/dividedBy
*/
- (MASConstraint * (^)(CGFloat divider))dividedBy;
/**
* Sets the NSLayoutConstraint priority to a float or MASLayoutPriority
*/
- (MASConstraint * (^)(MASLayoutPriority priority))priority;
/**
* Sets the NSLayoutConstraint priority to MASLayoutPriorityLow
*/
- (MASConstraint * (^)())priorityLow;
/**
* Sets the NSLayoutConstraint priority to MASLayoutPriorityMedium
*/
- (MASConstraint * (^)())priorityMedium;
/**
* Sets the NSLayoutConstraint priority to MASLayoutPriorityHigh
*/
- (MASConstraint * (^)())priorityHigh;
/**
* Sets the constraint relation to NSLayoutRelationEqual
* returns a block which accepts one of the following:
* MASViewAttribute, UIView, NSValue, NSArray
* see readme for more details.
*/
- (MASConstraint * (^)(id attr))equalTo;
/**
* Sets the constraint relation to NSLayoutRelationGreaterThanOrEqual
* returns a block which accepts one of the following:
* MASViewAttribute, UIView, NSValue, NSArray
* see readme for more details.
*/
- (MASConstraint * (^)(id attr))greaterThanOrEqualTo;
/**
* Sets the constraint relation to NSLayoutRelationLessThanOrEqual
* returns a block which accepts one of the following:
* MASViewAttribute, UIView, NSValue, NSArray
* see readme for more details.
*/
- (MASConstraint * (^)(id attr))lessThanOrEqualTo;
/**
* Optional semantic property which has no effect but improves the readability of constraint
*/
- (MASConstraint *)with;
/**
* Optional semantic property which has no effect but improves the readability of constraint
*/
- (MASConstraint *)and;
/**
* Creates a new MASCompositeConstraint with the called attribute and reciever
*/
- (MASConstraint *)left;
- (MASConstraint *)top;
- (MASConstraint *)right;
- (MASConstraint *)bottom;
- (MASConstraint *)leading;
- (MASConstraint *)trailing;
- (MASConstraint *)width;
- (MASConstraint *)height;
- (MASConstraint *)centerX;
- (MASConstraint *)centerY;
- (MASConstraint *)baseline;
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
- (MASConstraint *)firstBaseline;
- (MASConstraint *)lastBaseline;
#endif
#if TARGET_OS_IPHONE || TARGET_OS_TV
- (MASConstraint *)leftMargin;
- (MASConstraint *)rightMargin;
- (MASConstraint *)topMargin;
- (MASConstraint *)bottomMargin;
- (MASConstraint *)leadingMargin;
- (MASConstraint *)trailingMargin;
- (MASConstraint *)centerXWithinMargins;
- (MASConstraint *)centerYWithinMargins;
#endif
/**
* Sets the constraint debug name
*/
- (MASConstraint * (^)(id key))key;
// NSLayoutConstraint constant Setters
// for use outside of mas_updateConstraints/mas_makeConstraints blocks
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, NSLayoutAttributeRight
*/
- (void)setInsets:(MASEdgeInsets)insets;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeWidth, NSLayoutAttributeHeight
*/
- (void)setSizeOffset:(CGSize)sizeOffset;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeCenterX, NSLayoutAttributeCenterY
*/
- (void)setCenterOffset:(CGPoint)centerOffset;
/**
* Modifies the NSLayoutConstraint constant
*/
- (void)setOffset:(CGFloat)offset;
// NSLayoutConstraint Installation support
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
/**
* Whether or not to go through the animator proxy when modifying the constraint
*/
@property (nonatomic, copy, readonly) MASConstraint *animator;
#endif
/**
* Activates an NSLayoutConstraint if it's supported by an OS.
* Invokes install otherwise.
*/
- (void)activate;
/**
* Deactivates previously installed/activated NSLayoutConstraint.
*/
- (void)deactivate;
/**
* Creates a NSLayoutConstraint and adds it to the appropriate view.
*/
- (void)install;
/**
* Removes previously installed NSLayoutConstraint
*/
- (void)uninstall;
@end
/**
* Convenience auto-boxing macros for MASConstraint methods.
*
* Defining MAS_SHORTHAND_GLOBALS will turn on auto-boxing for default syntax.
* A potential drawback of this is that the unprefixed macros will appear in global scope.
*/
#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))
#define mas_greaterThanOrEqualTo(...) greaterThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_lessThanOrEqualTo(...) lessThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_offset(...) valueOffset(MASBoxValue((__VA_ARGS__)))
#ifdef MAS_SHORTHAND_GLOBALS
#define equalTo(...) mas_equalTo(__VA_ARGS__)
#define greaterThanOrEqualTo(...) mas_greaterThanOrEqualTo(__VA_ARGS__)
#define lessThanOrEqualTo(...) mas_lessThanOrEqualTo(__VA_ARGS__)
#define offset(...) mas_offset(__VA_ARGS__)
#endif
@interface MASConstraint (AutoboxingSupport)
/**
* Aliases to corresponding relation methods (for shorthand macros)
* Also needed to aid autocompletion
*/
- (MASConstraint * (^)(id attr))mas_equalTo;
- (MASConstraint * (^)(id attr))mas_greaterThanOrEqualTo;
- (MASConstraint * (^)(id attr))mas_lessThanOrEqualTo;
/**
* A dummy method to aid autocompletion
*/
- (MASConstraint * (^)(id offset))mas_offset;
@end
//
// MASConstraint.m
// Masonry
//
// Created by Nick Tymchenko on 1/20/14.
//
#import "MASConstraint.h"
#import "MASConstraint+Private.h"
#define MASMethodNotImplemented() \
@throw [NSException exceptionWithName:NSInternalInconsistencyException \
reason:[NSString stringWithFormat:@"You must override %@ in a subclass.", NSStringFromSelector(_cmd)] \
userInfo:nil]
@implementation MASConstraint
#pragma mark - Init
- (id)init {
NSAssert(![self isMemberOfClass:[MASConstraint class]], @"MASConstraint is an abstract class, you should not instantiate it directly.");
return [super init];
}
#pragma mark - NSLayoutRelation proxies
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
- (MASConstraint * (^)(id))mas_equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
- (MASConstraint * (^)(id))greaterThanOrEqualTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationGreaterThanOrEqual);
};
}
- (MASConstraint * (^)(id))mas_greaterThanOrEqualTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationGreaterThanOrEqual);
};
}
- (MASConstraint * (^)(id))lessThanOrEqualTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationLessThanOrEqual);
};
}
- (MASConstraint * (^)(id))mas_lessThanOrEqualTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationLessThanOrEqual);
};
}
#pragma mark - MASLayoutPriority proxies
- (MASConstraint * (^)())priorityLow {
return ^id{
self.priority(MASLayoutPriorityDefaultLow);
return self;
};
}
- (MASConstraint * (^)())priorityMedium {
return ^id{
self.priority(MASLayoutPriorityDefaultMedium);
return self;
};
}
- (MASConstraint * (^)())priorityHigh {
return ^id{
self.priority(MASLayoutPriorityDefaultHigh);
return self;
};
}
#pragma mark - NSLayoutConstraint constant proxies
- (MASConstraint * (^)(MASEdgeInsets))insets {
return ^id(MASEdgeInsets insets){
self.insets = insets;
return self;
};
}
- (MASConstraint * (^)(CGSize))sizeOffset {
return ^id(CGSize offset) {
self.sizeOffset = offset;
return self;
};
}
- (MASConstraint * (^)(CGPoint))centerOffset {
return ^id(CGPoint offset) {
self.centerOffset = offset;
return self;
};
}
- (MASConstraint * (^)(CGFloat))offset {
return ^id(CGFloat offset){
self.offset = offset;
return self;
};
}
- (MASConstraint * (^)(NSValue *value))valueOffset {
return ^id(NSValue *offset) {
NSAssert([offset isKindOfClass:NSValue.class], @"expected an NSValue offset, got: %@", offset);
[self setLayoutConstantWithValue:offset];
return self;
};
}
- (MASConstraint * (^)(id offset))mas_offset {
// Will never be called due to macro
return nil;
}
#pragma mark - NSLayoutConstraint constant setter
- (void)setLayoutConstantWithValue:(NSValue *)value {
if ([value isKindOfClass:NSNumber.class]) {
self.offset = [(NSNumber *)value doubleValue];
} else if (strcmp(value.objCType, @encode(CGPoint)) == 0) {
CGPoint point;
[value getValue:&point];
self.centerOffset = point;
} else if (strcmp(value.objCType, @encode(CGSize)) == 0) {
CGSize size;
[value getValue:&size];
self.sizeOffset = size;
} else if (strcmp(value.objCType, @encode(MASEdgeInsets)) == 0) {
MASEdgeInsets insets;
[value getValue:&insets];
self.insets = insets;
} else {
NSAssert(NO, @"attempting to set layout constant with unsupported value: %@", value);
}
}
#pragma mark - Semantic properties
- (MASConstraint *)with {
return self;
}
- (MASConstraint *)and {
return self;
}
#pragma mark - Chaining
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute {
MASMethodNotImplemented();
}
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
- (MASConstraint *)right {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}
- (MASConstraint *)bottom {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottom];
}
- (MASConstraint *)leading {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeading];
}
- (MASConstraint *)trailing {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTrailing];
}
- (MASConstraint *)width {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth];
}
- (MASConstraint *)height {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight];
}
- (MASConstraint *)centerX {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterX];
}
- (MASConstraint *)centerY {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterY];
}
- (MASConstraint *)baseline {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBaseline];
}
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
- (MASConstraint *)firstBaseline {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeFirstBaseline];
}
- (MASConstraint *)lastBaseline {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLastBaseline];
}
#endif
#if TARGET_OS_IPHONE || TARGET_OS_TV
- (MASConstraint *)leftMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeftMargin];
}
- (MASConstraint *)rightMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRightMargin];
}
- (MASConstraint *)topMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTopMargin];
}
- (MASConstraint *)bottomMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottomMargin];
}
- (MASConstraint *)leadingMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeadingMargin];
}
- (MASConstraint *)trailingMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTrailingMargin];
}
- (MASConstraint *)centerXWithinMargins {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterXWithinMargins];
}
- (MASConstraint *)centerYWithinMargins {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterYWithinMargins];
}
#endif
#pragma mark - Abstract
- (MASConstraint * (^)(CGFloat multiplier))multipliedBy { MASMethodNotImplemented(); }
- (MASConstraint * (^)(CGFloat divider))dividedBy { MASMethodNotImplemented(); }
- (MASConstraint * (^)(MASLayoutPriority priority))priority { MASMethodNotImplemented(); }
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation { MASMethodNotImplemented(); }
- (MASConstraint * (^)(id key))key { MASMethodNotImplemented(); }
- (void)setInsets:(MASEdgeInsets __unused)insets { MASMethodNotImplemented(); }
- (void)setSizeOffset:(CGSize __unused)sizeOffset { MASMethodNotImplemented(); }
- (void)setCenterOffset:(CGPoint __unused)centerOffset { MASMethodNotImplemented(); }
- (void)setOffset:(CGFloat __unused)offset { MASMethodNotImplemented(); }
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
- (MASConstraint *)animator { MASMethodNotImplemented(); }
#endif
- (void)activate { MASMethodNotImplemented(); }
- (void)deactivate { MASMethodNotImplemented(); }
- (void)install { MASMethodNotImplemented(); }
- (void)uninstall { MASMethodNotImplemented(); }
@end
//
// MASConstraintBuilder.h
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASConstraint.h"
#import "MASUtilities.h"
typedef NS_OPTIONS(NSInteger, MASAttribute) {
MASAttributeLeft = 1 << NSLayoutAttributeLeft,
MASAttributeRight = 1 << NSLayoutAttributeRight,
MASAttributeTop = 1 << NSLayoutAttributeTop,
MASAttributeBottom = 1 << NSLayoutAttributeBottom,
MASAttributeLeading = 1 << NSLayoutAttributeLeading,
MASAttributeTrailing = 1 << NSLayoutAttributeTrailing,
MASAttributeWidth = 1 << NSLayoutAttributeWidth,
MASAttributeHeight = 1 << NSLayoutAttributeHeight,
MASAttributeCenterX = 1 << NSLayoutAttributeCenterX,
MASAttributeCenterY = 1 << NSLayoutAttributeCenterY,
MASAttributeBaseline = 1 << NSLayoutAttributeBaseline,
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
MASAttributeFirstBaseline = 1 << NSLayoutAttributeFirstBaseline,
MASAttributeLastBaseline = 1 << NSLayoutAttributeLastBaseline,
#endif
#if TARGET_OS_IPHONE || TARGET_OS_TV
MASAttributeLeftMargin = 1 << NSLayoutAttributeLeftMargin,
MASAttributeRightMargin = 1 << NSLayoutAttributeRightMargin,
MASAttributeTopMargin = 1 << NSLayoutAttributeTopMargin,
MASAttributeBottomMargin = 1 << NSLayoutAttributeBottomMargin,
MASAttributeLeadingMargin = 1 << NSLayoutAttributeLeadingMargin,
MASAttributeTrailingMargin = 1 << NSLayoutAttributeTrailingMargin,
MASAttributeCenterXWithinMargins = 1 << NSLayoutAttributeCenterXWithinMargins,
MASAttributeCenterYWithinMargins = 1 << NSLayoutAttributeCenterYWithinMargins,
#endif
};
/**
* Provides factory methods for creating MASConstraints.
* Constraints are collected until they are ready to be installed
*
*/
@interface MASConstraintMaker : NSObject
/**
* The following properties return a new MASViewConstraint
* with the first item set to the makers associated view and the appropriate MASViewAttribute
*/
@property (nonatomic, strong, readonly) MASConstraint *left;
@property (nonatomic, strong, readonly) MASConstraint *top;
@property (nonatomic, strong, readonly) MASConstraint *right;
@property (nonatomic, strong, readonly) MASConstraint *bottom;
@property (nonatomic, strong, readonly) MASConstraint *leading;
@property (nonatomic, strong, readonly) MASConstraint *trailing;
@property (nonatomic, strong, readonly) MASConstraint *width;
@property (nonatomic, strong, readonly) MASConstraint *height;
@property (nonatomic, strong, readonly) MASConstraint *centerX;
@property (nonatomic, strong, readonly) MASConstraint *centerY;
@property (nonatomic, strong, readonly) MASConstraint *baseline;
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
@property (nonatomic, strong, readonly) MASConstraint *firstBaseline;
@property (nonatomic, strong, readonly) MASConstraint *lastBaseline;
#endif
#if TARGET_OS_IPHONE || TARGET_OS_TV
@property (nonatomic, strong, readonly) MASConstraint *leftMargin;
@property (nonatomic, strong, readonly) MASConstraint *rightMargin;
@property (nonatomic, strong, readonly) MASConstraint *topMargin;
@property (nonatomic, strong, readonly) MASConstraint *bottomMargin;
@property (nonatomic, strong, readonly) MASConstraint *leadingMargin;
@property (nonatomic, strong, readonly) MASConstraint *trailingMargin;
@property (nonatomic, strong, readonly) MASConstraint *centerXWithinMargins;
@property (nonatomic, strong, readonly) MASConstraint *centerYWithinMargins;
#endif
/**
* Returns a block which creates a new MASCompositeConstraint with the first item set
* to the makers associated view and children corresponding to the set bits in the
* MASAttribute parameter. Combine multiple attributes via binary-or.
*/
@property (nonatomic, strong, readonly) MASConstraint *(^attributes)(MASAttribute attrs);
/**
* Creates a MASCompositeConstraint with type MASCompositeConstraintTypeEdges
* which generates the appropriate MASViewConstraint children (top, left, bottom, right)
* with the first item set to the makers associated view
*/
@property (nonatomic, strong, readonly) MASConstraint *edges;
/**
* Creates a MASCompositeConstraint with type MASCompositeConstraintTypeSize
* which generates the appropriate MASViewConstraint children (width, height)
* with the first item set to the makers associated view
*/
@property (nonatomic, strong, readonly) MASConstraint *size;
/**
* Creates a MASCompositeConstraint with type MASCompositeConstraintTypeCenter
* which generates the appropriate MASViewConstraint children (centerX, centerY)
* with the first item set to the makers associated view
*/
@property (nonatomic, strong, readonly) MASConstraint *center;
/**
* Whether or not to check for an existing constraint instead of adding constraint
*/
@property (nonatomic, assign) BOOL updateExisting;
/**
* Whether or not to remove existing constraints prior to installing
*/
@property (nonatomic, assign) BOOL removeExisting;
/**
* initialises the maker with a default view
*
* @param view any MASConstrait are created with this view as the first item
*
* @return a new MASConstraintMaker
*/
- (id)initWithView:(MAS_VIEW *)view;
/**
* Calls install method on any MASConstraints which have been created by this maker
*
* @return an array of all the installed MASConstraints
*/
- (NSArray *)install;
- (MASConstraint * (^)(dispatch_block_t))group;
@end
//
// MASConstraintBuilder.m
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASConstraintMaker.h"
#import "MASViewConstraint.h"
#import "MASCompositeConstraint.h"
#import "MASConstraint+Private.h"
#import "MASViewAttribute.h"
#import "View+MASAdditions.h"
@interface MASConstraintMaker () <MASConstraintDelegate>
@property (nonatomic, weak) MAS_VIEW *view;
@property (nonatomic, strong) NSMutableArray *constraints;
@end
@implementation MASConstraintMaker
- (id)initWithView:(MAS_VIEW *)view {
self = [super init];
if (!self) return nil;
self.view = view;
self.constraints = NSMutableArray.new;
return self;
}
- (NSArray *)install {
if (self.removeExisting) {
NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
for (MASConstraint *constraint in installedConstraints) {
[constraint uninstall];
}
}
NSArray *constraints = self.constraints.copy;
for (MASConstraint *constraint in constraints) {
constraint.updateExisting = self.updateExisting;
[constraint install];
}
[self.constraints removeAllObjects];
return constraints;
}
#pragma mark - MASConstraintDelegate
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
NSUInteger index = [self.constraints indexOfObject:constraint];
NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
[self.constraints replaceObjectAtIndex:index withObject:replacementConstraint];
}
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
if ([constraint isKindOfClass:MASViewConstraint.class]) {
//replace with composite constraint
NSArray *children = @[constraint, newConstraint];
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self;
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
}
if (!constraint) {
newConstraint.delegate = self;
[self.constraints addObject:newConstraint];
}
return newConstraint;
}
- (MASConstraint *)addConstraintWithAttributes:(MASAttribute)attrs {
__unused MASAttribute anyAttribute = (MASAttributeLeft | MASAttributeRight | MASAttributeTop | MASAttributeBottom | MASAttributeLeading
| MASAttributeTrailing | MASAttributeWidth | MASAttributeHeight | MASAttributeCenterX
| MASAttributeCenterY | MASAttributeBaseline
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
| MASAttributeFirstBaseline | MASAttributeLastBaseline
#endif
#if TARGET_OS_IPHONE || TARGET_OS_TV
| MASAttributeLeftMargin | MASAttributeRightMargin | MASAttributeTopMargin | MASAttributeBottomMargin
| MASAttributeLeadingMargin | MASAttributeTrailingMargin | MASAttributeCenterXWithinMargins
| MASAttributeCenterYWithinMargins
#endif
);
NSAssert((attrs & anyAttribute) != 0, @"You didn't pass any attribute to make.attributes(...)");
NSMutableArray *attributes = [NSMutableArray array];
if (attrs & MASAttributeLeft) [attributes addObject:self.view.mas_left];
if (attrs & MASAttributeRight) [attributes addObject:self.view.mas_right];
if (attrs & MASAttributeTop) [attributes addObject:self.view.mas_top];
if (attrs & MASAttributeBottom) [attributes addObject:self.view.mas_bottom];
if (attrs & MASAttributeLeading) [attributes addObject:self.view.mas_leading];
if (attrs & MASAttributeTrailing) [attributes addObject:self.view.mas_trailing];
if (attrs & MASAttributeWidth) [attributes addObject:self.view.mas_width];
if (attrs & MASAttributeHeight) [attributes addObject:self.view.mas_height];
if (attrs & MASAttributeCenterX) [attributes addObject:self.view.mas_centerX];
if (attrs & MASAttributeCenterY) [attributes addObject:self.view.mas_centerY];
if (attrs & MASAttributeBaseline) [attributes addObject:self.view.mas_baseline];
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
if (attrs & MASAttributeFirstBaseline) [attributes addObject:self.view.mas_firstBaseline];
if (attrs & MASAttributeLastBaseline) [attributes addObject:self.view.mas_lastBaseline];
#endif
#if TARGET_OS_IPHONE || TARGET_OS_TV
if (attrs & MASAttributeLeftMargin) [attributes addObject:self.view.mas_leftMargin];
if (attrs & MASAttributeRightMargin) [attributes addObject:self.view.mas_rightMargin];
if (attrs & MASAttributeTopMargin) [attributes addObject:self.view.mas_topMargin];
if (attrs & MASAttributeBottomMargin) [attributes addObject:self.view.mas_bottomMargin];
if (attrs & MASAttributeLeadingMargin) [attributes addObject:self.view.mas_leadingMargin];
if (attrs & MASAttributeTrailingMargin) [attributes addObject:self.view.mas_trailingMargin];
if (attrs & MASAttributeCenterXWithinMargins) [attributes addObject:self.view.mas_centerXWithinMargins];
if (attrs & MASAttributeCenterYWithinMargins) [attributes addObject:self.view.mas_centerYWithinMargins];
#endif
NSMutableArray *children = [NSMutableArray arrayWithCapacity:attributes.count];
for (MASViewAttribute *a in attributes) {
[children addObject:[[MASViewConstraint alloc] initWithFirstViewAttribute:a]];
}
MASCompositeConstraint *constraint = [[MASCompositeConstraint alloc] initWithChildren:children];
constraint.delegate = self;
[self.constraints addObject:constraint];
return constraint;
}
#pragma mark - standard Attributes
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
- (MASConstraint *)right {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}
- (MASConstraint *)bottom {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottom];
}
- (MASConstraint *)leading {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeading];
}
- (MASConstraint *)trailing {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTrailing];
}
- (MASConstraint *)width {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth];
}
- (MASConstraint *)height {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight];
}
- (MASConstraint *)centerX {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterX];
}
- (MASConstraint *)centerY {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterY];
}
- (MASConstraint *)baseline {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBaseline];
}
- (MASConstraint *(^)(MASAttribute))attributes {
return ^(MASAttribute attrs){
return [self addConstraintWithAttributes:attrs];
};
}
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
- (MASConstraint *)firstBaseline {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeFirstBaseline];
}
- (MASConstraint *)lastBaseline {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLastBaseline];
}
#endif
#if TARGET_OS_IPHONE || TARGET_OS_TV
- (MASConstraint *)leftMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeftMargin];
}
- (MASConstraint *)rightMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRightMargin];
}
- (MASConstraint *)topMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTopMargin];
}
- (MASConstraint *)bottomMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottomMargin];
}
- (MASConstraint *)leadingMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeadingMargin];
}
- (MASConstraint *)trailingMargin {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTrailingMargin];
}
- (MASConstraint *)centerXWithinMargins {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterXWithinMargins];
}
- (MASConstraint *)centerYWithinMargins {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterYWithinMargins];
}
#endif
#pragma mark - composite Attributes
- (MASConstraint *)edges {
return [self addConstraintWithAttributes:MASAttributeTop | MASAttributeLeft | MASAttributeRight | MASAttributeBottom];
}
- (MASConstraint *)size {
return [self addConstraintWithAttributes:MASAttributeWidth | MASAttributeHeight];
}
- (MASConstraint *)center {
return [self addConstraintWithAttributes:MASAttributeCenterX | MASAttributeCenterY];
}
#pragma mark - grouping
- (MASConstraint *(^)(dispatch_block_t group))group {
return ^id(dispatch_block_t group) {
NSInteger previousCount = self.constraints.count;
group();
NSArray *children = [self.constraints subarrayWithRange:NSMakeRange(previousCount, self.constraints.count - previousCount)];
MASCompositeConstraint *constraint = [[MASCompositeConstraint alloc] initWithChildren:children];
constraint.delegate = self;
return constraint;
};
}
@end
//
// MASLayoutConstraint.h
// Masonry
//
// Created by Jonas Budelmann on 3/08/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import "MASUtilities.h"
/**
* When you are debugging or printing the constraints attached to a view this subclass
* makes it easier to identify which constraints have been created via Masonry
*/
@interface MASLayoutConstraint : NSLayoutConstraint
/**
* a key to associate with this constraint
*/
@property (nonatomic, strong) id mas_key;
@end
//
// MASLayoutConstraint.m
// Masonry
//
// Created by Jonas Budelmann on 3/08/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import "MASLayoutConstraint.h"
@implementation MASLayoutConstraint
@end
//
// MASUtilities.h
// Masonry
//
// Created by Jonas Budelmann on 19/08/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE || TARGET_OS_TV
#import <UIKit/UIKit.h>
#define MAS_VIEW UIView
#define MAS_VIEW_CONTROLLER UIViewController
#define MASEdgeInsets UIEdgeInsets
typedef UILayoutPriority MASLayoutPriority;
static const MASLayoutPriority MASLayoutPriorityRequired = UILayoutPriorityRequired;
static const MASLayoutPriority MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 500;
static const MASLayoutPriority MASLayoutPriorityDefaultLow = UILayoutPriorityDefaultLow;
static const MASLayoutPriority MASLayoutPriorityFittingSizeLevel = UILayoutPriorityFittingSizeLevel;
#elif TARGET_OS_MAC
#import <AppKit/AppKit.h>
#define MAS_VIEW NSView
#define MASEdgeInsets NSEdgeInsets
typedef NSLayoutPriority MASLayoutPriority;
static const MASLayoutPriority MASLayoutPriorityRequired = NSLayoutPriorityRequired;
static const MASLayoutPriority MASLayoutPriorityDefaultHigh = NSLayoutPriorityDefaultHigh;
static const MASLayoutPriority MASLayoutPriorityDragThatCanResizeWindow = NSLayoutPriorityDragThatCanResizeWindow;
static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 501;
static const MASLayoutPriority MASLayoutPriorityWindowSizeStayPut = NSLayoutPriorityWindowSizeStayPut;
static const MASLayoutPriority MASLayoutPriorityDragThatCannotResizeWindow = NSLayoutPriorityDragThatCannotResizeWindow;
static const MASLayoutPriority MASLayoutPriorityDefaultLow = NSLayoutPriorityDefaultLow;
static const MASLayoutPriority MASLayoutPriorityFittingSizeCompression = NSLayoutPriorityFittingSizeCompression;
#endif
/**
* Allows you to attach keys to objects matching the variable names passed.
*
* view1.mas_key = @"view1", view2.mas_key = @"view2";
*
* is equivalent to:
*
* MASAttachKeys(view1, view2);
*/
#define MASAttachKeys(...) \
{ \
NSDictionary *keyPairs = NSDictionaryOfVariableBindings(__VA_ARGS__); \
for (id key in keyPairs.allKeys) { \
id obj = keyPairs[key]; \
NSAssert([obj respondsToSelector:@selector(setMas_key:)], \
@"Cannot attach mas_key to %@", obj); \
[obj setMas_key:key]; \
} \
}
/**
* Used to create object hashes
* Based on http://www.mikeash.com/pyblog/friday-qa-2010-06-18-implementing-equality-and-hashing.html
*/
#define MAS_NSUINT_BIT (CHAR_BIT * sizeof(NSUInteger))
#define MAS_NSUINTROTATE(val, howmuch) ((((NSUInteger)val) << howmuch) | (((NSUInteger)val) >> (MAS_NSUINT_BIT - howmuch)))
/**
* Given a scalar or struct value, wraps it in NSValue
* Based on EXPObjectify: https://github.com/specta/expecta
*/
static inline id _MASBoxValue(const char *type, ...) {
va_list v;
va_start(v, type);
id obj = nil;
if (strcmp(type, @encode(id)) == 0) {
id actual = va_arg(v, id);
obj = actual;
} else if (strcmp(type, @encode(CGPoint)) == 0) {
CGPoint actual = (CGPoint)va_arg(v, CGPoint);
obj = [NSValue value:&actual withObjCType:type];
} else if (strcmp(type, @encode(CGSize)) == 0) {
CGSize actual = (CGSize)va_arg(v, CGSize);
obj = [NSValue value:&actual withObjCType:type];
} else if (strcmp(type, @encode(MASEdgeInsets)) == 0) {
MASEdgeInsets actual = (MASEdgeInsets)va_arg(v, MASEdgeInsets);
obj = [NSValue value:&actual withObjCType:type];
} else if (strcmp(type, @encode(double)) == 0) {
double actual = (double)va_arg(v, double);
obj = [NSNumber numberWithDouble:actual];
} else if (strcmp(type, @encode(float)) == 0) {
float actual = (float)va_arg(v, double);
obj = [NSNumber numberWithFloat:actual];
} else if (strcmp(type, @encode(int)) == 0) {
int actual = (int)va_arg(v, int);
obj = [NSNumber numberWithInt:actual];
} else if (strcmp(type, @encode(long)) == 0) {
long actual = (long)va_arg(v, long);
obj = [NSNumber numberWithLong:actual];
} else if (strcmp(type, @encode(long long)) == 0) {
long long actual = (long long)va_arg(v, long long);
obj = [NSNumber numberWithLongLong:actual];
} else if (strcmp(type, @encode(short)) == 0) {
short actual = (short)va_arg(v, int);
obj = [NSNumber numberWithShort:actual];
} else if (strcmp(type, @encode(char)) == 0) {
char actual = (char)va_arg(v, int);
obj = [NSNumber numberWithChar:actual];
} else if (strcmp(type, @encode(bool)) == 0) {
bool actual = (bool)va_arg(v, int);
obj = [NSNumber numberWithBool:actual];
} else if (strcmp(type, @encode(unsigned char)) == 0) {
unsigned char actual = (unsigned char)va_arg(v, unsigned int);
obj = [NSNumber numberWithUnsignedChar:actual];
} else if (strcmp(type, @encode(unsigned int)) == 0) {
unsigned int actual = (unsigned int)va_arg(v, unsigned int);
obj = [NSNumber numberWithUnsignedInt:actual];
} else if (strcmp(type, @encode(unsigned long)) == 0) {
unsigned long actual = (unsigned long)va_arg(v, unsigned long);
obj = [NSNumber numberWithUnsignedLong:actual];
} else if (strcmp(type, @encode(unsigned long long)) == 0) {
unsigned long long actual = (unsigned long long)va_arg(v, unsigned long long);
obj = [NSNumber numberWithUnsignedLongLong:actual];
} else if (strcmp(type, @encode(unsigned short)) == 0) {
unsigned short actual = (unsigned short)va_arg(v, unsigned int);
obj = [NSNumber numberWithUnsignedShort:actual];
}
va_end(v);
return obj;
}
#define MASBoxValue(value) _MASBoxValue(@encode(__typeof__((value))), (value))
//
// MASAttribute.h
// Masonry
//
// Created by Jonas Budelmann on 21/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASUtilities.h"
/**
* An immutable tuple which stores the view and the related NSLayoutAttribute.
* Describes part of either the left or right hand side of a constraint equation
*/
@interface MASViewAttribute : NSObject
/**
* The view which the reciever relates to. Can be nil if item is not a view.
*/
@property (nonatomic, weak, readonly) MAS_VIEW *view;
/**
* The item which the reciever relates to.
*/
@property (nonatomic, weak, readonly) id item;
/**
* The attribute which the reciever relates to
*/
@property (nonatomic, assign, readonly) NSLayoutAttribute layoutAttribute;
/**
* Convenience initializer.
*/
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute;
/**
* The designated initializer.
*/
- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute;
/**
* Determine whether the layoutAttribute is a size attribute
*
* @return YES if layoutAttribute is equal to NSLayoutAttributeWidth or NSLayoutAttributeHeight
*/
- (BOOL)isSizeAttribute;
@end
//
// MASAttribute.m
// Masonry
//
// Created by Jonas Budelmann on 21/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASViewAttribute.h"
@implementation MASViewAttribute
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute {
self = [self initWithView:view item:view layoutAttribute:layoutAttribute];
return self;
}
- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute {
self = [super init];
if (!self) return nil;
_view = view;
_item = item;
_layoutAttribute = layoutAttribute;
return self;
}
- (BOOL)isSizeAttribute {
return self.layoutAttribute == NSLayoutAttributeWidth
|| self.layoutAttribute == NSLayoutAttributeHeight;
}
- (BOOL)isEqual:(MASViewAttribute *)viewAttribute {
if ([viewAttribute isKindOfClass:self.class]) {
return self.view == viewAttribute.view
&& self.layoutAttribute == viewAttribute.layoutAttribute;
}
return [super isEqual:viewAttribute];
}
- (NSUInteger)hash {
return MAS_NSUINTROTATE([self.view hash], MAS_NSUINT_BIT / 2) ^ self.layoutAttribute;
}
@end
//
// MASConstraint.h
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASViewAttribute.h"
#import "MASConstraint.h"
#import "MASLayoutConstraint.h"
#import "MASUtilities.h"
/**
* A single constraint.
* Contains the attributes neccessary for creating a NSLayoutConstraint and adding it to the appropriate view
*/
@interface MASViewConstraint : MASConstraint <NSCopying>
/**
* First item/view and first attribute of the NSLayoutConstraint
*/
@property (nonatomic, strong, readonly) MASViewAttribute *firstViewAttribute;
/**
* Second item/view and second attribute of the NSLayoutConstraint
*/
@property (nonatomic, strong, readonly) MASViewAttribute *secondViewAttribute;
/**
* initialises the MASViewConstraint with the first part of the equation
*
* @param firstViewAttribute view.mas_left, view.mas_width etc.
*
* @return a new view constraint
*/
- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute;
/**
* Returns all MASViewConstraints installed with this view as a first item.
*
* @param view A view to retrieve constraints for.
*
* @return An array of MASViewConstraints.
*/
+ (NSArray *)installedConstraintsForView:(MAS_VIEW *)view;
@end
//
// MASConstraint.m
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASViewConstraint.h"
#import "MASConstraint+Private.h"
#import "MASCompositeConstraint.h"
#import "MASLayoutConstraint.h"
#import "View+MASAdditions.h"
#import <objc/runtime.h>
@interface MAS_VIEW (MASConstraints)
@property (nonatomic, readonly) NSMutableSet *mas_installedConstraints;
@end
@implementation MAS_VIEW (MASConstraints)
static char kInstalledConstraintsKey;
- (NSMutableSet *)mas_installedConstraints {
NSMutableSet *constraints = objc_getAssociatedObject(self, &kInstalledConstraintsKey);
if (!constraints) {
constraints = [NSMutableSet set];
objc_setAssociatedObject(self, &kInstalledConstraintsKey, constraints, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return constraints;
}
@end
@interface MASViewConstraint ()
@property (nonatomic, strong, readwrite) MASViewAttribute *secondViewAttribute;
@property (nonatomic, weak) MAS_VIEW *installedView;
@property (nonatomic, weak) MASLayoutConstraint *layoutConstraint;
@property (nonatomic, assign) NSLayoutRelation layoutRelation;
@property (nonatomic, assign) MASLayoutPriority layoutPriority;
@property (nonatomic, assign) CGFloat layoutMultiplier;
@property (nonatomic, assign) CGFloat layoutConstant;
@property (nonatomic, assign) BOOL hasLayoutRelation;
@property (nonatomic, strong) id mas_key;
@property (nonatomic, assign) BOOL useAnimator;
@end
@implementation MASViewConstraint
- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute {
self = [super init];
if (!self) return nil;
_firstViewAttribute = firstViewAttribute;
self.layoutPriority = MASLayoutPriorityRequired;
self.layoutMultiplier = 1;
return self;
}
#pragma mark - NSCoping
- (id)copyWithZone:(NSZone __unused *)zone {
MASViewConstraint *constraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:self.firstViewAttribute];
constraint.layoutConstant = self.layoutConstant;
constraint.layoutRelation = self.layoutRelation;
constraint.layoutPriority = self.layoutPriority;
constraint.layoutMultiplier = self.layoutMultiplier;
constraint.delegate = self.delegate;
return constraint;
}
#pragma mark - Public
+ (NSArray *)installedConstraintsForView:(MAS_VIEW *)view {
return [view.mas_installedConstraints allObjects];
}
#pragma mark - Private
- (void)setLayoutConstant:(CGFloat)layoutConstant {
_layoutConstant = layoutConstant;
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
if (self.useAnimator) {
[self.layoutConstraint.animator setConstant:layoutConstant];
} else {
self.layoutConstraint.constant = layoutConstant;
}
#else
self.layoutConstraint.constant = layoutConstant;
#endif
}
- (void)setLayoutRelation:(NSLayoutRelation)layoutRelation {
_layoutRelation = layoutRelation;
self.hasLayoutRelation = YES;
}
- (BOOL)supportsActiveProperty {
return [self.layoutConstraint respondsToSelector:@selector(isActive)];
}
- (BOOL)isActive {
BOOL active = YES;
if ([self supportsActiveProperty]) {
active = [self.layoutConstraint isActive];
}
return active;
}
- (BOOL)hasBeenInstalled {
return (self.layoutConstraint != nil) && [self isActive];
}
- (void)setSecondViewAttribute:(id)secondViewAttribute {
if ([secondViewAttribute isKindOfClass:NSValue.class]) {
[self setLayoutConstantWithValue:secondViewAttribute];
} else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
_secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
} else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {
_secondViewAttribute = secondViewAttribute;
} else {
NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);
}
}
#pragma mark - NSLayoutConstraint multiplier proxies
- (MASConstraint * (^)(CGFloat))multipliedBy {
return ^id(CGFloat multiplier) {
NSAssert(!self.hasBeenInstalled,
@"Cannot modify constraint multiplier after it has been installed");
self.layoutMultiplier = multiplier;
return self;
};
}
- (MASConstraint * (^)(CGFloat))dividedBy {
return ^id(CGFloat divider) {
NSAssert(!self.hasBeenInstalled,
@"Cannot modify constraint multiplier after it has been installed");
self.layoutMultiplier = 1.0/divider;
return self;
};
}
#pragma mark - MASLayoutPriority proxy
- (MASConstraint * (^)(MASLayoutPriority))priority {
return ^id(MASLayoutPriority priority) {
NSAssert(!self.hasBeenInstalled,
@"Cannot modify constraint priority after it has been installed");
self.layoutPriority = priority;
return self;
};
}
#pragma mark - NSLayoutRelation proxy
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attribute, NSLayoutRelation relation) {
if ([attribute isKindOfClass:NSArray.class]) {
NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
NSMutableArray *children = NSMutableArray.new;
for (id attr in attribute) {
MASViewConstraint *viewConstraint = [self copy];
viewConstraint.secondViewAttribute = attr;
[children addObject:viewConstraint];
}
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self.delegate;
[self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
} else {
NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
self.layoutRelation = relation;
self.secondViewAttribute = attribute;
return self;
}
};
}
#pragma mark - Semantic properties
- (MASConstraint *)with {
return self;
}
- (MASConstraint *)and {
return self;
}
#pragma mark - attribute chaining
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
#pragma mark - Animator proxy
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
- (MASConstraint *)animator {
self.useAnimator = YES;
return self;
}
#endif
#pragma mark - debug helpers
- (MASConstraint * (^)(id))key {
return ^id(id key) {
self.mas_key = key;
return self;
};
}
#pragma mark - NSLayoutConstraint constant setters
- (void)setInsets:(MASEdgeInsets)insets {
NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
switch (layoutAttribute) {
case NSLayoutAttributeLeft:
case NSLayoutAttributeLeading:
self.layoutConstant = insets.left;
break;
case NSLayoutAttributeTop:
self.layoutConstant = insets.top;
break;
case NSLayoutAttributeBottom:
self.layoutConstant = -insets.bottom;
break;
case NSLayoutAttributeRight:
case NSLayoutAttributeTrailing:
self.layoutConstant = -insets.right;
break;
default:
break;
}
}
- (void)setOffset:(CGFloat)offset {
self.layoutConstant = offset;
}
- (void)setSizeOffset:(CGSize)sizeOffset {
NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
switch (layoutAttribute) {
case NSLayoutAttributeWidth:
self.layoutConstant = sizeOffset.width;
break;
case NSLayoutAttributeHeight:
self.layoutConstant = sizeOffset.height;
break;
default:
break;
}
}
- (void)setCenterOffset:(CGPoint)centerOffset {
NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
switch (layoutAttribute) {
case NSLayoutAttributeCenterX:
self.layoutConstant = centerOffset.x;
break;
case NSLayoutAttributeCenterY:
self.layoutConstant = centerOffset.y;
break;
default:
break;
}
}
#pragma mark - MASConstraint
- (void)activate {
[self install];
}
- (void)deactivate {
[self uninstall];
}
- (void)install {
if (self.hasBeenInstalled) {
return;
}
if ([self supportsActiveProperty] && self.layoutConstraint) {
self.layoutConstraint.active = YES;
[self.firstViewAttribute.view.mas_installedConstraints addObject:self];
return;
}
MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;
// alignment attributes must have a secondViewAttribute
// therefore we assume that is refering to superview
// eg make.left.equalTo(@10)
if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
secondLayoutItem = self.firstViewAttribute.view.superview;
secondLayoutAttribute = firstLayoutAttribute;
}
MASLayoutConstraint *layoutConstraint
= [MASLayoutConstraint constraintWithItem:firstLayoutItem
attribute:firstLayoutAttribute
relatedBy:self.layoutRelation
toItem:secondLayoutItem
attribute:secondLayoutAttribute
multiplier:self.layoutMultiplier
constant:self.layoutConstant];
layoutConstraint.priority = self.layoutPriority;
layoutConstraint.mas_key = self.mas_key;
if (self.secondViewAttribute.view) {
MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
NSAssert(closestCommonSuperview,
@"couldn't find a common superview for %@ and %@",
self.firstViewAttribute.view, self.secondViewAttribute.view);
self.installedView = closestCommonSuperview;
} else if (self.firstViewAttribute.isSizeAttribute) {
self.installedView = self.firstViewAttribute.view;
} else {
self.installedView = self.firstViewAttribute.view.superview;
}
MASLayoutConstraint *existingConstraint = nil;
if (self.updateExisting) {
existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
}
if (existingConstraint) {
// just update the constant
existingConstraint.constant = layoutConstraint.constant;
self.layoutConstraint = existingConstraint;
} else {
[self.installedView addConstraint:layoutConstraint];
self.layoutConstraint = layoutConstraint;
[firstLayoutItem.mas_installedConstraints addObject:self];
}
}
- (MASLayoutConstraint *)layoutConstraintSimilarTo:(MASLayoutConstraint *)layoutConstraint {
// check if any constraints are the same apart from the only mutable property constant
// go through constraints in reverse as we do not want to match auto-resizing or interface builder constraints
// and they are likely to be added first.
for (NSLayoutConstraint *existingConstraint in self.installedView.constraints.reverseObjectEnumerator) {
if (![existingConstraint isKindOfClass:MASLayoutConstraint.class]) continue;
if (existingConstraint.firstItem != layoutConstraint.firstItem) continue;
if (existingConstraint.secondItem != layoutConstraint.secondItem) continue;
if (existingConstraint.firstAttribute != layoutConstraint.firstAttribute) continue;
if (existingConstraint.secondAttribute != layoutConstraint.secondAttribute) continue;
if (existingConstraint.relation != layoutConstraint.relation) continue;
if (existingConstraint.multiplier != layoutConstraint.multiplier) continue;
if (existingConstraint.priority != layoutConstraint.priority) continue;
return (id)existingConstraint;
}
return nil;
}
- (void)uninstall {
if ([self supportsActiveProperty]) {
self.layoutConstraint.active = NO;
[self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
return;
}
[self.installedView removeConstraint:self.layoutConstraint];
self.layoutConstraint = nil;
self.installedView = nil;
[self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
}
@end
//
// Masonry.h
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import <Foundation/Foundation.h>
//! Project version number for Masonry.
FOUNDATION_EXPORT double MasonryVersionNumber;
//! Project version string for Masonry.
FOUNDATION_EXPORT const unsigned char MasonryVersionString[];
#import "MASUtilities.h"
#import "View+MASAdditions.h"
#import "View+MASShorthandAdditions.h"
#import "ViewController+MASAdditions.h"
#import "NSArray+MASAdditions.h"
#import "NSArray+MASShorthandAdditions.h"
#import "MASConstraint.h"
#import "MASCompositeConstraint.h"
#import "MASViewAttribute.h"
#import "MASViewConstraint.h"
#import "MASConstraintMaker.h"
#import "MASLayoutConstraint.h"
#import "NSLayoutConstraint+MASDebugAdditions.h"
//
// NSArray+MASAdditions.h
//
//
// Created by Daniel Hammond on 11/26/13.
//
//
#import "MASUtilities.h"
#import "MASConstraintMaker.h"
#import "MASViewAttribute.h"
typedef NS_ENUM(NSUInteger, MASAxisType) {
MASAxisTypeHorizontal,
MASAxisTypeVertical
};
@interface NSArray (MASAdditions)
/**
* Creates a MASConstraintMaker with each view in the callee.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing on each view
*
* @param block scope within which you can build up the constraints which you wish to apply to each view.
*
* @return Array of created MASConstraints
*/
- (NSArray *)mas_makeConstraints:(void (^)(MASConstraintMaker *make))block;
/**
* Creates a MASConstraintMaker with each view in the callee.
* Any constraints defined are added to each view or the appropriate superview once the block has finished executing on each view.
* If an existing constraint exists then it will be updated instead.
*
* @param block scope within which you can build up the constraints which you wish to apply to each view.
*
* @return Array of created/updated MASConstraints
*/
- (NSArray *)mas_updateConstraints:(void (^)(MASConstraintMaker *make))block;
/**
* Creates a MASConstraintMaker with each view in the callee.
* Any constraints defined are added to each view or the appropriate superview once the block has finished executing on each view.
* All constraints previously installed for the views will be removed.
*
* @param block scope within which you can build up the constraints which you wish to apply to each view.
*
* @return Array of created/updated MASConstraints
*/
- (NSArray *)mas_remakeConstraints:(void (^)(MASConstraintMaker *make))block;
/**
* distribute with fixed spacing
*
* @param axisType which axis to distribute items along
* @param fixedSpacing the spacing between each item
* @param leadSpacing the spacing before the first item and the container
* @param tailSpacing the spacing after the last item and the container
*/
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
/**
* distribute with fixed item size
*
* @param axisType which axis to distribute items along
* @param fixedItemLength the fixed length of each item
* @param leadSpacing the spacing before the first item and the container
* @param tailSpacing the spacing after the last item and the container
*/
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedItemLength:(CGFloat)fixedItemLength leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
@end
//
// NSArray+MASAdditions.m
//
//
// Created by Daniel Hammond on 11/26/13.
//
//
#import "NSArray+MASAdditions.h"
#import "View+MASAdditions.h"
@implementation NSArray (MASAdditions)
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block {
NSMutableArray *constraints = [NSMutableArray array];
for (MAS_VIEW *view in self) {
NSAssert([view isKindOfClass:[MAS_VIEW class]], @"All objects in the array must be views");
[constraints addObjectsFromArray:[view mas_makeConstraints:block]];
}
return constraints;
}
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block {
NSMutableArray *constraints = [NSMutableArray array];
for (MAS_VIEW *view in self) {
NSAssert([view isKindOfClass:[MAS_VIEW class]], @"All objects in the array must be views");
[constraints addObjectsFromArray:[view mas_updateConstraints:block]];
}
return constraints;
}
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block {
NSMutableArray *constraints = [NSMutableArray array];
for (MAS_VIEW *view in self) {
NSAssert([view isKindOfClass:[MAS_VIEW class]], @"All objects in the array must be views");
[constraints addObjectsFromArray:[view mas_remakeConstraints:block]];
}
return constraints;
}
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing {
if (self.count < 2) {
NSAssert(self.count>1,@"views to distribute need to bigger than one");
return;
}
MAS_VIEW *tempSuperView = [self mas_commonSuperviewOfViews];
if (axisType == MASAxisTypeHorizontal) {
MAS_VIEW *prev;
for (int i = 0; i < self.count; i++) {
MAS_VIEW *v = self[i];
[v mas_makeConstraints:^(MASConstraintMaker *make) {
if (prev) {
make.width.equalTo(prev);
make.left.equalTo(prev.mas_right).offset(fixedSpacing);
if (i == self.count - 1) {//last one
make.right.equalTo(tempSuperView).offset(-tailSpacing);
}
}
else {//first one
make.left.equalTo(tempSuperView).offset(leadSpacing);
}
}];
prev = v;
}
}
else {
MAS_VIEW *prev;
for (int i = 0; i < self.count; i++) {
MAS_VIEW *v = self[i];
[v mas_makeConstraints:^(MASConstraintMaker *make) {
if (prev) {
make.height.equalTo(prev);
make.top.equalTo(prev.mas_bottom).offset(fixedSpacing);
if (i == self.count - 1) {//last one
make.bottom.equalTo(tempSuperView).offset(-tailSpacing);
}
}
else {//first one
make.top.equalTo(tempSuperView).offset(leadSpacing);
}
}];
prev = v;
}
}
}
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedItemLength:(CGFloat)fixedItemLength leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing {
if (self.count < 2) {
NSAssert(self.count>1,@"views to distribute need to bigger than one");
return;
}
MAS_VIEW *tempSuperView = [self mas_commonSuperviewOfViews];
if (axisType == MASAxisTypeHorizontal) {
MAS_VIEW *prev;
for (int i = 0; i < self.count; i++) {
MAS_VIEW *v = self[i];
[v mas_makeConstraints:^(MASConstraintMaker *make) {
if (prev) {
CGFloat offset = (1-(i/((CGFloat)self.count-1)))*(fixedItemLength+leadSpacing)-i*tailSpacing/(((CGFloat)self.count-1));
make.width.equalTo(@(fixedItemLength));
if (i == self.count - 1) {//last one
make.right.equalTo(tempSuperView).offset(-tailSpacing);
}
else {
make.right.equalTo(tempSuperView).multipliedBy(i/((CGFloat)self.count-1)).with.offset(offset);
}
}
else {//first one
make.left.equalTo(tempSuperView).offset(leadSpacing);
make.width.equalTo(@(fixedItemLength));
}
}];
prev = v;
}
}
else {
MAS_VIEW *prev;
for (int i = 0; i < self.count; i++) {
MAS_VIEW *v = self[i];
[v mas_makeConstraints:^(MASConstraintMaker *make) {
if (prev) {
CGFloat offset = (1-(i/((CGFloat)self.count-1)))*(fixedItemLength+leadSpacing)-i*tailSpacing/(((CGFloat)self.count-1));
make.height.equalTo(@(fixedItemLength));
if (i == self.count - 1) {//last one
make.bottom.equalTo(tempSuperView).offset(-tailSpacing);
}
else {
make.bottom.equalTo(tempSuperView).multipliedBy(i/((CGFloat)self.count-1)).with.offset(offset);
}
}
else {//first one
make.top.equalTo(tempSuperView).offset(leadSpacing);
make.height.equalTo(@(fixedItemLength));
}
}];
prev = v;
}
}
}
- (MAS_VIEW *)mas_commonSuperviewOfViews
{
MAS_VIEW *commonSuperview = nil;
MAS_VIEW *previousView = nil;
for (id object in self) {
if ([object isKindOfClass:[MAS_VIEW class]]) {
MAS_VIEW *view = (MAS_VIEW *)object;
if (previousView) {
commonSuperview = [view mas_closestCommonSuperview:commonSuperview];
} else {
commonSuperview = view;
}
previousView = view;
}
}
NSAssert(commonSuperview, @"Can't constrain views that do not share a common superview. Make sure that all the views in this array have been added into the same view hierarchy.");
return commonSuperview;
}
@end
//
// NSArray+MASShorthandAdditions.h
// Masonry
//
// Created by Jonas Budelmann on 22/07/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import "NSArray+MASAdditions.h"
#ifdef MAS_SHORTHAND
/**
* Shorthand array additions without the 'mas_' prefixes,
* only enabled if MAS_SHORTHAND is defined
*/
@interface NSArray (MASShorthandAdditions)
- (NSArray *)makeConstraints:(void(^)(MASConstraintMaker *make))block;
- (NSArray *)updateConstraints:(void(^)(MASConstraintMaker *make))block;
- (NSArray *)remakeConstraints:(void(^)(MASConstraintMaker *make))block;
@end
@implementation NSArray (MASShorthandAdditions)
- (NSArray *)makeConstraints:(void(^)(MASConstraintMaker *))block {
return [self mas_makeConstraints:block];
}
- (NSArray *)updateConstraints:(void(^)(MASConstraintMaker *))block {
return [self mas_updateConstraints:block];
}
- (NSArray *)remakeConstraints:(void(^)(MASConstraintMaker *))block {
return [self mas_remakeConstraints:block];
}
@end
#endif
//
// NSLayoutConstraint+MASDebugAdditions.h
// Masonry
//
// Created by Jonas Budelmann on 3/08/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import "MASUtilities.h"
/**
* makes debug and log output of NSLayoutConstraints more readable
*/
@interface NSLayoutConstraint (MASDebugAdditions)
@end
//
// NSLayoutConstraint+MASDebugAdditions.m
// Masonry
//
// Created by Jonas Budelmann on 3/08/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import "NSLayoutConstraint+MASDebugAdditions.h"
#import "MASConstraint.h"
#import "MASLayoutConstraint.h"
@implementation NSLayoutConstraint (MASDebugAdditions)
#pragma mark - description maps
+ (NSDictionary *)layoutRelationDescriptionsByValue {
static dispatch_once_t once;
static NSDictionary *descriptionMap;
dispatch_once(&once, ^{
descriptionMap = @{
@(NSLayoutRelationEqual) : @"==",
@(NSLayoutRelationGreaterThanOrEqual) : @">=",
@(NSLayoutRelationLessThanOrEqual) : @"<=",
};
});
return descriptionMap;
}
+ (NSDictionary *)layoutAttributeDescriptionsByValue {
static dispatch_once_t once;
static NSDictionary *descriptionMap;
dispatch_once(&once, ^{
descriptionMap = @{
@(NSLayoutAttributeTop) : @"top",
@(NSLayoutAttributeLeft) : @"left",
@(NSLayoutAttributeBottom) : @"bottom",
@(NSLayoutAttributeRight) : @"right",
@(NSLayoutAttributeLeading) : @"leading",
@(NSLayoutAttributeTrailing) : @"trailing",
@(NSLayoutAttributeWidth) : @"width",
@(NSLayoutAttributeHeight) : @"height",
@(NSLayoutAttributeCenterX) : @"centerX",
@(NSLayoutAttributeCenterY) : @"centerY",
@(NSLayoutAttributeBaseline) : @"baseline",
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
@(NSLayoutAttributeFirstBaseline) : @"firstBaseline",
@(NSLayoutAttributeLastBaseline) : @"lastBaseline",
#endif
#if TARGET_OS_IPHONE || TARGET_OS_TV
@(NSLayoutAttributeLeftMargin) : @"leftMargin",
@(NSLayoutAttributeRightMargin) : @"rightMargin",
@(NSLayoutAttributeTopMargin) : @"topMargin",
@(NSLayoutAttributeBottomMargin) : @"bottomMargin",
@(NSLayoutAttributeLeadingMargin) : @"leadingMargin",
@(NSLayoutAttributeTrailingMargin) : @"trailingMargin",
@(NSLayoutAttributeCenterXWithinMargins) : @"centerXWithinMargins",
@(NSLayoutAttributeCenterYWithinMargins) : @"centerYWithinMargins",
#endif
};
});
return descriptionMap;
}
+ (NSDictionary *)layoutPriorityDescriptionsByValue {
static dispatch_once_t once;
static NSDictionary *descriptionMap;
dispatch_once(&once, ^{
#if TARGET_OS_IPHONE || TARGET_OS_TV
descriptionMap = @{
@(MASLayoutPriorityDefaultHigh) : @"high",
@(MASLayoutPriorityDefaultLow) : @"low",
@(MASLayoutPriorityDefaultMedium) : @"medium",
@(MASLayoutPriorityRequired) : @"required",
@(MASLayoutPriorityFittingSizeLevel) : @"fitting size",
};
#elif TARGET_OS_MAC
descriptionMap = @{
@(MASLayoutPriorityDefaultHigh) : @"high",
@(MASLayoutPriorityDragThatCanResizeWindow) : @"drag can resize window",
@(MASLayoutPriorityDefaultMedium) : @"medium",
@(MASLayoutPriorityWindowSizeStayPut) : @"window size stay put",
@(MASLayoutPriorityDragThatCannotResizeWindow) : @"drag cannot resize window",
@(MASLayoutPriorityDefaultLow) : @"low",
@(MASLayoutPriorityFittingSizeCompression) : @"fitting size",
@(MASLayoutPriorityRequired) : @"required",
};
#endif
});
return descriptionMap;
}
#pragma mark - description override
+ (NSString *)descriptionForObject:(id)obj {
if ([obj respondsToSelector:@selector(mas_key)] && [obj mas_key]) {
return [NSString stringWithFormat:@"%@:%@", [obj class], [obj mas_key]];
}
return [NSString stringWithFormat:@"%@:%p", [obj class], obj];
}
- (NSString *)description {
NSMutableString *description = [[NSMutableString alloc] initWithString:@"<"];
[description appendString:[self.class descriptionForObject:self]];
[description appendFormat:@" %@", [self.class descriptionForObject:self.firstItem]];
if (self.firstAttribute != NSLayoutAttributeNotAnAttribute) {
[description appendFormat:@".%@", self.class.layoutAttributeDescriptionsByValue[@(self.firstAttribute)]];
}
[description appendFormat:@" %@", self.class.layoutRelationDescriptionsByValue[@(self.relation)]];
if (self.secondItem) {
[description appendFormat:@" %@", [self.class descriptionForObject:self.secondItem]];
}
if (self.secondAttribute != NSLayoutAttributeNotAnAttribute) {
[description appendFormat:@".%@", self.class.layoutAttributeDescriptionsByValue[@(self.secondAttribute)]];
}
if (self.multiplier != 1) {
[description appendFormat:@" * %g", self.multiplier];
}
if (self.secondAttribute == NSLayoutAttributeNotAnAttribute) {
[description appendFormat:@" %g", self.constant];
} else {
if (self.constant) {
[description appendFormat:@" %@ %g", (self.constant < 0 ? @"-" : @"+"), ABS(self.constant)];
}
}
if (self.priority != MASLayoutPriorityRequired) {
[description appendFormat:@" ^%@", self.class.layoutPriorityDescriptionsByValue[@(self.priority)] ?: [NSNumber numberWithDouble:self.priority]];
}
[description appendString:@">"];
return description;
}
@end
//
// UIView+MASAdditions.h
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "MASUtilities.h"
#import "MASConstraintMaker.h"
#import "MASViewAttribute.h"
/**
* Provides constraint maker block
* and convience methods for creating MASViewAttribute which are view + NSLayoutAttribute pairs
*/
@interface MAS_VIEW (MASAdditions)
/**
* following properties return a new MASViewAttribute with current view and appropriate NSLayoutAttribute
*/
@property (nonatomic, strong, readonly) MASViewAttribute *mas_left;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_top;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_right;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottom;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leading;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_trailing;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_width;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_height;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerX;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerY;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_baseline;
@property (nonatomic, strong, readonly) MASViewAttribute *(^mas_attribute)(NSLayoutAttribute attr);
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
@property (nonatomic, strong, readonly) MASViewAttribute *mas_firstBaseline;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_lastBaseline;
#endif
#if TARGET_OS_IPHONE || TARGET_OS_TV
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leftMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_rightMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_topMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottomMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leadingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_trailingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerXWithinMargins;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerYWithinMargins;
#endif
/**
* a key to associate with this view
*/
@property (nonatomic, strong) id mas_key;
/**
* Finds the closest common superview between this view and another view
*
* @param view other view
*
* @return returns nil if common superview could not be found
*/
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view;
/**
* Creates a MASConstraintMaker with the callee view.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing
*
* @param block scope within which you can build up the constraints which you wish to apply to the view.
*
* @return Array of created MASConstraints
*/
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;
/**
* Creates a MASConstraintMaker with the callee view.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing.
* If an existing constraint exists then it will be updated instead.
*
* @param block scope within which you can build up the constraints which you wish to apply to the view.
*
* @return Array of created/updated MASConstraints
*/
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block;
/**
* Creates a MASConstraintMaker with the callee view.
* Any constraints defined are added to the view or the appropriate superview once the block has finished executing.
* All constraints previously installed for the view will be removed.
*
* @param block scope within which you can build up the constraints which you wish to apply to the view.
*
* @return Array of created/updated MASConstraints
*/
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
@end
//
// UIView+MASAdditions.m
// Masonry
//
// Created by Jonas Budelmann on 20/07/13.
// Copyright (c) 2013 cloudling. All rights reserved.
//
#import "View+MASAdditions.h"
#import <objc/runtime.h>
@implementation MAS_VIEW (MASAdditions)
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
constraintMaker.updateExisting = YES;
block(constraintMaker);
return [constraintMaker install];
}
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
constraintMaker.removeExisting = YES;
block(constraintMaker);
return [constraintMaker install];
}
#pragma mark - NSLayoutAttribute properties
- (MASViewAttribute *)mas_left {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLeft];
}
- (MASViewAttribute *)mas_top {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeTop];
}
- (MASViewAttribute *)mas_right {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeRight];
}
- (MASViewAttribute *)mas_bottom {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeBottom];
}
- (MASViewAttribute *)mas_leading {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLeading];
}
- (MASViewAttribute *)mas_trailing {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeTrailing];
}
- (MASViewAttribute *)mas_width {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeWidth];
}
- (MASViewAttribute *)mas_height {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeHeight];
}
- (MASViewAttribute *)mas_centerX {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeCenterX];
}
- (MASViewAttribute *)mas_centerY {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeCenterY];
}
- (MASViewAttribute *)mas_baseline {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeBaseline];
}
- (MASViewAttribute *(^)(NSLayoutAttribute))mas_attribute
{
return ^(NSLayoutAttribute attr) {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:attr];
};
}
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
- (MASViewAttribute *)mas_firstBaseline {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeFirstBaseline];
}
- (MASViewAttribute *)mas_lastBaseline {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLastBaseline];
}
#endif
#if TARGET_OS_IPHONE || TARGET_OS_TV
- (MASViewAttribute *)mas_leftMargin {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLeftMargin];
}
- (MASViewAttribute *)mas_rightMargin {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeRightMargin];
}
- (MASViewAttribute *)mas_topMargin {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeTopMargin];
}
- (MASViewAttribute *)mas_bottomMargin {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeBottomMargin];
}
- (MASViewAttribute *)mas_leadingMargin {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLeadingMargin];
}
- (MASViewAttribute *)mas_trailingMargin {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeTrailingMargin];
}
- (MASViewAttribute *)mas_centerXWithinMargins {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeCenterXWithinMargins];
}
- (MASViewAttribute *)mas_centerYWithinMargins {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeCenterYWithinMargins];
}
#endif
#pragma mark - associated properties
- (id)mas_key {
return objc_getAssociatedObject(self, @selector(mas_key));
}
- (void)setMas_key:(id)key {
objc_setAssociatedObject(self, @selector(mas_key), key, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark - heirachy
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view {
MAS_VIEW *closestCommonSuperview = nil;
MAS_VIEW *secondViewSuperview = view;
while (!closestCommonSuperview && secondViewSuperview) {
MAS_VIEW *firstViewSuperview = self;
while (!closestCommonSuperview && firstViewSuperview) {
if (secondViewSuperview == firstViewSuperview) {
closestCommonSuperview = secondViewSuperview;
}
firstViewSuperview = firstViewSuperview.superview;
}
secondViewSuperview = secondViewSuperview.superview;
}
return closestCommonSuperview;
}
@end
//
// UIView+MASShorthandAdditions.h
// Masonry
//
// Created by Jonas Budelmann on 22/07/13.
// Copyright (c) 2013 Jonas Budelmann. All rights reserved.
//
#import "View+MASAdditions.h"
#ifdef MAS_SHORTHAND
/**
* Shorthand view additions without the 'mas_' prefixes,
* only enabled if MAS_SHORTHAND is defined
*/
@interface MAS_VIEW (MASShorthandAdditions)
@property (nonatomic, strong, readonly) MASViewAttribute *left;
@property (nonatomic, strong, readonly) MASViewAttribute *top;
@property (nonatomic, strong, readonly) MASViewAttribute *right;
@property (nonatomic, strong, readonly) MASViewAttribute *bottom;
@property (nonatomic, strong, readonly) MASViewAttribute *leading;
@property (nonatomic, strong, readonly) MASViewAttribute *trailing;
@property (nonatomic, strong, readonly) MASViewAttribute *width;
@property (nonatomic, strong, readonly) MASViewAttribute *height;
@property (nonatomic, strong, readonly) MASViewAttribute *centerX;
@property (nonatomic, strong, readonly) MASViewAttribute *centerY;
@property (nonatomic, strong, readonly) MASViewAttribute *baseline;
@property (nonatomic, strong, readonly) MASViewAttribute *(^attribute)(NSLayoutAttribute attr);
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
@property (nonatomic, strong, readonly) MASViewAttribute *firstBaseline;
@property (nonatomic, strong, readonly) MASViewAttribute *lastBaseline;
#endif
#if TARGET_OS_IPHONE || TARGET_OS_TV
@property (nonatomic, strong, readonly) MASViewAttribute *leftMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *rightMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *topMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *bottomMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *leadingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *trailingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *centerXWithinMargins;
@property (nonatomic, strong, readonly) MASViewAttribute *centerYWithinMargins;
#endif
- (NSArray *)makeConstraints:(void(^)(MASConstraintMaker *make))block;
- (NSArray *)updateConstraints:(void(^)(MASConstraintMaker *make))block;
- (NSArray *)remakeConstraints:(void(^)(MASConstraintMaker *make))block;
@end
#define MAS_ATTR_FORWARD(attr) \
- (MASViewAttribute *)attr { \
return [self mas_##attr]; \
}
@implementation MAS_VIEW (MASShorthandAdditions)
MAS_ATTR_FORWARD(top);
MAS_ATTR_FORWARD(left);
MAS_ATTR_FORWARD(bottom);
MAS_ATTR_FORWARD(right);
MAS_ATTR_FORWARD(leading);
MAS_ATTR_FORWARD(trailing);
MAS_ATTR_FORWARD(width);
MAS_ATTR_FORWARD(height);
MAS_ATTR_FORWARD(centerX);
MAS_ATTR_FORWARD(centerY);
MAS_ATTR_FORWARD(baseline);
#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
MAS_ATTR_FORWARD(firstBaseline);
MAS_ATTR_FORWARD(lastBaseline);
#endif
#if TARGET_OS_IPHONE || TARGET_OS_TV
MAS_ATTR_FORWARD(leftMargin);
MAS_ATTR_FORWARD(rightMargin);
MAS_ATTR_FORWARD(topMargin);
MAS_ATTR_FORWARD(bottomMargin);
MAS_ATTR_FORWARD(leadingMargin);
MAS_ATTR_FORWARD(trailingMargin);
MAS_ATTR_FORWARD(centerXWithinMargins);
MAS_ATTR_FORWARD(centerYWithinMargins);
#endif
- (MASViewAttribute *(^)(NSLayoutAttribute))attribute {
return [self mas_attribute];
}
- (NSArray *)makeConstraints:(void(^)(MASConstraintMaker *))block {
return [self mas_makeConstraints:block];
}
- (NSArray *)updateConstraints:(void(^)(MASConstraintMaker *))block {
return [self mas_updateConstraints:block];
}
- (NSArray *)remakeConstraints:(void(^)(MASConstraintMaker *))block {
return [self mas_remakeConstraints:block];
}
@end
#endif
//
// UIViewController+MASAdditions.h
// Masonry
//
// Created by Craig Siemens on 2015-06-23.
//
//
#import "MASUtilities.h"
#import "MASConstraintMaker.h"
#import "MASViewAttribute.h"
#ifdef MAS_VIEW_CONTROLLER
@interface MAS_VIEW_CONTROLLER (MASAdditions)
/**
* following properties return a new MASViewAttribute with appropriate UILayoutGuide and NSLayoutAttribute
*/
@property (nonatomic, strong, readonly) MASViewAttribute *mas_topLayoutGuide;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottomLayoutGuide;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_topLayoutGuideTop;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_topLayoutGuideBottom;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottomLayoutGuideTop;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottomLayoutGuideBottom;
@end
#endif
//
// UIViewController+MASAdditions.m
// Masonry
//
// Created by Craig Siemens on 2015-06-23.
//
//
#import "ViewController+MASAdditions.h"
#ifdef MAS_VIEW_CONTROLLER
@implementation MAS_VIEW_CONTROLLER (MASAdditions)
- (MASViewAttribute *)mas_topLayoutGuide {
return [[MASViewAttribute alloc] initWithView:self.view item:self.topLayoutGuide layoutAttribute:NSLayoutAttributeBottom];
}
- (MASViewAttribute *)mas_topLayoutGuideTop {
return [[MASViewAttribute alloc] initWithView:self.view item:self.topLayoutGuide layoutAttribute:NSLayoutAttributeTop];
}
- (MASViewAttribute *)mas_topLayoutGuideBottom {
return [[MASViewAttribute alloc] initWithView:self.view item:self.topLayoutGuide layoutAttribute:NSLayoutAttributeBottom];
}
- (MASViewAttribute *)mas_bottomLayoutGuide {
return [[MASViewAttribute alloc] initWithView:self.view item:self.bottomLayoutGuide layoutAttribute:NSLayoutAttributeTop];
}
- (MASViewAttribute *)mas_bottomLayoutGuideTop {
return [[MASViewAttribute alloc] initWithView:self.view item:self.bottomLayoutGuide layoutAttribute:NSLayoutAttributeTop];
}
- (MASViewAttribute *)mas_bottomLayoutGuideBottom {
return [[MASViewAttribute alloc] initWithView:self.view item:self.bottomLayoutGuide layoutAttribute:NSLayoutAttributeBottom];
}
@end
#endif
#Masonry [![Build Status](https://travis-ci.org/SnapKit/Masonry.svg?branch=master)](https://travis-ci.org/SnapKit/Masonry) [![Coverage Status](https://img.shields.io/coveralls/SnapKit/Masonry.svg?style=flat-square)](https://coveralls.io/r/SnapKit/Masonry) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
**Masonry is still actively maintained, we are committed to fixing bugs and merging good quality PRs from the wider community. However if you're using Swift in your project, we recommend using [SnapKit](https://github.com/SnapKit/SnapKit) as it provides better type safety with a simpler API.**
Masonry is a light-weight layout framework which wraps AutoLayout with a nicer syntax. Masonry has its own layout DSL which provides a chainable way of describing your NSLayoutConstraints which results in layout code that is more concise and readable.
Masonry supports iOS and Mac OS X.
For examples take a look at the **Masonry iOS Examples** project in the Masonry workspace. You will need to run `pod install` after downloading.
## What's wrong with NSLayoutConstraints?
Under the hood Auto Layout is a powerful and flexible way of organising and laying out your views. However creating constraints from code is verbose and not very descriptive.
Imagine a simple example in which you want to have a view fill its superview but inset by 10 pixels on every side
```obj-c
UIView *superview = self.view;
UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[superview addConstraints:@[
//view1 constraints
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:padding.left],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeRight
multiplier:1
constant:-padding.right],
]];
```
Even with such a simple example the code needed is quite verbose and quickly becomes unreadable when you have more than 2 or 3 views.
Another option is to use Visual Format Language (VFL), which is a bit less long winded.
However the ASCII type syntax has its own pitfalls and its also a bit harder to animate as `NSLayoutConstraint constraintsWithVisualFormat:` returns an array.
## Prepare to meet your Maker!
Heres the same constraints created using MASConstraintMaker
```obj-c
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
make.left.equalTo(superview.mas_left).with.offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];
```
Or even shorter
```obj-c
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];
```
Also note in the first example we had to add the constraints to the superview `[superview addConstraints:...`.
Masonry however will automagically add constraints to the appropriate view.
Masonry will also call `view1.translatesAutoresizingMaskIntoConstraints = NO;` for you.
## Not all things are created equal
> `.equalTo` equivalent to **NSLayoutRelationEqual**
> `.lessThanOrEqualTo` equivalent to **NSLayoutRelationLessThanOrEqual**
> `.greaterThanOrEqualTo` equivalent to **NSLayoutRelationGreaterThanOrEqual**
These three equality constraints accept one argument which can be any of the following:
#### 1. MASViewAttribute
```obj-c
make.centerX.lessThanOrEqualTo(view2.mas_left);
```
MASViewAttribute | NSLayoutAttribute
------------------------- | --------------------------
view.mas_left | NSLayoutAttributeLeft
view.mas_right | NSLayoutAttributeRight
view.mas_top | NSLayoutAttributeTop
view.mas_bottom | NSLayoutAttributeBottom
view.mas_leading | NSLayoutAttributeLeading
view.mas_trailing | NSLayoutAttributeTrailing
view.mas_width | NSLayoutAttributeWidth
view.mas_height | NSLayoutAttributeHeight
view.mas_centerX | NSLayoutAttributeCenterX
view.mas_centerY | NSLayoutAttributeCenterY
view.mas_baseline | NSLayoutAttributeBaseline
#### 2. UIView/NSView
if you want view.left to be greater than or equal to label.left :
```obj-c
//these two constraints are exactly the same
make.left.greaterThanOrEqualTo(label);
make.left.greaterThanOrEqualTo(label.mas_left);
```
#### 3. NSNumber
Auto Layout allows width and height to be set to constant values.
if you want to set view to have a minimum and maximum width you could pass a number to the equality blocks:
```obj-c
//width >= 200 && width <= 400
make.width.greaterThanOrEqualTo(@200);
make.width.lessThanOrEqualTo(@400)
```
However Auto Layout does not allow alignment attributes such as left, right, centerY etc to be set to constant values.
So if you pass a NSNumber for these attributes Masonry will turn these into constraints relative to the view&rsquo;s superview ie:
```obj-c
//creates view.left = view.superview.left + 10
make.left.lessThanOrEqualTo(@10)
```
Instead of using NSNumber, you can use primitives and structs to build your constraints, like so:
```obj-c
make.top.mas_equalTo(42);
make.height.mas_equalTo(20);
make.size.mas_equalTo(CGSizeMake(50, 100));
make.edges.mas_equalTo(UIEdgeInsetsMake(10, 0, 10, 0));
make.left.mas_equalTo(view).mas_offset(UIEdgeInsetsMake(10, 0, 10, 0));
```
By default, macros which support [autoboxing](https://en.wikipedia.org/wiki/Autoboxing#Autoboxing) are prefixed with `mas_`. Unprefixed versions are available by defining `MAS_SHORTHAND_GLOBALS` before importing Masonry.
#### 4. NSArray
An array of a mixture of any of the previous types
```obj-c
make.height.equalTo(@[view1.mas_height, view2.mas_height]);
make.height.equalTo(@[view1, view2]);
make.left.equalTo(@[view1, @100, view3.right]);
````
## Learn to prioritize
> `.priority` allows you to specify an exact priority
> `.priorityHigh` equivalent to **UILayoutPriorityDefaultHigh**
> `.priorityMedium` is half way between high and low
> `.priorityLow` equivalent to **UILayoutPriorityDefaultLow**
Priorities are can be tacked on to the end of a constraint chain like so:
```obj-c
make.left.greaterThanOrEqualTo(label.mas_left).with.priorityLow();
make.top.equalTo(label.mas_top).with.priority(600);
```
## Composition, composition, composition
Masonry also gives you a few convenience methods which create multiple constraints at the same time. These are called MASCompositeConstraints
#### edges
```obj-c
// make top, left, bottom, right equal view2
make.edges.equalTo(view2);
// make top = superview.top + 5, left = superview.left + 10,
// bottom = superview.bottom - 15, right = superview.right - 20
make.edges.equalTo(superview).insets(UIEdgeInsetsMake(5, 10, 15, 20))
```
#### size
```obj-c
// make width and height greater than or equal to titleLabel
make.size.greaterThanOrEqualTo(titleLabel)
// make width = superview.width + 100, height = superview.height - 50
make.size.equalTo(superview).sizeOffset(CGSizeMake(100, -50))
```
#### center
```obj-c
// make centerX and centerY = button1
make.center.equalTo(button1)
// make centerX = superview.centerX - 5, centerY = superview.centerY + 10
make.center.equalTo(superview).centerOffset(CGPointMake(-5, 10))
```
You can chain view attributes for increased readability:
```obj-c
// All edges but the top should equal those of the superview
make.left.right.and.bottom.equalTo(superview);
make.top.equalTo(otherView);
```
## Hold on for dear life
Sometimes you need modify existing constraints in order to animate or remove/replace constraints.
In Masonry there are a few different approaches to updating constraints.
#### 1. References
You can hold on to a reference of a particular constraint by assigning the result of a constraint make expression to a local variable or a class property.
You could also reference multiple constraints by storing them away in an array.
```obj-c
// in public/private interface
@property (nonatomic, strong) MASConstraint *topConstraint;
...
// when making constraints
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
self.topConstraint = make.top.equalTo(superview.mas_top).with.offset(padding.top);
make.left.equalTo(superview.mas_left).with.offset(padding.left);
}];
...
// then later you can call
[self.topConstraint uninstall];
```
#### 2. mas_updateConstraints
Alternatively if you are only updating the constant value of the constraint you can use the convience method `mas_updateConstraints` instead of `mas_makeConstraints`
```obj-c
// this is Apple's recommended place for adding/updating constraints
// this method can get called multiple times in response to setNeedsUpdateConstraints
// which can be called by UIKit internally or in your code if you need to trigger an update to your constraints
- (void)updateConstraints {
[self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self);
make.width.equalTo(@(self.buttonSize.width)).priorityLow();
make.height.equalTo(@(self.buttonSize.height)).priorityLow();
make.width.lessThanOrEqualTo(self);
make.height.lessThanOrEqualTo(self);
}];
//according to apple super should be called at end of method
[super updateConstraints];
}
```
### 3. mas_remakeConstraints
`mas_updateConstraints` is useful for updating a set of constraints, but doing anything beyond updating constant values can get exhausting. That's where `mas_remakeConstraints` comes in.
`mas_remakeConstraints` is similar to `mas_updateConstraints`, but instead of updating constant values, it will remove all of its constraints before installing them again. This lets you provide different constraints without having to keep around references to ones which you want to remove.
```obj-c
- (void)changeButtonPosition {
[self.button mas_remakeConstraints:^(MASConstraintMaker *make) {
make.size.equalTo(self.buttonSize);
if (topLeft) {
make.top.and.left.offset(10);
} else {
make.bottom.and.right.offset(-10);
}
}];
}
```
You can find more detailed examples of all three approaches in the **Masonry iOS Examples** project.
## When the ^&*!@ hits the fan!
Laying out your views doesn't always goto plan. So when things literally go pear shaped, you don't want to be looking at console output like this:
```obj-c
Unable to simultaneously satisfy constraints.....blah blah blah....
(
"<NSLayoutConstraint:0x7189ac0 V:[UILabel:0x7186980(>=5000)]>",
"<NSAutoresizingMaskLayoutConstraint:0x839ea20 h=--& v=--& V:[MASExampleDebuggingView:0x7186560(416)]>",
"<NSLayoutConstraint:0x7189c70 UILabel:0x7186980.bottom == MASExampleDebuggingView:0x7186560.bottom - 10>",
"<NSLayoutConstraint:0x7189560 V:|-(1)-[UILabel:0x7186980] (Names: '|':MASExampleDebuggingView:0x7186560 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7189ac0 V:[UILabel:0x7186980(>=5000)]>
```
Masonry adds a category to NSLayoutConstraint which overrides the default implementation of `- (NSString *)description`.
Now you can give meaningful names to views and constraints, and also easily pick out the constraints created by Masonry.
which means your console output can now look like this:
```obj-c
Unable to simultaneously satisfy constraints......blah blah blah....
(
"<NSAutoresizingMaskLayoutConstraint:0x8887740 MASExampleDebuggingView:superview.height == 416>",
"<MASLayoutConstraint:ConstantConstraint UILabel:messageLabel.height >= 5000>",
"<MASLayoutConstraint:BottomConstraint UILabel:messageLabel.bottom == MASExampleDebuggingView:superview.bottom - 10>",
"<MASLayoutConstraint:ConflictingConstraint[0] UILabel:messageLabel.top == MASExampleDebuggingView:superview.top + 1>"
)
Will attempt to recover by breaking constraint
<MASLayoutConstraint:ConstantConstraint UILabel:messageLabel.height >= 5000>
```
For an example of how to set this up take a look at the **Masonry iOS Examples** project in the Masonry workspace.
## Where should I create my constraints?
```objc
@implementation DIYCustomView
- (id)init {
self = [super init];
if (!self) return nil;
// --- Create your views here ---
self.button = [[UIButton alloc] init];
return self;
}
// tell UIKit that you are using AutoLayout
+ (BOOL)requiresConstraintBasedLayout {
return YES;
}
// this is Apple's recommended place for adding/updating constraints
- (void)updateConstraints {
// --- remake/update constraints here
[self.button remakeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(@(self.buttonSize.width));
make.height.equalTo(@(self.buttonSize.height));
}];
//according to apple super should be called at end of method
[super updateConstraints];
}
- (void)didTapButton:(UIButton *)button {
// --- Do your changes ie change variables that affect your layout etc ---
self.buttonSize = CGSize(200, 200);
// tell constraints they need updating
[self setNeedsUpdateConstraints];
}
@end
```
## Installation
Use the [orsome](http://www.youtube.com/watch?v=YaIZF8uUTtk) [CocoaPods](http://github.com/CocoaPods/CocoaPods).
In your Podfile
>`pod 'Masonry'`
If you want to use masonry without all those pesky 'mas_' prefixes. Add #define MAS_SHORTHAND to your prefix.pch before importing Masonry
>`#define MAS_SHORTHAND`
Get busy Masoning
>`#import "Masonry.h"`
## Code Snippets
Copy the included code snippets to ``~/Library/Developer/Xcode/UserData/CodeSnippets`` to write your masonry blocks at lightning speed!
`mas_make` -> `[<view> mas_makeConstraints:^(MASConstraintMaker *make){<code>}];`
`mas_update` -> `[<view> mas_updateConstraints:^(MASConstraintMaker *make){<code>}];`
`mas_remake` -> `[<view> mas_remakeConstraints:^(MASConstraintMaker *make){<code>}];`
## Features
* Not limited to subset of Auto Layout. Anything NSLayoutConstraint can do, Masonry can do too!
* Great debug support, give your views and constraints meaningful names.
* Constraints read like sentences.
* No crazy macro magic. Masonry won't pollute the global namespace with macros.
* Not string or dictionary based and hence you get compile time checking.
## TODO
* Eye candy
* Mac example project
* More tests and examples
This source diff could not be displayed because it is too large. You can view the blob instead.
Copyright (c) 2016 Olivier Poitrey rs@dailymotion.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Web Image
=========
[![Build Status](http://img.shields.io/travis/rs/SDWebImage/master.svg?style=flat)](https://travis-ci.org/rs/SDWebImage)
[![Pod Version](http://img.shields.io/cocoapods/v/SDWebImage.svg?style=flat)](http://cocoadocs.org/docsets/SDWebImage/)
[![Pod Platform](http://img.shields.io/cocoapods/p/SDWebImage.svg?style=flat)](http://cocoadocs.org/docsets/SDWebImage/)
[![Pod License](http://img.shields.io/cocoapods/l/SDWebImage.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0.html)
[![Dependency Status](https://www.versioneye.com/objective-c/sdwebimage/3.3/badge.svg?style=flat)](https://www.versioneye.com/objective-c/sdwebimage/3.3)
[![Reference Status](https://www.versioneye.com/objective-c/sdwebimage/reference_badge.svg?style=flat)](https://www.versioneye.com/objective-c/sdwebimage/references)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/rs/SDWebImage)
This library provides a category for UIImageView with support for remote images coming from the web.
It provides:
- An `UIImageView` category adding web image and cache management to the Cocoa Touch framework
- An asynchronous image downloader
- An asynchronous memory + disk image caching with automatic cache expiration handling
- Animated GIF support
- WebP format support
- A background image decompression
- A guarantee that the same URL won't be downloaded several times
- A guarantee that bogus URLs won't be retried again and again
- A guarantee that main thread will never be blocked
- Performances!
- Use GCD and ARC
- Arm64 support
NOTE: The version 3.0 of SDWebImage isn't fully backward compatible with 2.0 and requires iOS 5.1.1
minimum deployment version. If you need iOS < 5.0 support, please use the last [2.0 version](https://github.com/rs/SDWebImage/tree/2.0-compat).
[How is SDWebImage better than X?](https://github.com/rs/SDWebImage/wiki/How-is-SDWebImage-better-than-X%3F)
Who Uses It
----------
Find out [who uses SDWebImage](https://github.com/rs/SDWebImage/wiki/Who-Uses-SDWebImage) and add your app to the list.
How To Use
----------
API documentation is available at [CocoaDocs - SDWebImage](http://cocoadocs.org/docsets/SDWebImage/)
### Using UIImageView+WebCache category with UITableView
Just #import the UIImageView+WebCache.h header, and call the sd_setImageWithURL:placeholderImage:
method from the tableView:cellForRowAtIndexPath: UITableViewDataSource method. Everything will be
handled for you, from async downloads to caching management.
```objective-c
#import <SDWebImage/UIImageView+WebCache.h>
...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = @"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier] autorelease];
}
// Here we use the new provided sd_setImageWithURL: method to load the web image
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
cell.textLabel.text = @"My Text";
return cell;
}
```
### Using blocks
With blocks, you can be notified about the image download progress and whenever the image retrieval
has completed with success or not:
```objective-c
// Here we use the new provided sd_setImageWithURL: method to load the web image
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:@"placeholder.png"]
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
... completion code here ...
}];
```
Note: neither your success nor failure block will be call if your image request is canceled before completion.
### Using SDWebImageManager
The SDWebImageManager is the class behind the UIImageView+WebCache category. It ties the
asynchronous downloader with the image cache store. You can use this class directly to benefit
from web image downloading with caching in another context than a UIView (ie: with Cocoa).
Here is a simple example of how to use SDWebImageManager:
```objective-c
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:imageURL
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
// do something with image
}
}];
```
### Using Asynchronous Image Downloader Independently
It's also possible to use the async image downloader independently:
```objective-c
SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
[downloader downloadImageWithURL:imageURL
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
if (image && finished) {
// do something with image
}
}];
```
### Using Asynchronous Image Caching Independently
It is also possible to use the async based image cache store independently. SDImageCache
maintains a memory cache and an optional disk cache. Disk cache write operations are performed
asynchronous so it doesn't add unnecessary latency to the UI.
The SDImageCache class provides a singleton instance for convenience but you can create your own
instance if you want to create separated cache namespace.
To lookup the cache, you use the `queryDiskCacheForKey:done:` method. If the method returns nil, it means the cache
doesn't currently own the image. You are thus responsible for generating and caching it. The cache
key is an application unique identifier for the image to cache. It is generally the absolute URL of
the image.
```objective-c
SDImageCache *imageCache = [[SDImageCache alloc] initWithNamespace:@"myNamespace"];
[imageCache queryDiskCacheForKey:myCacheKey done:^(UIImage *image) {
// image is not nil if image was found
}];
```
By default SDImageCache will lookup the disk cache if an image can't be found in the memory cache.
You can prevent this from happening by calling the alternative method `imageFromMemoryCacheForKey:`.
To store an image into the cache, you use the storeImage:forKey: method:
```objective-c
[[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey];
```
By default, the image will be stored in memory cache as well as on disk cache (asynchronously). If
you want only the memory cache, use the alternative method storeImage:forKey:toDisk: with a negative
third argument.
### Using cache key filter
Sometime, you may not want to use the image URL as cache key because part of the URL is dynamic
(i.e.: for access control purpose). SDWebImageManager provides a way to set a cache key filter that
takes the NSURL as input, and output a cache key NSString.
The following example sets a filter in the application delegate that will remove any query-string from
the URL before to use it as a cache key:
```objective-c
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL *url) {
url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path];
return [url absoluteString];
};
// Your app init code...
return YES;
}
```
Common Problems
---------------
### Using dynamic image size with UITableViewCell
UITableView determines the size of the image by the first image set for a cell. If your remote images
don't have the same size as your placeholder image, you may experience strange anamorphic scaling issue.
The following article gives a way to workaround this issue:
[http://www.wrichards.com/blog/2011/11/sdwebimage-fixed-width-cell-images/](http://www.wrichards.com/blog/2011/11/sdwebimage-fixed-width-cell-images/)
### Handle image refresh
SDWebImage does very aggressive caching by default. It ignores all kind of caching control header returned by the HTTP server and cache the returned images with no time restriction. It implies your images URLs are static URLs pointing to images that never change. If the pointed image happen to change, some parts of the URL should change accordingly.
If you don't control the image server you're using, you may not be able to change the URL when its content is updated. This is the case for Facebook avatar URLs for instance. In such case, you may use the `SDWebImageRefreshCached` flag. This will slightly degrade the performance but will respect the HTTP caching control headers:
``` objective-c
[imageView sd_setImageWithURL:[NSURL URLWithString:@"https://graph.facebook.com/olivier.poitrey/picture"]
placeholderImage:[UIImage imageNamed:@"avatar-placeholder.png"]
options:SDWebImageRefreshCached];
```
### Add a progress indicator
See this category: https://github.com/JJSaccolo/UIActivityIndicator-for-SDWebImage
Installation
------------
There are three ways to use SDWebImage in your project:
- using CocoaPods
- copying all the files into your project
- importing the project as a static library
### Installation with CocoaPods
[CocoaPods](http://cocoapods.org/) is a dependency manager for Objective-C, which automates and simplifies the process of using 3rd-party libraries in your projects. See the [Get Started](http://cocoapods.org/#get_started) section for more details.
#### Podfile
```
platform :ios, '6.1'
pod 'SDWebImage', '~>3.7'
```
If you are using Swift, be sure to add `use_frameworks!` and set your target to iOS 8+:
```
platform :ios, '8.0'
use_frameworks!
```
#### Subspecs
There are 3 subspecs available now: `Core`, `MapKit` and `WebP` (this means you can install only some of the SDWebImage modules. By default, you get just `Core`, so if you need `WebP`, you need to specify it).
Podfile example:
```
pod 'SDWebImage/WebP'
```
### Installation with Carthage (iOS 8+)
[Carthage](https://github.com/Carthage/Carthage) is a lightweight dependency manager for Swift and Objective-C. It leverages CocoaTouch modules and is less invasive than CocoaPods.
To install with carthage, follow the instruction on [Carthage](https://github.com/Carthage/Carthage)
#### Cartfile
```
github "rs/SDWebImage"
```
#### Usage
Swift
If you installed using CocoaPods:
```
import SDWebImage
```
If you installed manually:
```
import WebImage
```
Objective-C
```
@import WebImage;
```
### Installation by cloning the repository
In order to gain access to all the files from the repository, you should clone it.
```
git clone --recursive https://github.com/rs/SDWebImage.git
```
### Add the SDWebImage project to your project
- Download and unzip the last version of the framework from the [download page](https://github.com/rs/SDWebImage/releases)
- Right-click on the project navigator and select "Add Files to "Your Project":
- In the dialog, select SDWebImage.framework:
- Check the "Copy items into destination group's folder (if needed)" checkbox
### Add dependencies
- In you application project app’s target settings, find the "Build Phases" section and open the "Link Binary With Libraries" block:
- Click the "+" button again and select the "ImageIO.framework", this is needed by the progressive download feature:
### Add Linker Flag
Open the "Build Settings" tab, in the "Linking" section, locate the "Other Linker Flags" setting and add the "-ObjC" flag:
![Other Linker Flags](http://dl.dropbox.com/u/123346/SDWebImage/10_other_linker_flags.jpg)
Alternatively, if this causes compilation problems with frameworks that extend optional libraries, such as Parse, RestKit or opencv2, instead of the -ObjC flag use:
```
-force_load SDWebImage.framework/Versions/Current/SDWebImage
```
If you're using Cocoa Pods and have any frameworks that extend optional libraries, such as Parsen RestKit or opencv2, instead of the -ObjC flag use:
```
-force_load $(TARGET_BUILD_DIR)/libPods.a
```
and this:
```
$(inherited)
```
### Import headers in your source files
In the source files where you need to use the library, import the header file:
```objective-c
#import <SDWebImage/UIImageView+WebCache.h>
```
### Build Project
At this point your workspace should build without error. If you are having problem, post to the Issue and the
community can help you solve it.
Future Enhancements
-------------------
- LRU memory cache cleanup instead of reset on memory warning
## Licenses
All source code is licensed under the [MIT License](https://raw.github.com/rs/SDWebImage/master/LICENSE).
//
// Created by Fabrice Aneche on 06/01/14.
// Copyright (c) 2014 Dailymotion. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSData (ImageContentType)
/**
* Compute the content type for an image data
*
* @param data the input data
*
* @return the content type as string (i.e. image/jpeg, image/gif)
*/
+ (NSString *)sd_contentTypeForImageData:(NSData *)data;
@end
@interface NSData (ImageContentTypeDeprecated)
+ (NSString *)contentTypeForImageData:(NSData *)data __deprecated_msg("Use `sd_contentTypeForImageData:`");
@end
//
// Created by Fabrice Aneche on 06/01/14.
// Copyright (c) 2014 Dailymotion. All rights reserved.
//
#import "NSData+ImageContentType.h"
@implementation NSData (ImageContentType)
+ (NSString *)sd_contentTypeForImageData:(NSData *)data {
uint8_t c;
[data getBytes:&c length:1];
switch (c) {
case 0xFF:
return @"image/jpeg";
case 0x89:
return @"image/png";
case 0x47:
return @"image/gif";
case 0x49:
case 0x4D:
return @"image/tiff";
case 0x52:
// R as RIFF for WEBP
if ([data length] < 12) {
return nil;
}
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
return @"image/webp";
}
return nil;
}
return nil;
}
@end
@implementation NSData (ImageContentTypeDeprecated)
+ (NSString *)contentTypeForImageData:(NSData *)data {
return [self sd_contentTypeForImageData:data];
}
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
typedef NS_ENUM(NSInteger, SDImageCacheType) {
/**
* The image wasn't available the SDWebImage caches, but was downloaded from the web.
*/
SDImageCacheTypeNone,
/**
* The image was obtained from the disk cache.
*/
SDImageCacheTypeDisk,
/**
* The image was obtained from the memory cache.
*/
SDImageCacheTypeMemory
};
typedef void(^SDWebImageQueryCompletedBlock)(UIImage *image, SDImageCacheType cacheType);
typedef void(^SDWebImageCheckCacheCompletionBlock)(BOOL isInCache);
typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger totalSize);
/**
* SDImageCache maintains a memory cache and an optional disk cache. Disk cache write operations are performed
* asynchronous so it doesn’t add unnecessary latency to the UI.
*/
@interface SDImageCache : NSObject
/**
* Decompressing images that are downloaded and cached can improve performance but can consume lot of memory.
* Defaults to YES. Set this to NO if you are experiencing a crash due to excessive memory consumption.
*/
@property (assign, nonatomic) BOOL shouldDecompressImages;
/**
* disable iCloud backup [defaults to YES]
*/
@property (assign, nonatomic) BOOL shouldDisableiCloud;
/**
* use memory cache [defaults to YES]
*/
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;
/**
* The maximum "total cost" of the in-memory image cache. The cost function is the number of pixels held in memory.
*/
@property (assign, nonatomic) NSUInteger maxMemoryCost;
/**
* The maximum number of objects the cache should hold.
*/
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit;
/**
* The maximum length of time to keep an image in the cache, in seconds
*/
@property (assign, nonatomic) NSInteger maxCacheAge;
/**
* The maximum size of the cache, in bytes.
*/
@property (assign, nonatomic) NSUInteger maxCacheSize;
/**
* Returns global shared cache instance
*
* @return SDImageCache global instance
*/
+ (SDImageCache *)sharedImageCache;
/**
* Init a new cache store with a specific namespace
*
* @param ns The namespace to use for this cache store
*/
- (id)initWithNamespace:(NSString *)ns;
/**
* Init a new cache store with a specific namespace and directory
*
* @param ns The namespace to use for this cache store
* @param directory Directory to cache disk images in
*/
- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory;
-(NSString *)makeDiskCachePath:(NSString*)fullNamespace;
/**
* Add a read-only cache path to search for images pre-cached by SDImageCache
* Useful if you want to bundle pre-loaded images with your app
*
* @param path The path to use for this read-only cache path
*/
- (void)addReadOnlyCachePath:(NSString *)path;
/**
* Store an image into memory and disk cache at the given key.
*
* @param image The image to store
* @param key The unique image cache key, usually it's image absolute URL
*/
- (void)storeImage:(UIImage *)image forKey:(NSString *)key;
/**
* Store an image into memory and optionally disk cache at the given key.
*
* @param image The image to store
* @param key The unique image cache key, usually it's image absolute URL
* @param toDisk Store the image to disk cache if YES
*/
- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk;
/**
* Store an image into memory and optionally disk cache at the given key.
*
* @param image The image to store
* @param recalculate BOOL indicates if imageData can be used or a new data should be constructed from the UIImage
* @param imageData The image data as returned by the server, this representation will be used for disk storage
* instead of converting the given image object into a storable/compressed image format in order
* to save quality and CPU
* @param key The unique image cache key, usually it's image absolute URL
* @param toDisk Store the image to disk cache if YES
*/
- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk;
/**
* Store image NSData into disk cache at the given key.
*
* @param imageData The image data to store
* @param key The unique image cache key, usually it's image absolute URL
*/
- (void)storeImageDataToDisk:(NSData *)imageData forKey:(NSString *)key;
/**
* Query the disk cache asynchronously.
*
* @param key The unique key used to store the wanted image
*/
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;
/**
* Query the memory cache synchronously.
*
* @param key The unique key used to store the wanted image
*/
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key;
/**
* Query the disk cache synchronously after checking the memory cache.
*
* @param key The unique key used to store the wanted image
*/
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key;
/**
* Remove the image from memory and disk cache synchronously
*
* @param key The unique image cache key
*/
- (void)removeImageForKey:(NSString *)key;
/**
* Remove the image from memory and disk cache asynchronously
*
* @param key The unique image cache key
* @param completion An block that should be executed after the image has been removed (optional)
*/
- (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion;
/**
* Remove the image from memory and optionally disk cache asynchronously
*
* @param key The unique image cache key
* @param fromDisk Also remove cache entry from disk if YES
*/
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk;
/**
* Remove the image from memory and optionally disk cache asynchronously
*
* @param key The unique image cache key
* @param fromDisk Also remove cache entry from disk if YES
* @param completion An block that should be executed after the image has been removed (optional)
*/
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion;
/**
* Clear all memory cached images
*/
- (void)clearMemory;
/**
* Clear all disk cached images. Non-blocking method - returns immediately.
* @param completion An block that should be executed after cache expiration completes (optional)
*/
- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;
/**
* Clear all disk cached images
* @see clearDiskOnCompletion:
*/
- (void)clearDisk;
/**
* Remove all expired cached image from disk. Non-blocking method - returns immediately.
* @param completionBlock An block that should be executed after cache expiration completes (optional)
*/
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock;
/**
* Remove all expired cached image from disk
* @see cleanDiskWithCompletionBlock:
*/
- (void)cleanDisk;
/**
* Get the size used by the disk cache
*/
- (NSUInteger)getSize;
/**
* Get the number of images in the disk cache
*/
- (NSUInteger)getDiskCount;
/**
* Asynchronously calculate the disk cache's size.
*/
- (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock;
/**
* Async check if image exists in disk cache already (does not load the image)
*
* @param key the key describing the url
* @param completionBlock the block to be executed when the check is done.
* @note the completion block will be always executed on the main queue
*/
- (void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
/**
* Check if image exists in disk cache already (does not load the image)
*
* @param key the key describing the url
*
* @return YES if an image exists for the given key
*/
- (BOOL)diskImageExistsWithKey:(NSString *)key;
/**
* Get the cache path for a certain key (needs the cache path root folder)
*
* @param key the key (can be obtained from url using cacheKeyForURL)
* @param path the cache path root folder
*
* @return the cache path
*/
- (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path;
/**
* Get the default cache path for a certain key
*
* @param key the key (can be obtained from url using cacheKeyForURL)
*
* @return the default cache path
*/
- (NSString *)defaultCachePathForKey:(NSString *)key;
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDImageCache.h"
#import "SDWebImageDecoder.h"
#import "UIImage+MultiFormat.h"
#import <CommonCrypto/CommonDigest.h>
// See https://github.com/rs/SDWebImage/pull/1141 for discussion
@interface AutoPurgeCache : NSCache
@end
@implementation AutoPurgeCache
- (id)init
{
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
@end
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
// PNG signature bytes and data (below)
static unsigned char kPNGSignatureBytes[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
static NSData *kPNGSignatureData = nil;
BOOL ImageDataHasPNGPreffix(NSData *data);
BOOL ImageDataHasPNGPreffix(NSData *data) {
NSUInteger pngSignatureLength = [kPNGSignatureData length];
if ([data length] >= pngSignatureLength) {
if ([[data subdataWithRange:NSMakeRange(0, pngSignatureLength)] isEqualToData:kPNGSignatureData]) {
return YES;
}
}
return NO;
}
FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
return image.size.height * image.size.width * image.scale * image.scale;
}
@interface SDImageCache ()
@property (strong, nonatomic) NSCache *memCache;
@property (strong, nonatomic) NSString *diskCachePath;
@property (strong, nonatomic) NSMutableArray *customPaths;
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t ioQueue;
@end
@implementation SDImageCache {
NSFileManager *_fileManager;
}
+ (SDImageCache *)sharedImageCache {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
- (id)init {
return [self initWithNamespace:@"default"];
}
- (id)initWithNamespace:(NSString *)ns {
NSString *path = [self makeDiskCachePath:ns];
return [self initWithNamespace:ns diskCacheDirectory:path];
}
- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory {
if ((self = [super init])) {
NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];
// initialise PNG signature data
kPNGSignatureData = [NSData dataWithBytes:kPNGSignatureBytes length:8];
// Create IO serial queue
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
// Init default values
_maxCacheAge = kDefaultCacheMaxCacheAge;
// Init the memory cache
_memCache = [[AutoPurgeCache alloc] init];
_memCache.name = fullNamespace;
// Init the disk cache
if (directory != nil) {
_diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
} else {
NSString *path = [self makeDiskCachePath:ns];
_diskCachePath = path;
}
// Set decompression to YES
_shouldDecompressImages = YES;
// memory cache enabled
_shouldCacheImagesInMemory = YES;
// Disable iCloud
_shouldDisableiCloud = YES;
dispatch_sync(_ioQueue, ^{
_fileManager = [NSFileManager new];
});
#if TARGET_OS_IOS
// Subscribe to app events
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearMemory)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(cleanDisk)
name:UIApplicationWillTerminateNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundCleanDisk)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
#endif
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
SDDispatchQueueRelease(_ioQueue);
}
- (void)addReadOnlyCachePath:(NSString *)path {
if (!self.customPaths) {
self.customPaths = [NSMutableArray new];
}
if (![self.customPaths containsObject:path]) {
[self.customPaths addObject:path];
}
}
- (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path {
NSString *filename = [self cachedFileNameForKey:key];
return [path stringByAppendingPathComponent:filename];
}
- (NSString *)defaultCachePathForKey:(NSString *)key {
return [self cachePathForKey:key inPath:self.diskCachePath];
}
#pragma mark SDImageCache (private)
- (NSString *)cachedFileNameForKey:(NSString *)key {
const char *str = [key UTF8String];
if (str == NULL) {
str = "";
}
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r);
NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
r[11], r[12], r[13], r[14], r[15], [[key pathExtension] isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", [key pathExtension]]];
return filename;
}
#pragma mark ImageCache
// Init the disk cache
-(NSString *)makeDiskCachePath:(NSString*)fullNamespace{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
return [paths[0] stringByAppendingPathComponent:fullNamespace];
}
- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk {
if (!image || !key) {
return;
}
// if memory cache is enabled
if (self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost];
}
if (toDisk) {
dispatch_async(self.ioQueue, ^{
NSData *data = imageData;
if (image && (recalculate || !data)) {
#if TARGET_OS_IPHONE
// We need to determine if the image is a PNG or a JPEG
// PNGs are easier to detect because they have a unique signature (http://www.w3.org/TR/PNG-Structure.html)
// The first eight bytes of a PNG file always contain the following (decimal) values:
// 137 80 78 71 13 10 26 10
// If the imageData is nil (i.e. if trying to save a UIImage directly or the image was transformed on download)
// and the image has an alpha channel, we will consider it PNG to avoid losing the transparency
int alphaInfo = CGImageGetAlphaInfo(image.CGImage);
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
alphaInfo == kCGImageAlphaNoneSkipFirst ||
alphaInfo == kCGImageAlphaNoneSkipLast);
BOOL imageIsPng = hasAlpha;
// But if we have an image data, we will look at the preffix
if ([imageData length] >= [kPNGSignatureData length]) {
imageIsPng = ImageDataHasPNGPreffix(imageData);
}
if (imageIsPng) {
data = UIImagePNGRepresentation(image);
}
else {
data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
}
#else
data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];
#endif
}
[self storeImageDataToDisk:data forKey:key];
});
}
}
- (void)storeImage:(UIImage *)image forKey:(NSString *)key {
[self storeImage:image recalculateFromImage:YES imageData:nil forKey:key toDisk:YES];
}
- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk {
[self storeImage:image recalculateFromImage:YES imageData:nil forKey:key toDisk:toDisk];
}
- (void)storeImageDataToDisk:(NSData *)imageData forKey:(NSString *)key {
if (!imageData) {
return;
}
if (![_fileManager fileExistsAtPath:_diskCachePath]) {
[_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
// get cache Path for image key
NSString *cachePathForKey = [self defaultCachePathForKey:key];
// transform to NSUrl
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
[_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
// disable iCloud backup
if (self.shouldDisableiCloud) {
[fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}
- (BOOL)diskImageExistsWithKey:(NSString *)key {
BOOL exists = NO;
// this is an exception to access the filemanager on another queue than ioQueue, but we are using the shared instance
// from apple docs on NSFileManager: The methods of the shared NSFileManager object can be called from multiple threads safely.
exists = [[NSFileManager defaultManager] fileExistsAtPath:[self defaultCachePathForKey:key]];
// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension
if (!exists) {
exists = [[NSFileManager defaultManager] fileExistsAtPath:[[self defaultCachePathForKey:key] stringByDeletingPathExtension]];
}
return exists;
}
- (void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock {
dispatch_async(_ioQueue, ^{
BOOL exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]];
// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension
if (!exists) {
exists = [_fileManager fileExistsAtPath:[[self defaultCachePathForKey:key] stringByDeletingPathExtension]];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(exists);
});
}
});
}
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
return [self.memCache objectForKey:key];
}
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key {
// First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
return image;
}
// Second check the disk cache...
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
return diskImage;
}
- (NSData *)diskImageDataBySearchingAllPathsForKey:(NSString *)key {
NSString *defaultPath = [self defaultCachePathForKey:key];
NSData *data = [NSData dataWithContentsOfFile:defaultPath];
if (data) {
return data;
}
// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension
data = [NSData dataWithContentsOfFile:[defaultPath stringByDeletingPathExtension]];
if (data) {
return data;
}
NSArray *customPaths = [self.customPaths copy];
for (NSString *path in customPaths) {
NSString *filePath = [self cachePathForKey:key inPath:path];
NSData *imageData = [NSData dataWithContentsOfFile:filePath];
if (imageData) {
return imageData;
}
// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension
imageData = [NSData dataWithContentsOfFile:[filePath stringByDeletingPathExtension]];
if (imageData) {
return imageData;
}
}
return nil;
}
- (UIImage *)diskImageForKey:(NSString *)key {
NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
if (data) {
UIImage *image = [UIImage sd_imageWithData:data];
image = [self scaledImageForKey:key image:image];
if (self.shouldDecompressImages) {
image = [UIImage decodedImageWithImage:image];
}
return image;
}
else {
return nil;
}
}
- (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image {
return SDScaledImageForKey(key, image);
}
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {
if (!doneBlock) {
return nil;
}
if (!key) {
doneBlock(nil, SDImageCacheTypeNone);
return nil;
}
// First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
doneBlock(image, SDImageCacheTypeMemory);
return nil;
}
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
return;
}
@autoreleasepool {
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, SDImageCacheTypeDisk);
});
}
});
return operation;
}
- (void)removeImageForKey:(NSString *)key {
[self removeImageForKey:key withCompletion:nil];
}
- (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion {
[self removeImageForKey:key fromDisk:YES withCompletion:completion];
}
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk {
[self removeImageForKey:key fromDisk:fromDisk withCompletion:nil];
}
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion {
if (key == nil) {
return;
}
if (self.shouldCacheImagesInMemory) {
[self.memCache removeObjectForKey:key];
}
if (fromDisk) {
dispatch_async(self.ioQueue, ^{
[_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
} else if (completion){
completion();
}
}
- (void)setMaxMemoryCost:(NSUInteger)maxMemoryCost {
self.memCache.totalCostLimit = maxMemoryCost;
}
- (NSUInteger)maxMemoryCost {
return self.memCache.totalCostLimit;
}
- (NSUInteger)maxMemoryCountLimit {
return self.memCache.countLimit;
}
- (void)setMaxMemoryCountLimit:(NSUInteger)maxCountLimit {
self.memCache.countLimit = maxCountLimit;
}
- (void)clearMemory {
[self.memCache removeAllObjects];
}
- (void)clearDisk {
[self clearDiskOnCompletion:nil];
}
- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion
{
dispatch_async(self.ioQueue, ^{
[_fileManager removeItemAtPath:self.diskCachePath error:nil];
[_fileManager createDirectoryAtPath:self.diskCachePath
withIntermediateDirectories:YES
attributes:nil
error:NULL];
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
}
- (void)cleanDisk {
[self cleanDiskWithCompletionBlock:nil];
}
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];
// This enumerator prefetches useful properties for our cache files.
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:resourceKeys
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL];
NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];
NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];
NSUInteger currentCacheSize = 0;
// Enumerate all of the files in the cache directory. This loop has two purposes:
//
// 1. Removing files that are older than the expiration date.
// 2. Storing file attributes for the size-based cleanup pass.
NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];
for (NSURL *fileURL in fileEnumerator) {
NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];
// Skip directories.
if ([resourceValues[NSURLIsDirectoryKey] boolValue]) {
continue;
}
// Remove files that are older than the expiration date;
NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
[urlsToDelete addObject:fileURL];
continue;
}
// Store a reference to this file and account for its total size.
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize += [totalAllocatedSize unsignedIntegerValue];
[cacheFiles setObject:resourceValues forKey:fileURL];
}
for (NSURL *fileURL in urlsToDelete) {
[_fileManager removeItemAtURL:fileURL error:nil];
}
// If our remaining disk cache exceeds a configured maximum size, perform a second
// size-based cleanup pass. We delete the oldest files first.
if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {
// Target half of our maximum cache size for this cleanup pass.
const NSUInteger desiredCacheSize = self.maxCacheSize / 2;
// Sort the remaining cache files by their last modification time (oldest first).
NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
usingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
}];
// Delete files until we fall below our desired cache size.
for (NSURL *fileURL in sortedFiles) {
if ([_fileManager removeItemAtURL:fileURL error:nil]) {
NSDictionary *resourceValues = cacheFiles[fileURL];
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];
if (currentCacheSize < desiredCacheSize) {
break;
}
}
}
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}
- (void)backgroundCleanDisk {
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return;
}
UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
[self cleanDiskWithCompletionBlock:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
- (NSUInteger)getSize {
__block NSUInteger size = 0;
dispatch_sync(self.ioQueue, ^{
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
for (NSString *fileName in fileEnumerator) {
NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
size += [attrs fileSize];
}
});
return size;
}
- (NSUInteger)getDiskCount {
__block NSUInteger count = 0;
dispatch_sync(self.ioQueue, ^{
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
count = [[fileEnumerator allObjects] count];
});
return count;
}
- (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock {
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
dispatch_async(self.ioQueue, ^{
NSUInteger fileCount = 0;
NSUInteger totalSize = 0;
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:@[NSFileSize]
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL];
for (NSURL *fileURL in fileEnumerator) {
NSNumber *fileSize;
[fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL];
totalSize += [fileSize unsignedIntegerValue];
fileCount += 1;
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(fileCount, totalSize);
});
}
});
}
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
* (c) Jamie Pinkham
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <TargetConditionals.h>
#ifdef __OBJC_GC__
#error SDWebImage does not support Objective-C Garbage Collection
#endif
#if __IPHONE_OS_VERSION_MIN_REQUIRED != 20000 && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_5_0
#error SDWebImage doesn't support Deployment Target version < 5.0
#endif
#if !TARGET_OS_IPHONE
#import <AppKit/AppKit.h>
#ifndef UIImage
#define UIImage NSImage
#endif
#ifndef UIImageView
#define UIImageView NSImageView
#endif
#else
#import <UIKit/UIKit.h>
#endif
#ifndef NS_ENUM
#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#endif
#ifndef NS_OPTIONS
#define NS_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type
#endif
#if OS_OBJECT_USE_OBJC
#undef SDDispatchQueueRelease
#undef SDDispatchQueueSetterSementics
#define SDDispatchQueueRelease(q)
#define SDDispatchQueueSetterSementics strong
#else
#undef SDDispatchQueueRelease
#undef SDDispatchQueueSetterSementics
#define SDDispatchQueueRelease(q) (dispatch_release(q))
#define SDDispatchQueueSetterSementics assign
#endif
extern UIImage *SDScaledImageForKey(NSString *key, UIImage *image);
typedef void(^SDWebImageNoParamsBlock)();
extern NSString *const SDWebImageErrorDomain;
#define dispatch_main_sync_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_sync(dispatch_get_main_queue(), block);\
}
#define dispatch_main_async_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
//
// SDWebImageCompat.m
// SDWebImage
//
// Created by Olivier Poitrey on 11/12/12.
// Copyright (c) 2012 Dailymotion. All rights reserved.
//
#import "SDWebImageCompat.h"
#if !__has_feature(objc_arc)
#error SDWebImage is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
#endif
inline UIImage *SDScaledImageForKey(NSString *key, UIImage *image) {
if (!image) {
return nil;
}
if ([image.images count] > 0) {
NSMutableArray *scaledImages = [NSMutableArray array];
for (UIImage *tempImage in image.images) {
[scaledImages addObject:SDScaledImageForKey(key, tempImage)];
}
return [UIImage animatedImageWithImages:scaledImages duration:image.duration];
}
else {
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
CGFloat scale = 1;
if (key.length >= 8) {
NSRange range = [key rangeOfString:@"@2x."];
if (range.location != NSNotFound) {
scale = 2.0;
}
range = [key rangeOfString:@"@3x."];
if (range.location != NSNotFound) {
scale = 3.0;
}
}
UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];
image = scaledImage;
}
return image;
}
}
NSString *const SDWebImageErrorDomain = @"SDWebImageErrorDomain";
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* Created by james <https://github.com/mystcolor> on 9/28/11.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
@interface UIImage (ForceDecode)
+ (UIImage *)decodedImageWithImage:(UIImage *)image;
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* Created by james <https://github.com/mystcolor> on 9/28/11.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageDecoder.h"
@implementation UIImage (ForceDecode)
+ (UIImage *)decodedImageWithImage:(UIImage *)image {
// while downloading huge amount of images
// autorelease the bitmap context
// and all vars to help system to free memory
// when there are memory warning.
// on iOS7, do not forget to call
// [[SDImageCache sharedImageCache] clearMemory];
if (image == nil) { // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
return nil;
}
@autoreleasepool{
// do not decode animated images
if (image.images != nil) {
return image;
}
CGImageRef imageRef = image.CGImage;
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
alpha == kCGImageAlphaLast ||
alpha == kCGImageAlphaPremultipliedFirst ||
alpha == kCGImageAlphaPremultipliedLast);
if (anyAlpha) {
return image;
}
// current
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
imageColorSpaceModel == kCGColorSpaceModelCMYK ||
imageColorSpaceModel == kCGColorSpaceModelIndexed);
if (unsupportedColorSpace) {
colorspaceRef = CGColorSpaceCreateDeviceRGB();
}
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
// to create bitmap graphics contexts without alpha info.
CGContextRef context = CGBitmapContextCreate(NULL,
width,
height,
bitsPerComponent,
bytesPerRow,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
// Draw the image into the context and retrieve the new bitmap image without alpha
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
scale:image.scale
orientation:image.imageOrientation];
if (unsupportedColorSpace) {
CGColorSpaceRelease(colorspaceRef);
}
CGContextRelease(context);
CGImageRelease(imageRefWithoutAlpha);
return imageWithoutAlpha;
}
}
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
#import "SDWebImageOperation.h"
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
SDWebImageDownloaderLowPriority = 1 << 0,
SDWebImageDownloaderProgressiveDownload = 1 << 1,
/**
* By default, request prevent the use of NSURLCache. With this flag, NSURLCache
* is used with default policies.
*/
SDWebImageDownloaderUseNSURLCache = 1 << 2,
/**
* Call completion block with nil image/imageData if the image was read from NSURLCache
* (to be combined with `SDWebImageDownloaderUseNSURLCache`).
*/
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
/**
* In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for
* extra time in background to let the request finish. If the background task expires the operation will be cancelled.
*/
SDWebImageDownloaderContinueInBackground = 1 << 4,
/**
* Handles cookies stored in NSHTTPCookieStore by setting
* NSMutableURLRequest.HTTPShouldHandleCookies = YES;
*/
SDWebImageDownloaderHandleCookies = 1 << 5,
/**
* Enable to allow untrusted SSL certificates.
* Useful for testing purposes. Use with caution in production.
*/
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
/**
* Put the image in the high priority queue.
*/
SDWebImageDownloaderHighPriority = 1 << 7,
};
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
/**
* Default value. All download operations will execute in queue style (first-in-first-out).
*/
SDWebImageDownloaderFIFOExecutionOrder,
/**
* All download operations will execute in stack style (last-in-first-out).
*/
SDWebImageDownloaderLIFOExecutionOrder
};
extern NSString *const SDWebImageDownloadStartNotification;
extern NSString *const SDWebImageDownloadStopNotification;
typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);
typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished);
typedef NSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url, NSDictionary *headers);
/**
* Asynchronous downloader dedicated and optimized for image loading.
*/
@interface SDWebImageDownloader : NSObject
/**
* Decompressing images that are downloaded and cached can improve performance but can consume lot of memory.
* Defaults to YES. Set this to NO if you are experiencing a crash due to excessive memory consumption.
*/
@property (assign, nonatomic) BOOL shouldDecompressImages;
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;
/**
* Shows the current amount of downloads that still need to be downloaded
*/
@property (readonly, nonatomic) NSUInteger currentDownloadCount;
/**
* The timeout value (in seconds) for the download operation. Default: 15.0.
*/
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
/**
* Changes download operations execution order. Default value is `SDWebImageDownloaderFIFOExecutionOrder`.
*/
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;
/**
* Singleton method, returns the shared instance
*
* @return global shared instance of downloader class
*/
+ (SDWebImageDownloader *)sharedDownloader;
/**
* Set the default URL credential to be set for request operations.
*/
@property (strong, nonatomic) NSURLCredential *urlCredential;
/**
* Set username
*/
@property (strong, nonatomic) NSString *username;
/**
* Set password
*/
@property (strong, nonatomic) NSString *password;
/**
* Set filter to pick headers for downloading image HTTP request.
*
* This block will be invoked for each downloading image request, returned
* NSDictionary will be used as headers in corresponding HTTP request.
*/
@property (nonatomic, copy) SDWebImageDownloaderHeadersFilterBlock headersFilter;
/**
* Set a value for a HTTP header to be appended to each download HTTP request.
*
* @param value The value for the header field. Use `nil` value to remove the header.
* @param field The name of the header field to set.
*/
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field;
/**
* Returns the value of the specified HTTP header field.
*
* @return The value associated with the header field field, or `nil` if there is no corresponding header field.
*/
- (NSString *)valueForHTTPHeaderField:(NSString *)field;
/**
* Sets a subclass of `SDWebImageDownloaderOperation` as the default
* `NSOperation` to be used each time SDWebImage constructs a request
* operation to download an image.
*
* @param operationClass The subclass of `SDWebImageDownloaderOperation` to set
* as default. Passing `nil` will revert to `SDWebImageDownloaderOperation`.
*/
- (void)setOperationClass:(Class)operationClass;
/**
* Creates a SDWebImageDownloader async downloader instance with a given URL
*
* The delegate will be informed when the image is finish downloaded or an error has happen.
*
* @see SDWebImageDownloaderDelegate
*
* @param url The URL to the image to download
* @param options The options to be used for this download
* @param progressBlock A block called repeatedly while the image is downloading
* @param completedBlock A block called once the download is completed.
* If the download succeeded, the image parameter is set, in case of error,
* error parameter is set with the error. The last parameter is always YES
* if SDWebImageDownloaderProgressiveDownload isn't use. With the
* SDWebImageDownloaderProgressiveDownload option, this block is called
* repeatedly with the partial image object and the finished argument set to NO
* before to be called a last time with the full image and finished argument
* set to YES. In case of error, the finished argument is always YES.
*
* @return A cancellable SDWebImageOperation
*/
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
/**
* Sets the download queue suspension state
*/
- (void)setSuspended:(BOOL)suspended;
/**
* Cancels all download operations in the queue
*/
- (void)cancelAllDownloads;
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageDownloader.h"
#import "SDWebImageDownloaderOperation.h"
#import <ImageIO/ImageIO.h>
static NSString *const kProgressCallbackKey = @"progress";
static NSString *const kCompletedCallbackKey = @"completed";
@interface SDWebImageDownloader ()
@property (strong, nonatomic) NSOperationQueue *downloadQueue;
@property (weak, nonatomic) NSOperation *lastAddedOperation;
@property (assign, nonatomic) Class operationClass;
@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;
@property (strong, nonatomic) NSMutableDictionary *HTTPHeaders;
// This queue is used to serialize the handling of the network responses of all the download operation in a single queue
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
@end
@implementation SDWebImageDownloader
+ (void)initialize {
// Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator )
// To use it, just add #import "SDNetworkActivityIndicator.h" in addition to the SDWebImage import
if (NSClassFromString(@"SDNetworkActivityIndicator")) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
#pragma clang diagnostic pop
// Remove observer in case it was previously added.
[[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStartNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStopNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:activityIndicator
selector:NSSelectorFromString(@"startActivity")
name:SDWebImageDownloadStartNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:activityIndicator
selector:NSSelectorFromString(@"stopActivity")
name:SDWebImageDownloadStopNotification object:nil];
}
}
+ (SDWebImageDownloader *)sharedDownloader {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
- (id)init {
if ((self = [super init])) {
_operationClass = [SDWebImageDownloaderOperation class];
_shouldDecompressImages = YES;
_executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 6;
_URLCallbacks = [NSMutableDictionary new];
#ifdef SD_WEBP
_HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
#else
_HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
_downloadTimeout = 15.0;
}
return self;
}
- (void)dealloc {
[self.downloadQueue cancelAllOperations];
SDDispatchQueueRelease(_barrierQueue);
}
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field {
if (value) {
self.HTTPHeaders[field] = value;
}
else {
[self.HTTPHeaders removeObjectForKey:field];
}
}
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
return self.HTTPHeaders[field];
}
- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
_downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
}
- (NSUInteger)currentDownloadCount {
return _downloadQueue.operationCount;
}
- (NSInteger)maxConcurrentDownloads {
return _downloadQueue.maxConcurrentOperationCount;
}
- (void)setOperationClass:(Class)operationClass {
_operationClass = operationClass ?: [SDWebImageDownloaderOperation class];
}
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
__block SDWebImageDownloaderOperation *operation;
__weak __typeof(self)wself = self;
[self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{
NSTimeInterval timeoutInterval = wself.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (wself.headersFilter) {
request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = wself.HTTPHeaders;
}
operation = [[wself.operationClass alloc] initWithRequest:request
options:options
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
dispatch_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
});
for (NSDictionary *callbacks in callbacksForURL) {
dispatch_async(dispatch_get_main_queue(), ^{
SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
if (callback) callback(receivedSize, expectedSize);
});
}
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
dispatch_barrier_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
if (finished) {
[sself.URLCallbacks removeObjectForKey:url];
}
});
for (NSDictionary *callbacks in callbacksForURL) {
SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
if (callback) callback(image, data, error, finished);
}
}
cancelled:^{
SDWebImageDownloader *sself = wself;
if (!sself) return;
dispatch_barrier_async(sself.barrierQueue, ^{
[sself.URLCallbacks removeObjectForKey:url];
});
}];
operation.shouldDecompressImages = wself.shouldDecompressImages;
if (wself.urlCredential) {
operation.credential = wself.urlCredential;
} else if (wself.username && wself.password) {
operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
}
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
[wself.downloadQueue addOperation:operation];
if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[wself.lastAddedOperation addDependency:operation];
wself.lastAddedOperation = operation;
}
}];
return operation;
}
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback {
// The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
if (url == nil) {
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
}
return;
}
dispatch_barrier_sync(self.barrierQueue, ^{
BOOL first = NO;
if (!self.URLCallbacks[url]) {
self.URLCallbacks[url] = [NSMutableArray new];
first = YES;
}
// Handle single download of simultaneous download request for the same URL
NSMutableArray *callbacksForURL = self.URLCallbacks[url];
NSMutableDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
[callbacksForURL addObject:callbacks];
self.URLCallbacks[url] = callbacksForURL;
if (first) {
createCallback();
}
});
}
- (void)setSuspended:(BOOL)suspended {
[self.downloadQueue setSuspended:suspended];
}
- (void)cancelAllDownloads {
[self.downloadQueue cancelAllOperations];
}
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageDownloader.h"
#import "SDWebImageOperation.h"
extern NSString *const SDWebImageDownloadStartNotification;
extern NSString *const SDWebImageDownloadReceiveResponseNotification;
extern NSString *const SDWebImageDownloadStopNotification;
extern NSString *const SDWebImageDownloadFinishNotification;
@interface SDWebImageDownloaderOperation : NSOperation <SDWebImageOperation>
/**
* The request used by the operation's connection.
*/
@property (strong, nonatomic, readonly) NSURLRequest *request;
@property (assign, nonatomic) BOOL shouldDecompressImages;
/**
* Whether the URL connection should consult the credential storage for authenticating the connection. `YES` by default.
*
* This is the value that is returned in the `NSURLConnectionDelegate` method `-connectionShouldUseCredentialStorage:`.
*/
@property (nonatomic, assign) BOOL shouldUseCredentialStorage;
/**
* The credential used for authentication challenges in `-connection:didReceiveAuthenticationChallenge:`.
*
* This will be overridden by any shared credentials that exist for the username or password of the request URL, if present.
*/
@property (nonatomic, strong) NSURLCredential *credential;
/**
* The SDWebImageDownloaderOptions for the receiver.
*/
@property (assign, nonatomic, readonly) SDWebImageDownloaderOptions options;
/**
* The expected size of data.
*/
@property (assign, nonatomic) NSInteger expectedSize;
/**
* The response returned by the operation's connection.
*/
@property (strong, nonatomic) NSURLResponse *response;
/**
* Initializes a `SDWebImageDownloaderOperation` object
*
* @see SDWebImageDownloaderOperation
*
* @param request the URL request
* @param options downloader options
* @param progressBlock the block executed when a new chunk of data arrives.
* @note the progress block is executed on a background queue
* @param completedBlock the block executed when the download is done.
* @note the completed block is executed on the main queue for success. If errors are found, there is a chance the block will be executed on a background queue
* @param cancelBlock the block executed if the download (operation) is cancelled
*
* @return the initialized instance
*/
- (id)initWithRequest:(NSURLRequest *)request
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock;
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageDownloaderOperation.h"
#import "SDWebImageDecoder.h"
#import "UIImage+MultiFormat.h"
#import <ImageIO/ImageIO.h>
#import "SDWebImageManager.h"
NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification";
NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";
@interface SDWebImageDownloaderOperation () <NSURLConnectionDataDelegate>
@property (copy, nonatomic) SDWebImageDownloaderProgressBlock progressBlock;
@property (copy, nonatomic) SDWebImageDownloaderCompletedBlock completedBlock;
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
@property (assign, nonatomic, getter = isFinished) BOOL finished;
@property (strong, nonatomic) NSMutableData *imageData;
@property (strong, nonatomic) NSURLConnection *connection;
@property (strong, atomic) NSThread *thread;
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
#endif
@end
@implementation SDWebImageDownloaderOperation {
size_t width, height;
UIImageOrientation orientation;
BOOL responseFromCached;
}
@synthesize executing = _executing;
@synthesize finished = _finished;
- (id)initWithRequest:(NSURLRequest *)request
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock {
if ((self = [super init])) {
_request = request;
_shouldDecompressImages = YES;
_shouldUseCredentialStorage = YES;
_options = options;
_progressBlock = [progressBlock copy];
_completedBlock = [completedBlock copy];
_cancelBlock = [cancelBlock copy];
_executing = NO;
_finished = NO;
_expectedSize = 0;
responseFromCached = YES; // Initially wrong until `connection:willCacheResponse:` is called or not called
}
return self;
}
- (void)start {
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
__weak __typeof__ (self) wself = self;
UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (wself) sself = wself;
if (sself) {
[sself cancel];
[app endBackgroundTask:sself.backgroundTaskId];
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}
#endif
self.executing = YES;
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
self.thread = [NSThread currentThread];
}
[self.connection start];
if (self.connection) {
if (self.progressBlock) {
self.progressBlock(0, NSURLResponseUnknownLength);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
});
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) {
// Make sure to run the runloop in our background thread so it can process downloaded data
// Note: we use a timeout to work around an issue with NSURLConnection cancel under iOS 5
// not waking up the runloop, leading to dead threads (see https://github.com/rs/SDWebImage/issues/466)
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
}
else {
CFRunLoopRun();
}
if (!self.isFinished) {
[self.connection cancel];
[self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]];
}
}
else {
if (self.completedBlock) {
self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
}
}
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return;
}
if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
[app endBackgroundTask:self.backgroundTaskId];
self.backgroundTaskId = UIBackgroundTaskInvalid;
}
#endif
}
- (void)cancel {
@synchronized (self) {
if (self.thread) {
[self performSelector:@selector(cancelInternalAndStop) onThread:self.thread withObject:nil waitUntilDone:NO];
}
else {
[self cancelInternal];
}
}
}
- (void)cancelInternalAndStop {
if (self.isFinished) return;
[self cancelInternal];
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void)cancelInternal {
if (self.isFinished) return;
[super cancel];
if (self.cancelBlock) self.cancelBlock();
if (self.connection) {
[self.connection cancel];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});
// As we cancelled the connection, its callback won't be called and thus won't
// maintain the isFinished and isExecuting flags.
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
}
[self reset];
}
- (void)done {
self.finished = YES;
self.executing = NO;
[self reset];
}
- (void)reset {
self.cancelBlock = nil;
self.completedBlock = nil;
self.progressBlock = nil;
self.connection = nil;
self.imageData = nil;
self.thread = nil;
}
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
- (void)setExecuting:(BOOL)executing {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
- (BOOL)isConcurrent {
return YES;
}
#pragma mark NSURLConnection (delegate)
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
//'304 Not Modified' is an exceptional one
if (![response respondsToSelector:@selector(statusCode)] || ([((NSHTTPURLResponse *)response) statusCode] < 400 && [((NSHTTPURLResponse *)response) statusCode] != 304)) {
NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
self.expectedSize = expected;
if (self.progressBlock) {
self.progressBlock(0, expected);
}
self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
self.response = response;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:self];
});
}
else {
NSUInteger code = [((NSHTTPURLResponse *)response) statusCode];
//This is the case when server returns '304 Not Modified'. It means that remote image is not changed.
//In case of 304 we need just cancel the operation and return cached image from the cache.
if (code == 304) {
[self cancelInternal];
} else {
[self.connection cancel];
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});
if (self.completedBlock) {
self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:nil], YES);
}
CFRunLoopStop(CFRunLoopGetCurrent());
[self done];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.imageData appendData:data];
if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0 && self.completedBlock) {
// The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
// Thanks to the author @Nyx0uf
// Get the total bytes downloaded
const NSInteger totalSize = self.imageData.length;
// Update the data source, we must pass ALL the data, not just the new bytes
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL);
if (width + height == 0) {
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
if (properties) {
NSInteger orientationValue = -1;
CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
if (val) CFNumberGetValue(val, kCFNumberLongType, &height);
val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
if (val) CFNumberGetValue(val, kCFNumberLongType, &width);
val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);
CFRelease(properties);
// When we draw to Core Graphics, we lose orientation information,
// which means the image below born of initWithCGIImage will be
// oriented incorrectly sometimes. (Unlike the image born of initWithData
// in connectionDidFinishLoading.) So save it here and pass it on later.
orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)];
}
}
if (width + height > 0 && totalSize < self.expectedSize) {
// Create the image
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
#ifdef TARGET_OS_IPHONE
// Workaround for iOS anamorphic image
if (partialImageRef) {
const size_t partialHeight = CGImageGetHeight(partialImageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bmContext = CGBitmapContextCreate(NULL, width, height, 8, width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);
if (bmContext) {
CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = width, .size.height = partialHeight}, partialImageRef);
CGImageRelease(partialImageRef);
partialImageRef = CGBitmapContextCreateImage(bmContext);
CGContextRelease(bmContext);
}
else {
CGImageRelease(partialImageRef);
partialImageRef = nil;
}
}
#endif
if (partialImageRef) {
UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:orientation];
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
UIImage *scaledImage = [self scaledImageForKey:key image:image];
if (self.shouldDecompressImages) {
image = [UIImage decodedImageWithImage:scaledImage];
}
else {
image = scaledImage;
}
CGImageRelease(partialImageRef);
dispatch_main_sync_safe(^{
if (self.completedBlock) {
self.completedBlock(image, nil, nil, NO);
}
});
}
}
CFRelease(imageSource);
}
if (self.progressBlock) {
self.progressBlock(self.imageData.length, self.expectedSize);
}
}
+ (UIImageOrientation)orientationFromPropertyValue:(NSInteger)value {
switch (value) {
case 1:
return UIImageOrientationUp;
case 3:
return UIImageOrientationDown;
case 8:
return UIImageOrientationLeft;
case 6:
return UIImageOrientationRight;
case 2:
return UIImageOrientationUpMirrored;
case 4:
return UIImageOrientationDownMirrored;
case 5:
return UIImageOrientationLeftMirrored;
case 7:
return UIImageOrientationRightMirrored;
default:
return UIImageOrientationUp;
}
}
- (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image {
return SDScaledImageForKey(key, image);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
@synchronized(self) {
CFRunLoopStop(CFRunLoopGetCurrent());
self.thread = nil;
self.connection = nil;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
});
}
if (![[NSURLCache sharedURLCache] cachedResponseForRequest:_request]) {
responseFromCached = NO;
}
if (completionBlock) {
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached) {
completionBlock(nil, nil, nil, YES);
} else if (self.imageData) {
UIImage *image = [UIImage sd_imageWithData:self.imageData];
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
image = [self scaledImageForKey:key image:image];
// Do not force decoding animated GIFs
if (!image.images) {
if (self.shouldDecompressImages) {
image = [UIImage decodedImageWithImage:image];
}
}
if (CGSizeEqualToSize(image.size, CGSizeZero)) {
completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
}
else {
completionBlock(image, self.imageData, nil, YES);
}
} else {
completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}], YES);
}
}
self.completionBlock = nil;
[self done];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
@synchronized(self) {
CFRunLoopStop(CFRunLoopGetCurrent());
self.thread = nil;
self.connection = nil;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});
}
if (self.completedBlock) {
self.completedBlock(nil, nil, error, YES);
}
self.completionBlock = nil;
[self done];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {
responseFromCached = NO; // If this method is called, it means the response wasn't read from cache
if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
// Prevents caching of responses
return nil;
}
else {
return cachedResponse;
}
}
- (BOOL)shouldContinueWhenAppEntersBackground {
return self.options & SDWebImageDownloaderContinueInBackground;
}
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection __unused *)connection {
return self.shouldUseCredentialStorage;
}
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates) &&
[challenge.sender respondsToSelector:@selector(performDefaultHandlingForAuthenticationChallenge:)]) {
[challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge];
} else {
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
} else {
if ([challenge previousFailureCount] == 0) {
if (self.credential) {
[[challenge sender] useCredential:self.credential forAuthenticationChallenge:challenge];
} else {
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
}
} else {
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
}
}
}
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
#import "SDWebImageOperation.h"
#import "SDWebImageDownloader.h"
#import "SDImageCache.h"
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
/**
* By default, when a URL fail to be downloaded, the URL is blacklisted so the library won't keep trying.
* This flag disable this blacklisting.
*/
SDWebImageRetryFailed = 1 << 0,
/**
* By default, image downloads are started during UI interactions, this flags disable this feature,
* leading to delayed download on UIScrollView deceleration for instance.
*/
SDWebImageLowPriority = 1 << 1,
/**
* This flag disables on-disk caching
*/
SDWebImageCacheMemoryOnly = 1 << 2,
/**
* This flag enables progressive download, the image is displayed progressively during download as a browser would do.
* By default, the image is only displayed once completely downloaded.
*/
SDWebImageProgressiveDownload = 1 << 3,
/**
* Even if the image is cached, respect the HTTP response cache control, and refresh the image from remote location if needed.
* The disk caching will be handled by NSURLCache instead of SDWebImage leading to slight performance degradation.
* This option helps deal with images changing behind the same request URL, e.g. Facebook graph api profile pics.
* If a cached image is refreshed, the completion block is called once with the cached image and again with the final image.
*
* Use this flag only if you can't make your URLs static with embedded cache busting parameter.
*/
SDWebImageRefreshCached = 1 << 4,
/**
* In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for
* extra time in background to let the request finish. If the background task expires the operation will be cancelled.
*/
SDWebImageContinueInBackground = 1 << 5,
/**
* Handles cookies stored in NSHTTPCookieStore by setting
* NSMutableURLRequest.HTTPShouldHandleCookies = YES;
*/
SDWebImageHandleCookies = 1 << 6,
/**
* Enable to allow untrusted SSL certificates.
* Useful for testing purposes. Use with caution in production.
*/
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
/**
* By default, images are loaded in the order in which they were queued. This flag moves them to
* the front of the queue.
*/
SDWebImageHighPriority = 1 << 8,
/**
* By default, placeholder images are loaded while the image is loading. This flag will delay the loading
* of the placeholder image until after the image has finished loading.
*/
SDWebImageDelayPlaceholder = 1 << 9,
/**
* We usually don't call transformDownloadedImage delegate method on animated images,
* as most transformation code would mangle it.
* Use this flag to transform them anyway.
*/
SDWebImageTransformAnimatedImage = 1 << 10,
/**
* By default, image is added to the imageView after download. But in some cases, we want to
* have the hand before setting the image (apply a filter or add it with cross-fade animation for instance)
* Use this flag if you want to manually set the image in the completion when success
*/
SDWebImageAvoidAutoSetImage = 1 << 11
};
typedef void(^SDWebImageCompletionBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL);
typedef void(^SDWebImageCompletionWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL);
typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);
@class SDWebImageManager;
@protocol SDWebImageManagerDelegate <NSObject>
@optional
/**
* Controls which image should be downloaded when the image is not found in the cache.
*
* @param imageManager The current `SDWebImageManager`
* @param imageURL The url of the image to be downloaded
*
* @return Return NO to prevent the downloading of the image on cache misses. If not implemented, YES is implied.
*/
- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;
/**
* Allows to transform the image immediately after it has been downloaded and just before to cache it on disk and memory.
* NOTE: This method is called from a global queue in order to not to block the main thread.
*
* @param imageManager The current `SDWebImageManager`
* @param image The image to transform
* @param imageURL The url of the image to transform
*
* @return The transformed image object.
*/
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;
@end
/**
* The SDWebImageManager is the class behind the UIImageView+WebCache category and likes.
* It ties the asynchronous downloader (SDWebImageDownloader) with the image cache store (SDImageCache).
* You can use this class directly to benefit from web image downloading with caching in another context than
* a UIView.
*
* Here is a simple example of how to use SDWebImageManager:
*
* @code
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:imageURL
options:0
progress:nil
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
// do something with image
}
}];
* @endcode
*/
@interface SDWebImageManager : NSObject
@property (weak, nonatomic) id <SDWebImageManagerDelegate> delegate;
@property (strong, nonatomic, readonly) SDImageCache *imageCache;
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;
/**
* The cache filter is a block used each time SDWebImageManager need to convert an URL into a cache key. This can
* be used to remove dynamic part of an image URL.
*
* The following example sets a filter in the application delegate that will remove any query-string from the
* URL before to use it as a cache key:
*
* @code
[[SDWebImageManager sharedManager] setCacheKeyFilter:^(NSURL *url) {
url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path];
return [url absoluteString];
}];
* @endcode
*/
@property (nonatomic, copy) SDWebImageCacheKeyFilterBlock cacheKeyFilter;
/**
* Returns global SDWebImageManager instance.
*
* @return SDWebImageManager shared instance
*/
+ (SDWebImageManager *)sharedManager;
/**
* Downloads the image at the given URL if not present in cache or return the cached version otherwise.
*
* @param url The URL to the image
* @param options A mask to specify options to use for this request
* @param progressBlock A block called while image is downloading
* @param completedBlock A block called when operation has been completed.
*
* This parameter is required.
*
* This block has no return value and takes the requested UIImage as first parameter.
* In case of error the image parameter is nil and the second parameter may contain an NSError.
*
* The third parameter is an `SDImageCacheType` enum indicating if the image was retrieved from the local cache
* or from the memory cache or from the network.
*
* The last parameter is set to NO when the SDWebImageProgressiveDownload option is used and the image is
* downloading. This block is thus called repeatedly with a partial image. When image is fully downloaded, the
* block is called a last time with the full image and the last parameter set to YES.
*
* @return Returns an NSObject conforming to SDWebImageOperation. Should be an instance of SDWebImageDownloaderOperation
*/
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
/**
* Saves image to cache for given URL
*
* @param image The image to cache
* @param url The URL to the image
*
*/
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;
/**
* Cancel all current operations
*/
- (void)cancelAll;
/**
* Check one or more operations running
*/
- (BOOL)isRunning;
/**
* Check if image has already been cached
*
* @param url image url
*
* @return if the image was already cached
*/
- (BOOL)cachedImageExistsForURL:(NSURL *)url;
/**
* Check if image has already been cached on disk only
*
* @param url image url
*
* @return if the image was already cached (disk only)
*/
- (BOOL)diskImageExistsForURL:(NSURL *)url;
/**
* Async check if image has already been cached
*
* @param url image url
* @param completionBlock the block to be executed when the check is finished
*
* @note the completion block is always executed on the main queue
*/
- (void)cachedImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
/**
* Async check if image has already been cached on disk only
*
* @param url image url
* @param completionBlock the block to be executed when the check is finished
*
* @note the completion block is always executed on the main queue
*/
- (void)diskImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
/**
*Return the cache key for a given URL
*/
- (NSString *)cacheKeyForURL:(NSURL *)url;
@end
#pragma mark - Deprecated
typedef void(^SDWebImageCompletedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType) __deprecated_msg("Block type deprecated. Use `SDWebImageCompletionBlock`");
typedef void(^SDWebImageCompletedWithFinishedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) __deprecated_msg("Block type deprecated. Use `SDWebImageCompletionWithFinishedBlock`");
@interface SDWebImageManager (Deprecated)
/**
* Downloads the image at the given URL if not present in cache or return the cached version otherwise.
*
* @deprecated This method has been deprecated. Use `downloadImageWithURL:options:progress:completed:`
*/
- (id <SDWebImageOperation>)downloadWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletedWithFinishedBlock)completedBlock __deprecated_msg("Method deprecated. Use `downloadImageWithURL:options:progress:completed:`");
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageManager.h"
#import <objc/message.h>
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic) NSOperation *cacheOperation;
@end
@interface SDWebImageManager ()
@property (strong, nonatomic, readwrite) SDImageCache *imageCache;
@property (strong, nonatomic, readwrite) SDWebImageDownloader *imageDownloader;
@property (strong, nonatomic) NSMutableSet *failedURLs;
@property (strong, nonatomic) NSMutableArray *runningOperations;
@end
@implementation SDWebImageManager
+ (id)sharedManager {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
- (id)init {
if ((self = [super init])) {
_imageCache = [self createCache];
_imageDownloader = [SDWebImageDownloader sharedDownloader];
_failedURLs = [NSMutableSet new];
_runningOperations = [NSMutableArray new];
}
return self;
}
- (SDImageCache *)createCache {
return [SDImageCache sharedImageCache];
}
- (NSString *)cacheKeyForURL:(NSURL *)url {
if (self.cacheKeyFilter) {
return self.cacheKeyFilter(url);
}
else {
return [url absoluteString];
}
}
- (BOOL)cachedImageExistsForURL:(NSURL *)url {
NSString *key = [self cacheKeyForURL:url];
if ([self.imageCache imageFromMemoryCacheForKey:key] != nil) return YES;
return [self.imageCache diskImageExistsWithKey:key];
}
- (BOOL)diskImageExistsForURL:(NSURL *)url {
NSString *key = [self cacheKeyForURL:url];
return [self.imageCache diskImageExistsWithKey:key];
}
- (void)cachedImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock {
NSString *key = [self cacheKeyForURL:url];
BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil);
if (isInMemoryCache) {
// making sure we call the completion block on the main queue
dispatch_async(dispatch_get_main_queue(), ^{
if (completionBlock) {
completionBlock(YES);
}
});
return;
}
[self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
// the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
if (completionBlock) {
completionBlock(isInDiskCache);
}
}];
}
- (void)diskImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock {
NSString *key = [self cacheKeyForURL:url];
[self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
// the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
if (completionBlock) {
completionBlock(isInDiskCache);
}
}];
}
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
// Invoking this method without a completedBlock is pointless
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
// Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, XCode won't
// throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
BOOL isFailedUrl = NO;
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
dispatch_main_sync_safe(^{
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
});
return operation;
}
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
NSString *key = [self cacheKeyForURL:url];
operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
if (operation.isCancelled) {
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
return;
}
if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
if (image && options & SDWebImageRefreshCached) {
dispatch_main_sync_safe(^{
// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
// AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
completedBlock(image, nil, cacheType, YES, url);
});
}
// download if no image or requested to refresh anyway, and download allowed by delegate
SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
if (image && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
// Do nothing if the operation was cancelled
// See #699 for more details
// if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
}
else if (error) {
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
}
});
if ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost) {
@synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
}
}
}
else {
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
if (options & SDWebImageRefreshCached && image && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
}
else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
[self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
}
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
});
}
else {
if (downloadedImage && finished) {
[self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
}
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
}
}
if (finished) {
@synchronized (self.runningOperations) {
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
}
}];
operation.cancelBlock = ^{
[subOperation cancel];
@synchronized (self.runningOperations) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
};
}
else if (image) {
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(image, nil, cacheType, YES, url);
}
});
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
}
else {
// Image not in cache and download disallowed by delegate
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !weakOperation.isCancelled) {
completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
}
});
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
}
}];
return operation;
}
- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url {
if (image && url) {
NSString *key = [self cacheKeyForURL:url];
[self.imageCache storeImage:image forKey:key toDisk:YES];
}
}
- (void)cancelAll {
@synchronized (self.runningOperations) {
NSArray *copiedOperations = [self.runningOperations copy];
[copiedOperations makeObjectsPerformSelector:@selector(cancel)];
[self.runningOperations removeObjectsInArray:copiedOperations];
}
}
- (BOOL)isRunning {
BOOL isRunning = NO;
@synchronized(self.runningOperations) {
isRunning = (self.runningOperations.count > 0);
}
return isRunning;
}
@end
@implementation SDWebImageCombinedOperation
- (void)setCancelBlock:(SDWebImageNoParamsBlock)cancelBlock {
// check if the operation is already cancelled, then we just call the cancelBlock
if (self.isCancelled) {
if (cancelBlock) {
cancelBlock();
}
_cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes
} else {
_cancelBlock = [cancelBlock copy];
}
}
- (void)cancel {
self.cancelled = YES;
if (self.cacheOperation) {
[self.cacheOperation cancel];
self.cacheOperation = nil;
}
if (self.cancelBlock) {
self.cancelBlock();
// TODO: this is a temporary fix to #809.
// Until we can figure the exact cause of the crash, going with the ivar instead of the setter
// self.cancelBlock = nil;
_cancelBlock = nil;
}
}
@end
@implementation SDWebImageManager (Deprecated)
// deprecated method, uses the non deprecated method
// adapter for the completion block
- (id <SDWebImageOperation>)downloadWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedWithFinishedBlock)completedBlock {
return [self downloadImageWithURL:url
options:options
progress:progressBlock
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType, finished);
}
}];
}
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
@protocol SDWebImageOperation <NSObject>
- (void)cancel;
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageManager.h"
@class SDWebImagePrefetcher;
@protocol SDWebImagePrefetcherDelegate <NSObject>
@optional
/**
* Called when an image was prefetched.
*
* @param imagePrefetcher The current image prefetcher
* @param imageURL The image url that was prefetched
* @param finishedCount The total number of images that were prefetched (successful or not)
* @param totalCount The total number of images that were to be prefetched
*/
- (void)imagePrefetcher:(SDWebImagePrefetcher *)imagePrefetcher didPrefetchURL:(NSURL *)imageURL finishedCount:(NSUInteger)finishedCount totalCount:(NSUInteger)totalCount;
/**
* Called when all images are prefetched.
* @param imagePrefetcher The current image prefetcher
* @param totalCount The total number of images that were prefetched (whether successful or not)
* @param skippedCount The total number of images that were skipped
*/
- (void)imagePrefetcher:(SDWebImagePrefetcher *)imagePrefetcher didFinishWithTotalCount:(NSUInteger)totalCount skippedCount:(NSUInteger)skippedCount;
@end
typedef void(^SDWebImagePrefetcherProgressBlock)(NSUInteger noOfFinishedUrls, NSUInteger noOfTotalUrls);
typedef void(^SDWebImagePrefetcherCompletionBlock)(NSUInteger noOfFinishedUrls, NSUInteger noOfSkippedUrls);
/**
* Prefetch some URLs in the cache for future use. Images are downloaded in low priority.
*/
@interface SDWebImagePrefetcher : NSObject
/**
* The web image manager
*/
@property (strong, nonatomic, readonly) SDWebImageManager *manager;
/**
* Maximum number of URLs to prefetch at the same time. Defaults to 3.
*/
@property (nonatomic, assign) NSUInteger maxConcurrentDownloads;
/**
* SDWebImageOptions for prefetcher. Defaults to SDWebImageLowPriority.
*/
@property (nonatomic, assign) SDWebImageOptions options;
/**
* Queue options for Prefetcher. Defaults to Main Queue.
*/
@property (nonatomic, assign) dispatch_queue_t prefetcherQueue;
@property (weak, nonatomic) id <SDWebImagePrefetcherDelegate> delegate;
/**
* Return the global image prefetcher instance.
*/
+ (SDWebImagePrefetcher *)sharedImagePrefetcher;
/**
* Allows you to instantiate a prefetcher with any arbitrary image manager.
*/
- (id)initWithImageManager:(SDWebImageManager *)manager;
/**
* Assign list of URLs to let SDWebImagePrefetcher to queue the prefetching,
* currently one image is downloaded at a time,
* and skips images for failed downloads and proceed to the next image in the list
*
* @param urls list of URLs to prefetch
*/
- (void)prefetchURLs:(NSArray *)urls;
/**
* Assign list of URLs to let SDWebImagePrefetcher to queue the prefetching,
* currently one image is downloaded at a time,
* and skips images for failed downloads and proceed to the next image in the list
*
* @param urls list of URLs to prefetch
* @param progressBlock block to be called when progress updates;
* first parameter is the number of completed (successful or not) requests,
* second parameter is the total number of images originally requested to be prefetched
* @param completionBlock block to be called when prefetching is completed
* first param is the number of completed (successful or not) requests,
* second parameter is the number of skipped requests
*/
- (void)prefetchURLs:(NSArray *)urls progress:(SDWebImagePrefetcherProgressBlock)progressBlock completed:(SDWebImagePrefetcherCompletionBlock)completionBlock;
/**
* Remove and cancel queued list
*/
- (void)cancelPrefetching;
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImagePrefetcher.h"
@interface SDWebImagePrefetcher ()
@property (strong, nonatomic) SDWebImageManager *manager;
@property (strong, nonatomic) NSArray *prefetchURLs;
@property (assign, nonatomic) NSUInteger requestedCount;
@property (assign, nonatomic) NSUInteger skippedCount;
@property (assign, nonatomic) NSUInteger finishedCount;
@property (assign, nonatomic) NSTimeInterval startedTime;
@property (copy, nonatomic) SDWebImagePrefetcherCompletionBlock completionBlock;
@property (copy, nonatomic) SDWebImagePrefetcherProgressBlock progressBlock;
@end
@implementation SDWebImagePrefetcher
+ (SDWebImagePrefetcher *)sharedImagePrefetcher {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
- (id)init {
return [self initWithImageManager:[SDWebImageManager new]];
}
- (id)initWithImageManager:(SDWebImageManager *)manager {
if ((self = [super init])) {
_manager = manager;
_options = SDWebImageLowPriority;
_prefetcherQueue = dispatch_get_main_queue();
self.maxConcurrentDownloads = 3;
}
return self;
}
- (void)setMaxConcurrentDownloads:(NSUInteger)maxConcurrentDownloads {
self.manager.imageDownloader.maxConcurrentDownloads = maxConcurrentDownloads;
}
- (NSUInteger)maxConcurrentDownloads {
return self.manager.imageDownloader.maxConcurrentDownloads;
}
- (void)startPrefetchingAtIndex:(NSUInteger)index {
if (index >= self.prefetchURLs.count) return;
self.requestedCount++;
[self.manager downloadImageWithURL:self.prefetchURLs[index] options:self.options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (!finished) return;
self.finishedCount++;
if (image) {
if (self.progressBlock) {
self.progressBlock(self.finishedCount,[self.prefetchURLs count]);
}
}
else {
if (self.progressBlock) {
self.progressBlock(self.finishedCount,[self.prefetchURLs count]);
}
// Add last failed
self.skippedCount++;
}
if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didPrefetchURL:finishedCount:totalCount:)]) {
[self.delegate imagePrefetcher:self
didPrefetchURL:self.prefetchURLs[index]
finishedCount:self.finishedCount
totalCount:self.prefetchURLs.count
];
}
if (self.prefetchURLs.count > self.requestedCount) {
dispatch_async(self.prefetcherQueue, ^{
[self startPrefetchingAtIndex:self.requestedCount];
});
} else if (self.finishedCount == self.requestedCount) {
[self reportStatus];
if (self.completionBlock) {
self.completionBlock(self.finishedCount, self.skippedCount);
self.completionBlock = nil;
}
self.progressBlock = nil;
}
}];
}
- (void)reportStatus {
NSUInteger total = [self.prefetchURLs count];
if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didFinishWithTotalCount:skippedCount:)]) {
[self.delegate imagePrefetcher:self
didFinishWithTotalCount:(total - self.skippedCount)
skippedCount:self.skippedCount
];
}
}
- (void)prefetchURLs:(NSArray *)urls {
[self prefetchURLs:urls progress:nil completed:nil];
}
- (void)prefetchURLs:(NSArray *)urls progress:(SDWebImagePrefetcherProgressBlock)progressBlock completed:(SDWebImagePrefetcherCompletionBlock)completionBlock {
[self cancelPrefetching]; // Prevent duplicate prefetch request
self.startedTime = CFAbsoluteTimeGetCurrent();
self.prefetchURLs = urls;
self.completionBlock = completionBlock;
self.progressBlock = progressBlock;
if (urls.count == 0) {
if (completionBlock) {
completionBlock(0,0);
}
} else {
// Starts prefetching from the very first image on the list with the max allowed concurrency
NSUInteger listCount = self.prefetchURLs.count;
for (NSUInteger i = 0; i < self.maxConcurrentDownloads && self.requestedCount < listCount; i++) {
[self startPrefetchingAtIndex:i];
}
}
}
- (void)cancelPrefetching {
self.prefetchURLs = nil;
self.skippedCount = 0;
self.requestedCount = 0;
self.finishedCount = 0;
[self.manager cancelAll];
}
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
#import "SDWebImageManager.h"
/**
* Integrates SDWebImage async downloading and caching of remote images with UIButtonView.
*/
@interface UIButton (WebCache)
/**
* Get the current image URL.
*/
- (NSURL *)sd_currentImageURL;
/**
* Get the image URL for a control state.
*
* @param state Which state you want to know the URL for. The values are described in UIControlState.
*/
- (NSURL *)sd_imageURLForState:(UIControlState)state;
/**
* Set the imageView `image` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
*/
- (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state;
/**
* Set the imageView `image` with an `url` and a placeholder.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
* @param placeholder The image to be set initially, until the image request finishes.
* @see sd_setImageWithURL:placeholderImage:options:
*/
- (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder;
/**
* Set the imageView `image` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
*/
- (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
/**
* Set the imageView `image` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletionBlock)completedBlock;
/**
* Set the imageView `image` with an `url`, placeholder.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
* @param placeholder The image to be set initially, until the image request finishes.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock;
/**
* Set the imageView `image` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;
/**
* Set the backgroundImageView `image` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
*/
- (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state;
/**
* Set the backgroundImageView `image` with an `url` and a placeholder.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
* @param placeholder The image to be set initially, until the image request finishes.
* @see sd_setImageWithURL:placeholderImage:options:
*/
- (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder;
/**
* Set the backgroundImageView `image` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
*/
- (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
/**
* Set the backgroundImageView `image` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletionBlock)completedBlock;
/**
* Set the backgroundImageView `image` with an `url`, placeholder.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param state The state that uses the specified title. The values are described in UIControlState.
* @param placeholder The image to be set initially, until the image request finishes.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock;
/**
* Set the backgroundImageView `image` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;
/**
* Cancel the current image download
*/
- (void)sd_cancelImageLoadForState:(UIControlState)state;
/**
* Cancel the current backgroundImage download
*/
- (void)sd_cancelBackgroundImageLoadForState:(UIControlState)state;
@end
@interface UIButton (WebCacheDeprecated)
- (NSURL *)currentImageURL __deprecated_msg("Use `sd_currentImageURL`");
- (NSURL *)imageURLForState:(UIControlState)state __deprecated_msg("Use `sd_imageURLForState:`");
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:forState:`");
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:forState:placeholderImage:`");
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:forState:placeholderImage:options:`");
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:forState:completed:`");
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:forState:placeholderImage:completed:`");
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:forState:placeholderImage:options:completed:`");
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state __deprecated_msg("Method deprecated. Use `sd_setBackgroundImageWithURL:forState:`");
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder __deprecated_msg("Method deprecated. Use `sd_setBackgroundImageWithURL:forState:placeholderImage:`");
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options __deprecated_msg("Method deprecated. Use `sd_setBackgroundImageWithURL:forState:placeholderImage:options:`");
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setBackgroundImageWithURL:forState:completed:`");
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setBackgroundImageWithURL:forState:placeholderImage:completed:`");
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setBackgroundImageWithURL:forState:placeholderImage:options:completed:`");
- (void)cancelCurrentImageLoad __deprecated_msg("Use `sd_cancelImageLoadForState:`");
- (void)cancelBackgroundImageLoadForState:(UIControlState)state __deprecated_msg("Use `sd_cancelBackgroundImageLoadForState:`");
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "UIButton+WebCache.h"
#import "objc/runtime.h"
#import "UIView+WebCacheOperation.h"
static char imageURLStorageKey;
@implementation UIButton (WebCache)
- (NSURL *)sd_currentImageURL {
NSURL *url = self.imageURLStorage[@(self.state)];
if (!url) {
url = self.imageURLStorage[@(UIControlStateNormal)];
}
return url;
}
- (NSURL *)sd_imageURLForState:(UIControlState)state {
return self.imageURLStorage[@(state)];
}
- (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state {
[self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil];
}
- (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil];
}
- (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil];
}
- (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:completedBlock];
}
- (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:completedBlock];
}
- (void)sd_setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock {
[self setImage:placeholder forState:state];
[self sd_cancelImageLoadForState:state];
if (!url) {
[self.imageURLStorage removeObjectForKey:@(state)];
dispatch_main_async_safe(^{
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
return;
}
self.imageURLStorage[@(state)] = url;
__weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (!wself) return;
dispatch_main_sync_safe(^{
__strong UIButton *sself = wself;
if (!sself) return;
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
{
completedBlock(image, error, cacheType, url);
return;
}
else if (image) {
[sself setImage:image forState:state];
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
[self sd_setImageLoadOperation:operation forState:state];
}
- (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil];
}
- (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil];
}
- (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil];
}
- (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:completedBlock];
}
- (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:completedBlock];
}
- (void)sd_setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_cancelBackgroundImageLoadForState:state];
[self setBackgroundImage:placeholder forState:state];
if (url) {
__weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (!wself) return;
dispatch_main_sync_safe(^{
__strong UIButton *sself = wself;
if (!sself) return;
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
{
completedBlock(image, error, cacheType, url);
return;
}
else if (image) {
[sself setBackgroundImage:image forState:state];
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
[self sd_setBackgroundImageLoadOperation:operation forState:state];
} else {
dispatch_main_async_safe(^{
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
if (completedBlock) {
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
- (void)sd_setImageLoadOperation:(id<SDWebImageOperation>)operation forState:(UIControlState)state {
[self sd_setImageLoadOperation:operation forKey:[NSString stringWithFormat:@"UIButtonImageOperation%@", @(state)]];
}
- (void)sd_cancelImageLoadForState:(UIControlState)state {
[self sd_cancelImageLoadOperationWithKey:[NSString stringWithFormat:@"UIButtonImageOperation%@", @(state)]];
}
- (void)sd_setBackgroundImageLoadOperation:(id<SDWebImageOperation>)operation forState:(UIControlState)state {
[self sd_setImageLoadOperation:operation forKey:[NSString stringWithFormat:@"UIButtonBackgroundImageOperation%@", @(state)]];
}
- (void)sd_cancelBackgroundImageLoadForState:(UIControlState)state {
[self sd_cancelImageLoadOperationWithKey:[NSString stringWithFormat:@"UIButtonBackgroundImageOperation%@", @(state)]];
}
- (NSMutableDictionary *)imageURLStorage {
NSMutableDictionary *storage = objc_getAssociatedObject(self, &imageURLStorageKey);
if (!storage)
{
storage = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &imageURLStorageKey, storage, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return storage;
}
@end
@implementation UIButton (WebCacheDeprecated)
- (NSURL *)currentImageURL {
return [self sd_currentImageURL];
}
- (NSURL *)imageURLForState:(UIControlState)state {
return [self sd_imageURLForState:state];
}
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state {
[self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil];
}
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil];
}
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil];
}
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setImageWithURL:url forState:state placeholderImage:nil options:0 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)setImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setImageWithURL:url forState:state placeholderImage:placeholder options:options completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:nil];
}
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:nil];
}
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:options completed:nil];
}
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:nil options:0 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:0 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)setBackgroundImageWithURL:(NSURL *)url forState:(UIControlState)state placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setBackgroundImageWithURL:url forState:state placeholderImage:placeholder options:options completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)cancelCurrentImageLoad {
// in a backwards compatible manner, cancel for current state
[self sd_cancelImageLoadForState:self.state];
}
- (void)cancelBackgroundImageLoadForState:(UIControlState)state {
[self sd_cancelBackgroundImageLoadForState:state];
}
@end
//
// UIImage+GIF.h
// LBGIFImage
//
// Created by Laurin Brandner on 06.01.12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIImage (GIF)
+ (UIImage *)sd_animatedGIFNamed:(NSString *)name;
+ (UIImage *)sd_animatedGIFWithData:(NSData *)data;
- (UIImage *)sd_animatedImageByScalingAndCroppingToSize:(CGSize)size;
@end
//
// UIImage+GIF.m
// LBGIFImage
//
// Created by Laurin Brandner on 06.01.12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#import "UIImage+GIF.h"
#import <ImageIO/ImageIO.h>
@implementation UIImage (GIF)
+ (UIImage *)sd_animatedGIFWithData:(NSData *)data {
if (!data) {
return nil;
}
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
size_t count = CGImageSourceGetCount(source);
UIImage *animatedImage;
if (count <= 1) {
animatedImage = [[UIImage alloc] initWithData:data];
}
else {
NSMutableArray *images = [NSMutableArray array];
NSTimeInterval duration = 0.0f;
for (size_t i = 0; i < count; i++) {
CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
if (!image) {
continue;
}
duration += [self sd_frameDurationAtIndex:i source:source];
[images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]];
CGImageRelease(image);
}
if (!duration) {
duration = (1.0f / 10.0f) * count;
}
animatedImage = [UIImage animatedImageWithImages:images duration:duration];
}
CFRelease(source);
return animatedImage;
}
+ (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
float frameDuration = 0.1f;
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
if (delayTimeUnclampedProp) {
frameDuration = [delayTimeUnclampedProp floatValue];
}
else {
NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];
if (delayTimeProp) {
frameDuration = [delayTimeProp floatValue];
}
}
// 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 < 0.011f) {
frameDuration = 0.100f;
}
CFRelease(cfFrameProperties);
return frameDuration;
}
+ (UIImage *)sd_animatedGIFNamed:(NSString *)name {
CGFloat scale = [UIScreen mainScreen].scale;
if (scale > 1.0f) {
NSString *retinaPath = [[NSBundle mainBundle] pathForResource:[name stringByAppendingString:@"@2x"] ofType:@"gif"];
NSData *data = [NSData dataWithContentsOfFile:retinaPath];
if (data) {
return [UIImage sd_animatedGIFWithData:data];
}
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:@"gif"];
data = [NSData dataWithContentsOfFile:path];
if (data) {
return [UIImage sd_animatedGIFWithData:data];
}
return [UIImage imageNamed:name];
}
else {
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:@"gif"];
NSData *data = [NSData dataWithContentsOfFile:path];
if (data) {
return [UIImage sd_animatedGIFWithData:data];
}
return [UIImage imageNamed:name];
}
}
- (UIImage *)sd_animatedImageByScalingAndCroppingToSize:(CGSize)size {
if (CGSizeEqualToSize(self.size, size) || CGSizeEqualToSize(size, CGSizeZero)) {
return self;
}
CGSize scaledSize = size;
CGPoint thumbnailPoint = CGPointZero;
CGFloat widthFactor = size.width / self.size.width;
CGFloat heightFactor = size.height / self.size.height;
CGFloat scaleFactor = (widthFactor > heightFactor) ? widthFactor : heightFactor;
scaledSize.width = self.size.width * scaleFactor;
scaledSize.height = self.size.height * scaleFactor;
if (widthFactor > heightFactor) {
thumbnailPoint.y = (size.height - scaledSize.height) * 0.5;
}
else if (widthFactor < heightFactor) {
thumbnailPoint.x = (size.width - scaledSize.width) * 0.5;
}
NSMutableArray *scaledImages = [NSMutableArray array];
for (UIImage *image in self.images) {
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
[image drawInRect:CGRectMake(thumbnailPoint.x, thumbnailPoint.y, scaledSize.width, scaledSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
[scaledImages addObject:newImage];
UIGraphicsEndImageContext();
}
return [UIImage animatedImageWithImages:scaledImages duration:self.duration];
}
@end
//
// UIImage+MultiFormat.h
// SDWebImage
//
// Created by Olivier Poitrey on 07/06/13.
// Copyright (c) 2013 Dailymotion. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIImage (MultiFormat)
+ (UIImage *)sd_imageWithData:(NSData *)data;
@end
//
// UIImage+MultiFormat.m
// SDWebImage
//
// Created by Olivier Poitrey on 07/06/13.
// Copyright (c) 2013 Dailymotion. All rights reserved.
//
#import "UIImage+MultiFormat.h"
#import "UIImage+GIF.h"
#import "NSData+ImageContentType.h"
#import <ImageIO/ImageIO.h>
#ifdef SD_WEBP
#import "UIImage+WebP.h"
#endif
@implementation UIImage (MultiFormat)
+ (UIImage *)sd_imageWithData:(NSData *)data {
if (!data) {
return nil;
}
UIImage *image;
NSString *imageContentType = [NSData sd_contentTypeForImageData:data];
if ([imageContentType isEqualToString:@"image/gif"]) {
image = [UIImage sd_animatedGIFWithData:data];
}
#ifdef SD_WEBP
else if ([imageContentType isEqualToString:@"image/webp"])
{
image = [UIImage sd_imageWithWebPData:data];
}
#endif
else {
image = [[UIImage alloc] initWithData:data];
UIImageOrientation orientation = [self sd_imageOrientationFromImageData:data];
if (orientation != UIImageOrientationUp) {
image = [UIImage imageWithCGImage:image.CGImage
scale:image.scale
orientation:orientation];
}
}
return image;
}
+(UIImageOrientation)sd_imageOrientationFromImageData:(NSData *)imageData {
UIImageOrientation result = UIImageOrientationUp;
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
if (imageSource) {
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
if (properties) {
CFTypeRef val;
int exifOrientation;
val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
if (val) {
CFNumberGetValue(val, kCFNumberIntType, &exifOrientation);
result = [self sd_exifOrientationToiOSOrientation:exifOrientation];
} // else - if it's not set it remains at up
CFRelease((CFTypeRef) properties);
} else {
//NSLog(@"NO PROPERTIES, FAIL");
}
CFRelease(imageSource);
}
return result;
}
#pragma mark EXIF orientation tag converter
// Convert an EXIF image orientation to an iOS one.
// reference see here: http://sylvana.net/jpegcrop/exif_orientation.html
+ (UIImageOrientation) sd_exifOrientationToiOSOrientation:(int)exifOrientation {
UIImageOrientation orientation = UIImageOrientationUp;
switch (exifOrientation) {
case 1:
orientation = UIImageOrientationUp;
break;
case 3:
orientation = UIImageOrientationDown;
break;
case 8:
orientation = UIImageOrientationLeft;
break;
case 6:
orientation = UIImageOrientationRight;
break;
case 2:
orientation = UIImageOrientationUpMirrored;
break;
case 4:
orientation = UIImageOrientationDownMirrored;
break;
case 5:
orientation = UIImageOrientationLeftMirrored;
break;
case 7:
orientation = UIImageOrientationRightMirrored;
break;
default:
break;
}
return orientation;
}
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <UIKit/UIKit.h>
#import "SDWebImageCompat.h"
#import "SDWebImageManager.h"
/**
* Integrates SDWebImage async downloading and caching of remote images with UIImageView for highlighted state.
*/
@interface UIImageView (HighlightedWebCache)
/**
* Set the imageView `highlightedImage` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
*/
- (void)sd_setHighlightedImageWithURL:(NSURL *)url;
/**
* Set the imageView `highlightedImage` with an `url` and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
*/
- (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options;
/**
* Set the imageView `highlightedImage` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setHighlightedImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock;
/**
* Set the imageView `highlightedImage` with an `url` and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;
/**
* Set the imageView `highlightedImage` with an `url` and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param progressBlock A block called while image is downloading
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;
/**
* Cancel the current download
*/
- (void)sd_cancelCurrentHighlightedImageLoad;
@end
@interface UIImageView (HighlightedWebCacheDeprecated)
- (void)setHighlightedImageWithURL:(NSURL *)url __deprecated_msg("Method deprecated. Use `sd_setHighlightedImageWithURL:`");
- (void)setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options __deprecated_msg("Method deprecated. Use `sd_setHighlightedImageWithURL:options:`");
- (void)setHighlightedImageWithURL:(NSURL *)url completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setHighlightedImageWithURL:completed:`");
- (void)setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setHighlightedImageWithURL:options:completed:`");
- (void)setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setHighlightedImageWithURL:options:progress:completed:`");
- (void)cancelCurrentHighlightedImageLoad __deprecated_msg("Use `sd_cancelCurrentHighlightedImageLoad`");
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "UIImageView+HighlightedWebCache.h"
#import "UIView+WebCacheOperation.h"
#define UIImageViewHighlightedWebCacheOperationKey @"highlightedImage"
@implementation UIImageView (HighlightedWebCache)
- (void)sd_setHighlightedImageWithURL:(NSURL *)url {
[self sd_setHighlightedImageWithURL:url options:0 progress:nil completed:nil];
}
- (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options {
[self sd_setHighlightedImageWithURL:url options:options progress:nil completed:nil];
}
- (void)sd_setHighlightedImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setHighlightedImageWithURL:url options:0 progress:nil completed:completedBlock];
}
- (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setHighlightedImageWithURL:url options:options progress:nil completed:completedBlock];
}
- (void)sd_setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_cancelCurrentHighlightedImageLoad];
if (url) {
__weak __typeof(self)wself = self;
id<SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (!wself) return;
dispatch_main_sync_safe (^
{
if (!wself) return;
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
{
completedBlock(image, error, cacheType, url);
return;
}
else if (image) {
wself.highlightedImage = image;
[wself setNeedsLayout];
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
[self sd_setImageLoadOperation:operation forKey:UIImageViewHighlightedWebCacheOperationKey];
} else {
dispatch_main_async_safe(^{
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
if (completedBlock) {
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
- (void)sd_cancelCurrentHighlightedImageLoad {
[self sd_cancelImageLoadOperationWithKey:UIImageViewHighlightedWebCacheOperationKey];
}
@end
@implementation UIImageView (HighlightedWebCacheDeprecated)
- (void)setHighlightedImageWithURL:(NSURL *)url {
[self sd_setHighlightedImageWithURL:url options:0 progress:nil completed:nil];
}
- (void)setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options {
[self sd_setHighlightedImageWithURL:url options:options progress:nil completed:nil];
}
- (void)setHighlightedImageWithURL:(NSURL *)url completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setHighlightedImageWithURL:url options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setHighlightedImageWithURL:url options:options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)setHighlightedImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setHighlightedImageWithURL:url options:0 progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)cancelCurrentHighlightedImageLoad {
[self sd_cancelCurrentHighlightedImageLoad];
}
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
#import "SDWebImageManager.h"
/**
* Integrates SDWebImage async downloading and caching of remote images with UIImageView.
*
* Usage with a UITableViewCell sub-class:
*
* @code
#import <SDWebImage/UIImageView+WebCache.h>
...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = @"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier]
autorelease];
}
// Here we use the provided sd_setImageWithURL: method to load the web image
// Ensure you use a placeholder image otherwise cells will be initialized with no image
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://example.com/image.jpg"]
placeholderImage:[UIImage imageNamed:@"placeholder"]];
cell.textLabel.text = @"My Text";
return cell;
}
* @endcode
*/
@interface UIImageView (WebCache)
/**
* Get the current image URL.
*
* Note that because of the limitations of categories this property can get out of sync
* if you use sd_setImage: directly.
*/
- (NSURL *)sd_imageURL;
/**
* Set the imageView `image` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
*/
- (void)sd_setImageWithURL:(NSURL *)url;
/**
* Set the imageView `image` with an `url` and a placeholder.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @see sd_setImageWithURL:placeholderImage:options:
*/
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder;
/**
* Set the imageView `image` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
*/
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;
/**
* Set the imageView `image` with an `url`.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock;
/**
* Set the imageView `image` with an `url`, placeholder.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock;
/**
* Set the imageView `image` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock;
/**
* Set the imageView `image` with an `url`, placeholder and custom options.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param progressBlock A block called while image is downloading
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;
/**
* Set the imageView `image` with an `url` and optionally a placeholder image.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param progressBlock A block called while image is downloading
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
*/
- (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;
/**
* Download an array of images and starts them in an animation loop
*
* @param arrayOfURLs An array of NSURL
*/
- (void)sd_setAnimationImagesWithURLs:(NSArray *)arrayOfURLs;
/**
* Cancel the current download
*/
- (void)sd_cancelCurrentImageLoad;
- (void)sd_cancelCurrentAnimationImagesLoad;
/**
* Show activity UIActivityIndicatorView
*/
- (void)setShowActivityIndicatorView:(BOOL)show;
/**
* set desired UIActivityIndicatorViewStyle
*
* @param style The style of the UIActivityIndicatorView
*/
- (void)setIndicatorStyle:(UIActivityIndicatorViewStyle)style;
@end
@interface UIImageView (WebCacheDeprecated)
- (NSURL *)imageURL __deprecated_msg("Use `sd_imageURL`");
- (void)setImageWithURL:(NSURL *)url __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:`");
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:placeholderImage:`");
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:placeholderImage:options`");
- (void)setImageWithURL:(NSURL *)url completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:completed:`");
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:placeholderImage:completed:`");
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:placeholderImage:options:completed:`");
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithURL:placeholderImage:options:progress:completed:`");
- (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url andPlaceholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock __deprecated_msg("Method deprecated. Use `sd_setImageWithPreviousCachedImageWithURL:placeholderImage:options:progress:completed:`");
- (void)setAnimationImagesWithURLs:(NSArray *)arrayOfURLs __deprecated_msg("Use `sd_setAnimationImagesWithURLs:`");
- (void)cancelCurrentArrayLoad __deprecated_msg("Use `sd_cancelCurrentAnimationImagesLoad`");
- (void)cancelCurrentImageLoad __deprecated_msg("Use `sd_cancelCurrentImageLoad`");
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "UIImageView+WebCache.h"
#import "objc/runtime.h"
#import "UIView+WebCacheOperation.h"
static char imageURLKey;
static char TAG_ACTIVITY_INDICATOR;
static char TAG_ACTIVITY_STYLE;
static char TAG_ACTIVITY_SHOW;
@implementation UIImageView (WebCache)
- (void)sd_setImageWithURL:(NSURL *)url {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
}
- (void)sd_setImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];
}
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
}
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock];
}
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_cancelCurrentImageLoad];
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
self.image = placeholder;
});
}
if (url) {
// check if activityView is enabled or not
if ([self showActivityIndicatorView]) {
[self addActivityIndicator];
}
__weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
[wself removeActivityIndicator];
if (!wself) return;
dispatch_main_sync_safe(^{
if (!wself) return;
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
{
completedBlock(image, error, cacheType, url);
return;
}
else if (image) {
wself.image = image;
[wself setNeedsLayout];
} else {
if ((options & SDWebImageDelayPlaceholder)) {
wself.image = placeholder;
[wself setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
} else {
dispatch_main_async_safe(^{
[self removeActivityIndicator];
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
- (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:url];
UIImage *lastPreviousCachedImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:key];
[self sd_setImageWithURL:url placeholderImage:lastPreviousCachedImage ?: placeholder options:options progress:progressBlock completed:completedBlock];
}
- (NSURL *)sd_imageURL {
return objc_getAssociatedObject(self, &imageURLKey);
}
- (void)sd_setAnimationImagesWithURLs:(NSArray *)arrayOfURLs {
[self sd_cancelCurrentAnimationImagesLoad];
__weak __typeof(self)wself = self;
NSMutableArray *operationsArray = [[NSMutableArray alloc] init];
for (NSURL *logoImageURL in arrayOfURLs) {
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:logoImageURL options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (!wself) return;
dispatch_main_sync_safe(^{
__strong UIImageView *sself = wself;
[sself stopAnimating];
if (sself && image) {
NSMutableArray *currentImages = [[sself animationImages] mutableCopy];
if (!currentImages) {
currentImages = [[NSMutableArray alloc] init];
}
[currentImages addObject:image];
sself.animationImages = currentImages;
[sself setNeedsLayout];
}
[sself startAnimating];
});
}];
[operationsArray addObject:operation];
}
[self sd_setImageLoadOperation:[NSArray arrayWithArray:operationsArray] forKey:@"UIImageViewAnimationImages"];
}
- (void)sd_cancelCurrentImageLoad {
[self sd_cancelImageLoadOperationWithKey:@"UIImageViewImageLoad"];
}
- (void)sd_cancelCurrentAnimationImagesLoad {
[self sd_cancelImageLoadOperationWithKey:@"UIImageViewAnimationImages"];
}
#pragma mark -
- (UIActivityIndicatorView *)activityIndicator {
return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
}
- (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
}
- (void)setShowActivityIndicatorView:(BOOL)show{
objc_setAssociatedObject(self, &TAG_ACTIVITY_SHOW, [NSNumber numberWithBool:show], OBJC_ASSOCIATION_RETAIN);
}
- (BOOL)showActivityIndicatorView{
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_SHOW) boolValue];
}
- (void)setIndicatorStyle:(UIActivityIndicatorViewStyle)style{
objc_setAssociatedObject(self, &TAG_ACTIVITY_STYLE, [NSNumber numberWithInt:style], OBJC_ASSOCIATION_RETAIN);
}
- (int)getIndicatorStyle{
return [objc_getAssociatedObject(self, &TAG_ACTIVITY_STYLE) intValue];
}
- (void)addActivityIndicator {
if (!self.activityIndicator) {
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:[self getIndicatorStyle]];
self.activityIndicator.translatesAutoresizingMaskIntoConstraints = NO;
dispatch_main_async_safe(^{
[self addSubview:self.activityIndicator];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self.activityIndicator
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
});
}
dispatch_main_async_safe(^{
[self.activityIndicator startAnimating];
});
}
- (void)removeActivityIndicator {
if (self.activityIndicator) {
[self.activityIndicator removeFromSuperview];
self.activityIndicator = nil;
}
}
@end
@implementation UIImageView (WebCacheDeprecated)
- (NSURL *)imageURL {
return [self sd_imageURL];
}
- (void)setImageWithURL:(NSURL *)url {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options {
[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
}
- (void)setImageWithURL:(NSURL *)url completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock {
[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, cacheType);
}
}];
}
- (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url andPlaceholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
[self sd_setImageWithPreviousCachedImageWithURL:url placeholderImage:placeholder options:options progress:progressBlock completed:completedBlock];
}
- (void)cancelCurrentArrayLoad {
[self sd_cancelCurrentAnimationImagesLoad];
}
- (void)cancelCurrentImageLoad {
[self sd_cancelCurrentImageLoad];
}
- (void)setAnimationImagesWithURLs:(NSArray *)arrayOfURLs {
[self sd_setAnimationImagesWithURLs:arrayOfURLs];
}
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <UIKit/UIKit.h>
#import "SDWebImageManager.h"
@interface UIView (WebCacheOperation)
/**
* Set the image load operation (storage in a UIView based dictionary)
*
* @param operation the operation
* @param key key for storing the operation
*/
- (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key;
/**
* Cancel all operations for the current UIView and key
*
* @param key key for identifying the operations
*/
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key;
/**
* Just remove the operations corresponding to the current UIView and key without cancelling them
*
* @param key key for identifying the operations
*/
- (void)sd_removeImageLoadOperationWithKey:(NSString *)key;
@end
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "UIView+WebCacheOperation.h"
#import "objc/runtime.h"
static char loadOperationKey;
@implementation UIView (WebCacheOperation)
- (NSMutableDictionary *)operationDictionary {
NSMutableDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
if (operations) {
return operations;
}
operations = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations;
}
- (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key {
[self sd_cancelImageLoadOperationWithKey:key];
NSMutableDictionary *operationDictionary = [self operationDictionary];
[operationDictionary setObject:operation forKey:key];
}
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key {
// Cancel in progress downloader from queue
NSMutableDictionary *operationDictionary = [self operationDictionary];
id operations = [operationDictionary objectForKey:key];
if (operations) {
if ([operations isKindOfClass:[NSArray class]]) {
for (id <SDWebImageOperation> operation in operations) {
if (operation) {
[operation cancel];
}
}
} else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
[(id<SDWebImageOperation>) operations cancel];
}
[operationDictionary removeObjectForKey:key];
}
}
- (void)sd_removeImageLoadOperationWithKey:(NSString *)key {
NSMutableDictionary *operationDictionary = [self operationDictionary];
[operationDictionary removeObjectForKey:key];
}
@end
#import <Foundation/Foundation.h>
@interface PodsDummy_Base64nl : NSObject
@end
@implementation PodsDummy_Base64nl
@end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#import "Base64.h"
FOUNDATION_EXPORT double Base64nlVersionNumber;
FOUNDATION_EXPORT const unsigned char Base64nlVersionString[];
framework module Base64nl {
umbrella header "Base64nl-umbrella.h"
export *
module * { export * }
}
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/Base64nl
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.2.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
#import <Foundation/Foundation.h>
@interface PodsDummy_GMFoundation : NSObject
@end
@implementation PodsDummy_GMFoundation
@end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#import "GMFont.h"
#import "NSAttributedString+GMSize.h"
#import "NSDate+DateFormat.h"
#import "NSFileManager+FolderSize.h"
#import "NSNull+Empty.h"
#import "NSObject+KeyboardAnimation.h"
#import "NSString+DateFormat.h"
#import "NSString+Encrypt.h"
#import "NSString+GM.h"
FOUNDATION_EXPORT double GMFoundationVersionNumber;
FOUNDATION_EXPORT const unsigned char GMFoundationVersionString[];
framework module GMFoundation {
umbrella header "GMFoundation-umbrella.h"
export *
module * { export * }
}
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/GMFoundation
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Base64nl"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.0.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
#import <Foundation/Foundation.h>
@interface PodsDummy_GMKit : NSObject
@end
@implementation PodsDummy_GMKit
@end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#import "UIDevice+Resolutions.h"
#import "UIImage+GM.h"
#import "UILabel+CopyExtern.h"
#import "UITextView+Keyboard.h"
#import "UIView+Layout.h"
#import "UIView+LineWithAutolayout.h"
#import "UIViewController+ChildControllerSwitch.h"
#import "UINavigationController+FDFullscreenPopGesture.h"
#import "GMAnnotation.h"
#import "GMButton.h"
#import "GMCollectionView.h"
#import "GMCollectionViewCell.h"
#import "GMControl.h"
#import "GMDatePickerView.h"
#import "GMEmptyView.h"
#import "GMHighlightLabel.h"
#import "GMHorizontalLayoutButton.h"
#import "GMImageView.h"
#import "GMInfiniteScrollView.h"
#import "GMLabel.h"
#import "GMLoadingView.h"
#import "GMMarkStarView.h"
#import "GMPlaceholderTextContainer.h"
#import "GMPreviewCell.h"
#import "GMPreviewListView.h"
#import "GMRateView.h"
#import "GMScrollView.h"
#import "GMTableView.h"
#import "GMTableViewCell.h"
#import "GMTextField.h"
#import "GMTopSearchButton.h"
#import "GMVerticalLayoutButton.h"
#import "GMView.h"
#import "WYSegmentView.h"
FOUNDATION_EXPORT double GMKitVersionNumber;
FOUNDATION_EXPORT const unsigned char GMKitVersionString[];
framework module GMKit {
umbrella header "GMKit-umbrella.h"
export *
module * { export * }
}
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/GMKit
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Base64nl" "$PODS_CONFIGURATION_BUILD_DIR/GMFoundation" "$PODS_CONFIGURATION_BUILD_DIR/GMOCConstant" "$PODS_CONFIGURATION_BUILD_DIR/Masonry" "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
OTHER_LDFLAGS = -framework "CoreLocation" -framework "Foundation" -framework "MapKit" -framework "UIKit"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.7.11</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
#import <Foundation/Foundation.h>
@interface PodsDummy_GMOCConstant : NSObject
@end
@implementation PodsDummy_GMOCConstant
@end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#import "GMConstant.h"
#import "GMTheme.h"
FOUNDATION_EXPORT double GMOCConstantVersionNumber;
FOUNDATION_EXPORT const unsigned char GMOCConstantVersionString[];
framework module GMOCConstant {
umbrella header "GMOCConstant-umbrella.h"
export *
module * { export * }
}
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/GMOCConstant
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.0.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/GMPhobos
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Base64nl" "$PODS_CONFIGURATION_BUILD_DIR/GMCache" "$PODS_CONFIGURATION_BUILD_DIR/GMFoundation" "$PODS_CONFIGURATION_BUILD_DIR/GMKit" "$PODS_CONFIGURATION_BUILD_DIR/GMOCConstant" "$PODS_CONFIGURATION_BUILD_DIR/Masonry" "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage" "$PODS_CONFIGURATION_BUILD_DIR/TMCache"
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/GMCache" "$PODS_CONFIGURATION_BUILD_DIR/TMCache"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
OTHER_LDFLAGS = -l"z"
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
#import <Foundation/Foundation.h>
@interface PodsDummy_Masonry : NSObject
@end
@implementation PodsDummy_Masonry
@end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#import "MASCompositeConstraint.h"
#import "MASConstraint+Private.h"
#import "MASConstraint.h"
#import "MASConstraintMaker.h"
#import "MASLayoutConstraint.h"
#import "Masonry.h"
#import "MASUtilities.h"
#import "MASViewAttribute.h"
#import "MASViewConstraint.h"
#import "NSArray+MASAdditions.h"
#import "NSArray+MASShorthandAdditions.h"
#import "NSLayoutConstraint+MASDebugAdditions.h"
#import "View+MASAdditions.h"
#import "View+MASShorthandAdditions.h"
#import "ViewController+MASAdditions.h"
FOUNDATION_EXPORT double MasonryVersionNumber;
FOUNDATION_EXPORT const unsigned char MasonryVersionString[];
framework module Masonry {
umbrella header "Masonry-umbrella.h"
export *
module * { export * }
}
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/Masonry
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
OTHER_LDFLAGS = -framework "Foundation" -framework "UIKit"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
# Acknowledgements
This application makes use of the following third party libraries:
## Base64nl
Base64
Version 1.2, Feburary 7th, 2014
Copyright (C) 2012 Charcoal Design
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.
## GMCache
Copyright (c) 2016 wangyang <wangyang@wanmeizhensuo.com>
......@@ -50,59 +24,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## GMFoundation
Copyright (c) 2016 licong <1240690490@qq.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## GMKit
Copyright (c) 2016 北京更美互动信息科技有限公司
仅限北京更美互动信息科技有限公司内部使用
## GMOCConstant
Copyright (c) 2016 licong <1240690490@qq.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## GMPhobos
Copyright (c) 2016 licong <1240690490@qq.com>
......@@ -126,52 +47,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## Masonry
Copyright (c) 2011-2012 Masonry Team - https://github.com/Masonry
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## SDWebImage
Copyright (c) 2016 Olivier Poitrey rs@dailymotion.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## TMCache
Apache License
......
......@@ -12,38 +12,6 @@
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Base64
Version 1.2, Feburary 7th, 2014
Copyright (C) 2012 Charcoal Design
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.</string>
<key>License</key>
<string>zlib</string>
<key>Title</key>
<string>Base64nl</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2016 wangyang &lt;wangyang@wanmeizhensuo.com&gt;
......@@ -87,77 +55,6 @@ furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>GMFoundation</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2016 北京更美互动信息科技有限公司
仅限北京更美互动信息科技有限公司内部使用
</string>
<key>License</key>
<string>仅限北京更美互动信息科技有限公司内部使用</string>
<key>Title</key>
<string>GMKit</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2016 licong &lt;1240690490@qq.com&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>GMOCConstant</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2016 licong &lt;1240690490@qq.com&gt;
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
......@@ -173,64 +70,6 @@ THE SOFTWARE.
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2011-2012 Masonry Team - https://github.com/Masonry
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>Masonry</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2016 Olivier Poitrey rs@dailymotion.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</string>
<key>License</key>
<string>MIT</string>
<key>Title</key>
<string>SDWebImage</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string> Apache License
......
......@@ -84,24 +84,12 @@ strip_invalid_archs() {
if [[ "$CONFIGURATION" == "Debug" ]]; then
install_framework "$BUILT_PRODUCTS_DIR/Base64nl/Base64nl.framework"
install_framework "$BUILT_PRODUCTS_DIR/GMCache/GMCache.framework"
install_framework "$BUILT_PRODUCTS_DIR/GMFoundation/GMFoundation.framework"
install_framework "$BUILT_PRODUCTS_DIR/GMKit/GMKit.framework"
install_framework "$BUILT_PRODUCTS_DIR/GMOCConstant/GMOCConstant.framework"
install_framework "$BUILT_PRODUCTS_DIR/GMPhobos/GMPhobos.framework"
install_framework "$BUILT_PRODUCTS_DIR/Masonry/Masonry.framework"
install_framework "$BUILT_PRODUCTS_DIR/SDWebImage/SDWebImage.framework"
install_framework "$BUILT_PRODUCTS_DIR/TMCache/TMCache.framework"
fi
if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework "$BUILT_PRODUCTS_DIR/Base64nl/Base64nl.framework"
install_framework "$BUILT_PRODUCTS_DIR/GMCache/GMCache.framework"
install_framework "$BUILT_PRODUCTS_DIR/GMFoundation/GMFoundation.framework"
install_framework "$BUILT_PRODUCTS_DIR/GMKit/GMKit.framework"
install_framework "$BUILT_PRODUCTS_DIR/GMOCConstant/GMOCConstant.framework"
install_framework "$BUILT_PRODUCTS_DIR/GMPhobos/GMPhobos.framework"
install_framework "$BUILT_PRODUCTS_DIR/Masonry/Masonry.framework"
install_framework "$BUILT_PRODUCTS_DIR/SDWebImage/SDWebImage.framework"
install_framework "$BUILT_PRODUCTS_DIR/TMCache/TMCache.framework"
fi
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Base64nl" "$PODS_CONFIGURATION_BUILD_DIR/GMCache" "$PODS_CONFIGURATION_BUILD_DIR/GMFoundation" "$PODS_CONFIGURATION_BUILD_DIR/GMKit" "$PODS_CONFIGURATION_BUILD_DIR/GMOCConstant" "$PODS_CONFIGURATION_BUILD_DIR/GMPhobos" "$PODS_CONFIGURATION_BUILD_DIR/Masonry" "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage" "$PODS_CONFIGURATION_BUILD_DIR/TMCache"
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/GMCache" "$PODS_CONFIGURATION_BUILD_DIR/GMPhobos" "$PODS_CONFIGURATION_BUILD_DIR/TMCache"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Base64nl/Base64nl.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMCache/GMCache.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMFoundation/GMFoundation.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMKit/GMKit.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMOCConstant/GMOCConstant.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMPhobos/GMPhobos.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/Masonry/Masonry.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage/SDWebImage.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/TMCache/TMCache.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "Base64nl" -framework "GMCache" -framework "GMFoundation" -framework "GMKit" -framework "GMOCConstant" -framework "GMPhobos" -framework "Masonry" -framework "SDWebImage" -framework "TMCache"
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMCache/GMCache.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMPhobos/GMPhobos.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/TMCache/TMCache.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "GMCache" -framework "GMPhobos" -framework "TMCache"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}/Pods
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Base64nl" "$PODS_CONFIGURATION_BUILD_DIR/GMCache" "$PODS_CONFIGURATION_BUILD_DIR/GMFoundation" "$PODS_CONFIGURATION_BUILD_DIR/GMKit" "$PODS_CONFIGURATION_BUILD_DIR/GMOCConstant" "$PODS_CONFIGURATION_BUILD_DIR/GMPhobos" "$PODS_CONFIGURATION_BUILD_DIR/Masonry" "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage" "$PODS_CONFIGURATION_BUILD_DIR/TMCache"
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/GMCache" "$PODS_CONFIGURATION_BUILD_DIR/GMPhobos" "$PODS_CONFIGURATION_BUILD_DIR/TMCache"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Base64nl/Base64nl.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMCache/GMCache.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMFoundation/GMFoundation.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMKit/GMKit.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMOCConstant/GMOCConstant.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMPhobos/GMPhobos.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/Masonry/Masonry.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage/SDWebImage.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/TMCache/TMCache.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "Base64nl" -framework "GMCache" -framework "GMFoundation" -framework "GMKit" -framework "GMOCConstant" -framework "GMPhobos" -framework "Masonry" -framework "SDWebImage" -framework "TMCache"
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMCache/GMCache.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMPhobos/GMPhobos.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/TMCache/TMCache.framework/Headers"
OTHER_LDFLAGS = $(inherited) -framework "GMCache" -framework "GMPhobos" -framework "TMCache"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}/Pods
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Base64nl" "$PODS_CONFIGURATION_BUILD_DIR/GMCache" "$PODS_CONFIGURATION_BUILD_DIR/GMFoundation" "$PODS_CONFIGURATION_BUILD_DIR/GMKit" "$PODS_CONFIGURATION_BUILD_DIR/GMOCConstant" "$PODS_CONFIGURATION_BUILD_DIR/GMPhobos" "$PODS_CONFIGURATION_BUILD_DIR/Masonry" "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage" "$PODS_CONFIGURATION_BUILD_DIR/TMCache"
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/GMCache" "$PODS_CONFIGURATION_BUILD_DIR/GMPhobos" "$PODS_CONFIGURATION_BUILD_DIR/TMCache"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Base64nl/Base64nl.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMCache/GMCache.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMFoundation/GMFoundation.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMKit/GMKit.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMOCConstant/GMOCConstant.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMPhobos/GMPhobos.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/Masonry/Masonry.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage/SDWebImage.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/TMCache/TMCache.framework/Headers"
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMCache/GMCache.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMPhobos/GMPhobos.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/TMCache/TMCache.framework/Headers"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}/Pods
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Base64nl" "$PODS_CONFIGURATION_BUILD_DIR/GMCache" "$PODS_CONFIGURATION_BUILD_DIR/GMFoundation" "$PODS_CONFIGURATION_BUILD_DIR/GMKit" "$PODS_CONFIGURATION_BUILD_DIR/GMOCConstant" "$PODS_CONFIGURATION_BUILD_DIR/GMPhobos" "$PODS_CONFIGURATION_BUILD_DIR/Masonry" "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage" "$PODS_CONFIGURATION_BUILD_DIR/TMCache"
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/GMCache" "$PODS_CONFIGURATION_BUILD_DIR/GMPhobos" "$PODS_CONFIGURATION_BUILD_DIR/TMCache"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Base64nl/Base64nl.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMCache/GMCache.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMFoundation/GMFoundation.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMKit/GMKit.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMOCConstant/GMOCConstant.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMPhobos/GMPhobos.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/Masonry/Masonry.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage/SDWebImage.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/TMCache/TMCache.framework/Headers"
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMCache/GMCache.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GMPhobos/GMPhobos.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/TMCache/TMCache.framework/Headers"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}/Pods
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>3.7.6</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
#import <Foundation/Foundation.h>
@interface PodsDummy_SDWebImage : NSObject
@end
@implementation PodsDummy_SDWebImage
@end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#import "NSData+ImageContentType.h"
#import "SDImageCache.h"
#import "SDWebImageCompat.h"
#import "SDWebImageDecoder.h"
#import "SDWebImageDownloader.h"
#import "SDWebImageDownloaderOperation.h"
#import "SDWebImageManager.h"
#import "SDWebImageOperation.h"
#import "SDWebImagePrefetcher.h"
#import "UIButton+WebCache.h"
#import "UIImage+GIF.h"
#import "UIImage+MultiFormat.h"
#import "UIImageView+HighlightedWebCache.h"
#import "UIImageView+WebCache.h"
#import "UIView+WebCacheOperation.h"
FOUNDATION_EXPORT double SDWebImageVersionNumber;
FOUNDATION_EXPORT const unsigned char SDWebImageVersionString[];
framework module SDWebImage {
umbrella header "SDWebImage-umbrella.h"
export *
module * { export * }
}
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/SDWebImage
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
OTHER_LDFLAGS = -framework "ImageIO"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
......@@ -79,4 +79,9 @@
return compressedData;
}
- (void)testDeviceId {
NSString *result = [PhobosUtil deviceId];
XCTAssertTrue(result.length > 0, "PhobosUtil deviceId方法返回不正确");
}
@end
......@@ -29,7 +29,6 @@ Pod::Spec.new do |s|
s.source_files = 'Pod/Classes/**/*'
s.dependency 'GMCache','~> 0.1.1'
s.dependency 'GMKit'
s.library = 'z'
end
......@@ -12,7 +12,7 @@
#import "PhobosUtil.h"
#import "PhobosConfig.h"
#import "UIResponder+PhobosPV.h"
#import "UIDevice+Resolutions.h"
//#import "UIDevice+Resolutions.h"
#import "PhobosUtil.h"
static Phobos *sharedClient = nil;
......@@ -311,7 +311,7 @@ static NSString *sdkVersion = @"110";
NSMutableDictionary *deviceParams = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString],@"idfa",
[[[UIDevice currentDevice] identifierForVendor] UUIDString],@"idfv",
[UIDevice deviceId],@"device_id",
[PhobosUtil deviceId],@"device_id",
@"ios",@"device_type",
@"Apple",@"manufacturer",
@(self.gps.coordinate.latitude),@"lat",
......
......@@ -36,4 +36,5 @@ typedef void (^SendDataSuccessBlock)(NSInteger code);
+ (NSString *)getAppVersion;
+ (BOOL)isNonEmpty:(NSString *)string;
+ (NSData *)encodeJSON:(id)obj;
+ (NSString *)deviceId;
@end
......@@ -9,6 +9,7 @@
#import "PhobosUtil.h"
#import <zlib.h>
#import "PhobosConfig.h"
#import <AdSupport/AdSupport.h>
@implementation PhobosUtil
......@@ -182,4 +183,15 @@
return [str length] > 0;
}
+ (NSString *)deviceId {
NSString *idfa = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
NSString *idfv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
if (![idfa isEqualToString:@"00000000-0000-0000-0000-000000000000"]) {
return idfa;
}
return idfv;
}
@end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment