Commit 28bd1061 cgx

修复意见反馈输入问题

1 个父辈 d16606f8
正在显示 100 个修改的文件 包含 7458 行增加3426 行删除
......@@ -17,7 +17,6 @@
#import <DKNightVersion/DKNightVersion.h>
#import <Masonry/Masonry.h>
#import <YTKNetwork/YTKNetwork.h>
#import <YYModel/YYModel.h>
#import <YYWebImage/YYWebImage.h>
......
......@@ -53,9 +53,9 @@ static int AlbumColumnCount = 4;
[self getUnreadMessageRequest];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.feedTV endEditing:YES];
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.feedTV resignFirstResponder];
}
#pragma mark - MyFeedListControllerDelegate
......@@ -73,9 +73,6 @@ static int AlbumColumnCount = 4;
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
// 回车键盘消失
if ([text isEqualToString:@"\n"]) { [textView resignFirstResponder]; }
int textLength = (int)(textView.text.length - range.length + text.length);
[self dealWorldLimitAttTextWithChangeLength:textLength];
......@@ -122,10 +119,6 @@ static int AlbumColumnCount = 4;
}];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.feedTV endEditing:YES];
}
- (void)deleteBtnClick:(UIButton *)sender {
[_selectedPhotos removeObjectAtIndex:sender.tag];
[_selectedAssets removeObjectAtIndex:sender.tag];
......@@ -320,7 +313,6 @@ static int AlbumColumnCount = 4;
_feedTV.dk_textColorPicker = DKColorPickerWithColors(SubTitleColor, DarkTextColor, DSWhite);
_feedTV.font = SysFont(14);
_feedTV.delegate = self;
_feedTV.returnKeyType = UIReturnKeyDone;
[_feedTV cornerRadius:12];
}
return _feedTV;
......
......@@ -13,6 +13,7 @@
#import <UMCommon/UMCommon.h>
#import <UMShare/UMShare.h>
#import <AVFoundation/AVFoundation.h>
#import <IQKeyboardManager/IQKeyboardManager.h>
@interface AppDelegate () <WXApiDelegate>
@end
......@@ -59,6 +60,11 @@
[avSession setCategory:AVAudioSessionCategoryPlayback error:nil];
[avSession setActive:YES error:nil];
// 设置键盘
[[IQKeyboardManager sharedManager] setEnable:YES];
[[IQKeyboardManager sharedManager] setEnableAutoToolbar:YES];
[[IQKeyboardManager sharedManager] setShouldResignOnTouchOutside:YES];
return YES;
}
......
......@@ -2,7 +2,7 @@ platform :ios, '11.0'
target 'DreamSleep' do
use_frameworks!
pod 'YTKNetwork', '~> 3.0.6'
pod 'AFNetworking', '~> 4.0.1'
pod 'DKNightVersion', '~> 2.4.3'
pod 'MJRefresh', '~> 3.7.5'
pod 'Masonry', '~> 1.1.0'
......@@ -13,6 +13,7 @@ target 'DreamSleep' do
pod 'YYImage/WebP'
pod 'YYModel', '~> 1.0.4'
pod 'FreeStreamer', '~> 4.0.0'
pod 'IQKeyboardManager', '~> 6.5.10'
end
# AFNetworking (4.0.1)
......@@ -28,3 +29,4 @@ end
# YYModel (1.0.4)
# SDWebImage (5.12.5)(去掉)
# FreeStreamer(4.0.0)
# IQKeyboardManager(6.5.10)
PODS:
- AFNetworking (4.0.1):
- AFNetworking/NSURLSession (= 4.0.1)
- AFNetworking/Reachability (= 4.0.1)
- AFNetworking/Security (= 4.0.1)
- AFNetworking/Serialization (= 4.0.1)
- AFNetworking/UIKit (= 4.0.1)
- AFNetworking/NSURLSession (4.0.1):
- AFNetworking/Reachability
- AFNetworking/Security
......@@ -6,6 +12,8 @@ PODS:
- AFNetworking/Reachability (4.0.1)
- AFNetworking/Security (4.0.1)
- AFNetworking/Serialization (4.0.1)
- AFNetworking/UIKit (4.0.1):
- AFNetworking/NSURLSession
- DKNightVersion (2.4.3):
- DKNightVersion/Core (= 2.4.3)
- DKNightVersion/CoreAnimation (= 2.4.3)
......@@ -22,13 +30,12 @@ PODS:
- DOUAudioStreamer (0.2.16)
- FreeStreamer (4.0.0):
- Reachability (~> 3.0)
- IQKeyboardManager (6.5.10)
- lottie-ios (2.5.3)
- Masonry (1.1.0)
- MBProgressHUD (1.2.0)
- MJRefresh (3.7.5)
- Reachability (3.2)
- YTKNetwork (3.0.6):
- AFNetworking/NSURLSession (~> 4.0)
- YYCache (1.0.4)
- YYImage (1.0.4):
- YYImage/Core (= 1.0.4)
......@@ -41,14 +48,15 @@ PODS:
- YYImage
DEPENDENCIES:
- AFNetworking (~> 4.0.1)
- DKNightVersion (~> 2.4.3)
- DOUAudioStreamer (~> 0.2.16)
- FreeStreamer (~> 4.0.0)
- IQKeyboardManager (~> 6.5.10)
- lottie-ios (~> 2.5.3)
- Masonry (~> 1.1.0)
- MBProgressHUD (~> 1.2.0)
- MJRefresh (~> 3.7.5)
- YTKNetwork (~> 3.0.6)
- YYImage/WebP
- YYModel (~> 1.0.4)
- YYWebImage (~> 1.0.5)
......@@ -59,12 +67,12 @@ SPEC REPOS:
- DKNightVersion
- DOUAudioStreamer
- FreeStreamer
- IQKeyboardManager
- lottie-ios
- Masonry
- MBProgressHUD
- MJRefresh
- Reachability
- YTKNetwork
- YYCache
- YYImage
- YYModel
......@@ -75,17 +83,17 @@ SPEC CHECKSUMS:
DKNightVersion: eaa80cc4014b4bae7d4b535fd87ecc6a3c2767b3
DOUAudioStreamer: c503ba2ecb9a54ff7bda0eff66963ad224f3c7dc
FreeStreamer: 7e9c976045701ac2f7e9c14c17245203c37bf2ea
IQKeyboardManager: 45a1fa55c1a5b02c61ac0fd7fd5b62bb4ad20d97
lottie-ios: a50d5c0160425cd4b01b852bb9578963e6d92d31
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
MBProgressHUD: 3ee5efcc380f6a79a7cc9b363dd669c5e1ae7406
MJRefresh: fdf5e979eb406a0341468932d1dfc8b7f9fce961
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
YTKNetwork: c16be90b06be003de9e9cd0d3b187cc8eaf35c04
YYCache: 8105b6638f5e849296c71f331ff83891a4942952
YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54
YYModel: 2a7fdd96aaa4b86a824e26d0c517de8928c04b30
YYWebImage: 5f7f36aee2ae293f016d418c7d6ba05c4863e928
PODFILE CHECKSUM: d78d9f7fd55a2a7be3fae24d212bdd5eab78666c
PODFILE CHECKSUM: 597c449d3caf07f7d1329c26f74f21892c777293
COCOAPODS: 1.11.3
// AFNetworking.h
//
// Copyright (c) 2013 AFNetworking (http://afnetworking.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.
#import <Foundation/Foundation.h>
#import <Availability.h>
#import <TargetConditionals.h>
#ifndef _AFNETWORKING_
#define _AFNETWORKING_
#import "AFURLRequestSerialization.h"
#import "AFURLResponseSerialization.h"
#import "AFSecurityPolicy.h"
#if !TARGET_OS_WATCH
#import "AFNetworkReachabilityManager.h"
#endif
#import "AFURLSessionManager.h"
#import "AFHTTPSessionManager.h"
#endif /* _AFNETWORKING_ */
// AFAutoPurgingImageCache.h
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 <TargetConditionals.h>
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS || TARGET_OS_TV
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
The `AFImageCache` protocol defines a set of APIs for adding, removing and fetching images from a cache synchronously.
*/
@protocol AFImageCache <NSObject>
/**
Adds the image to the cache with the given identifier.
@param image The image to cache.
@param identifier The unique identifier for the image in the cache.
*/
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier;
/**
Removes the image from the cache matching the given identifier.
@param identifier The unique identifier for the image in the cache.
@return A BOOL indicating whether or not the image was removed from the cache.
*/
- (BOOL)removeImageWithIdentifier:(NSString *)identifier;
/**
Removes all images from the cache.
@return A BOOL indicating whether or not all images were removed from the cache.
*/
- (BOOL)removeAllImages;
/**
Returns the image in the cache associated with the given identifier.
@param identifier The unique identifier for the image in the cache.
@return An image for the matching identifier, or nil.
*/
- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier;
@end
/**
The `ImageRequestCache` protocol extends the `ImageCache` protocol by adding methods for adding, removing and fetching images from a cache given an `NSURLRequest` and additional identifier.
*/
@protocol AFImageRequestCache <AFImageCache>
/**
Asks if the image should be cached using an identifier created from the request and additional identifier.
@param image The image to be cached.
@param request The unique URL request identifing the image asset.
@param identifier The additional identifier to apply to the URL request to identify the image.
@return A BOOL indicating whether or not the image should be added to the cache. YES will cache, NO will prevent caching.
*/
- (BOOL)shouldCacheImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;
/**
Adds the image to the cache using an identifier created from the request and additional identifier.
@param image The image to cache.
@param request The unique URL request identifing the image asset.
@param identifier The additional identifier to apply to the URL request to identify the image.
*/
- (void)addImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;
/**
Removes the image from the cache using an identifier created from the request and additional identifier.
@param request The unique URL request identifing the image asset.
@param identifier The additional identifier to apply to the URL request to identify the image.
@return A BOOL indicating whether or not all images were removed from the cache.
*/
- (BOOL)removeImageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;
/**
Returns the image from the cache associated with an identifier created from the request and additional identifier.
@param request The unique URL request identifing the image asset.
@param identifier The additional identifier to apply to the URL request to identify the image.
@return An image for the matching request and identifier, or nil.
*/
- (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier;
@end
/**
The `AutoPurgingImageCache` in an in-memory image cache used to store images up to a given memory capacity. When the memory capacity is reached, the image cache is sorted by last access date, then the oldest image is continuously purged until the preferred memory usage after purge is met. Each time an image is accessed through the cache, the internal access date of the image is updated.
*/
@interface AFAutoPurgingImageCache : NSObject <AFImageRequestCache>
/**
The total memory capacity of the cache in bytes.
*/
@property (nonatomic, assign) UInt64 memoryCapacity;
/**
The preferred memory usage after purge in bytes. During a purge, images will be purged until the memory capacity drops below this limit.
*/
@property (nonatomic, assign) UInt64 preferredMemoryUsageAfterPurge;
/**
The current total memory usage in bytes of all images stored within the cache.
*/
@property (nonatomic, assign, readonly) UInt64 memoryUsage;
/**
Initialies the `AutoPurgingImageCache` instance with default values for memory capacity and preferred memory usage after purge limit. `memoryCapcity` defaults to `100 MB`. `preferredMemoryUsageAfterPurge` defaults to `60 MB`.
@return The new `AutoPurgingImageCache` instance.
*/
- (instancetype)init;
/**
Initialies the `AutoPurgingImageCache` instance with the given memory capacity and preferred memory usage
after purge limit.
@param memoryCapacity The total memory capacity of the cache in bytes.
@param preferredMemoryCapacity The preferred memory usage after purge in bytes.
@return The new `AutoPurgingImageCache` instance.
*/
- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity;
@end
NS_ASSUME_NONNULL_END
#endif
// AFAutoPurgingImageCache.m
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 <TargetConditionals.h>
#if TARGET_OS_IOS || TARGET_OS_TV
#import "AFAutoPurgingImageCache.h"
@interface AFCachedImage : NSObject
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, copy) NSString *identifier;
@property (nonatomic, assign) UInt64 totalBytes;
@property (nonatomic, strong) NSDate *lastAccessDate;
@property (nonatomic, assign) UInt64 currentMemoryUsage;
@end
@implementation AFCachedImage
- (instancetype)initWithImage:(UIImage *)image identifier:(NSString *)identifier {
if (self = [self init]) {
self.image = image;
self.identifier = identifier;
CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale);
CGFloat bytesPerPixel = 4.0;
CGFloat bytesPerSize = imageSize.width * imageSize.height;
self.totalBytes = (UInt64)bytesPerPixel * (UInt64)bytesPerSize;
self.lastAccessDate = [NSDate date];
}
return self;
}
- (UIImage *)accessImage {
self.lastAccessDate = [NSDate date];
return self.image;
}
- (NSString *)description {
NSString *descriptionString = [NSString stringWithFormat:@"Idenfitier: %@ lastAccessDate: %@ ", self.identifier, self.lastAccessDate];
return descriptionString;
}
@end
@interface AFAutoPurgingImageCache ()
@property (nonatomic, strong) NSMutableDictionary <NSString* , AFCachedImage*> *cachedImages;
@property (nonatomic, assign) UInt64 currentMemoryUsage;
@property (nonatomic, strong) dispatch_queue_t synchronizationQueue;
@end
@implementation AFAutoPurgingImageCache
- (instancetype)init {
return [self initWithMemoryCapacity:100 * 1024 * 1024 preferredMemoryCapacity:60 * 1024 * 1024];
}
- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity {
if (self = [super init]) {
self.memoryCapacity = memoryCapacity;
self.preferredMemoryUsageAfterPurge = preferredMemoryCapacity;
self.cachedImages = [[NSMutableDictionary alloc] init];
NSString *queueName = [NSString stringWithFormat:@"com.alamofire.autopurgingimagecache-%@", [[NSUUID UUID] UUIDString]];
self.synchronizationQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(removeAllImages)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (UInt64)memoryUsage {
__block UInt64 result = 0;
dispatch_sync(self.synchronizationQueue, ^{
result = self.currentMemoryUsage;
});
return result;
}
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier {
dispatch_barrier_async(self.synchronizationQueue, ^{
AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier];
AFCachedImage *previousCachedImage = self.cachedImages[identifier];
if (previousCachedImage != nil) {
self.currentMemoryUsage -= previousCachedImage.totalBytes;
}
self.cachedImages[identifier] = cacheImage;
self.currentMemoryUsage += cacheImage.totalBytes;
});
dispatch_barrier_async(self.synchronizationQueue, ^{
if (self.currentMemoryUsage > self.memoryCapacity) {
UInt64 bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge;
NSMutableArray <AFCachedImage*> *sortedImages = [NSMutableArray arrayWithArray:self.cachedImages.allValues];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastAccessDate"
ascending:YES];
[sortedImages sortUsingDescriptors:@[sortDescriptor]];
UInt64 bytesPurged = 0;
for (AFCachedImage *cachedImage in sortedImages) {
[self.cachedImages removeObjectForKey:cachedImage.identifier];
bytesPurged += cachedImage.totalBytes;
if (bytesPurged >= bytesToPurge) {
break;
}
}
self.currentMemoryUsage -= bytesPurged;
}
});
}
- (BOOL)removeImageWithIdentifier:(NSString *)identifier {
__block BOOL removed = NO;
dispatch_barrier_sync(self.synchronizationQueue, ^{
AFCachedImage *cachedImage = self.cachedImages[identifier];
if (cachedImage != nil) {
[self.cachedImages removeObjectForKey:identifier];
self.currentMemoryUsage -= cachedImage.totalBytes;
removed = YES;
}
});
return removed;
}
- (BOOL)removeAllImages {
__block BOOL removed = NO;
dispatch_barrier_sync(self.synchronizationQueue, ^{
if (self.cachedImages.count > 0) {
[self.cachedImages removeAllObjects];
self.currentMemoryUsage = 0;
removed = YES;
}
});
return removed;
}
- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier {
__block UIImage *image = nil;
dispatch_sync(self.synchronizationQueue, ^{
AFCachedImage *cachedImage = self.cachedImages[identifier];
image = [cachedImage accessImage];
});
return image;
}
- (void)addImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
[self addImage:image withIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}
- (BOOL)removeImageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
return [self removeImageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}
- (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
return [self imageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}
- (NSString *)imageCacheKeyFromURLRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)additionalIdentifier {
NSString *key = request.URL.absoluteString;
if (additionalIdentifier != nil) {
key = [key stringByAppendingString:additionalIdentifier];
}
return key;
}
- (BOOL)shouldCacheImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier {
return YES;
}
@end
#endif
// AFImageDownloader.h
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 <TargetConditionals.h>
#if TARGET_OS_IOS || TARGET_OS_TV
#import <Foundation/Foundation.h>
#import "AFAutoPurgingImageCache.h"
#import "AFHTTPSessionManager.h"
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, AFImageDownloadPrioritization) {
AFImageDownloadPrioritizationFIFO,
AFImageDownloadPrioritizationLIFO
};
/**
The `AFImageDownloadReceipt` is an object vended by the `AFImageDownloader` when starting a data task. It can be used to cancel active tasks running on the `AFImageDownloader` session. As a general rule, image data tasks should be cancelled using the `AFImageDownloadReceipt` instead of calling `cancel` directly on the `task` itself. The `AFImageDownloader` is optimized to handle duplicate task scenarios as well as pending versus active downloads.
*/
@interface AFImageDownloadReceipt : NSObject
/**
The data task created by the `AFImageDownloader`.
*/
@property (nonatomic, strong) NSURLSessionDataTask *task;
/**
The unique identifier for the success and failure blocks when duplicate requests are made.
*/
@property (nonatomic, strong) NSUUID *receiptID;
@end
/** The `AFImageDownloader` class is responsible for downloading images in parallel on a prioritized queue. Incoming downloads are added to the front or back of the queue depending on the download prioritization. Each downloaded image is cached in the underlying `NSURLCache` as well as the in-memory image cache. By default, any download request with a cached image equivalent in the image cache will automatically be served the cached image representation.
*/
@interface AFImageDownloader : NSObject
/**
The image cache used to store all downloaded images in. `AFAutoPurgingImageCache` by default.
*/
@property (nonatomic, strong, nullable) id <AFImageRequestCache> imageCache;
/**
The `AFHTTPSessionManager` used to download images. By default, this is configured with an `AFImageResponseSerializer`, and a shared `NSURLCache` for all image downloads.
*/
@property (nonatomic, strong) AFHTTPSessionManager *sessionManager;
/**
Defines the order prioritization of incoming download requests being inserted into the queue. `AFImageDownloadPrioritizationFIFO` by default.
*/
@property (nonatomic, assign) AFImageDownloadPrioritization downloadPrioritization;
/**
The shared default instance of `AFImageDownloader` initialized with default values.
*/
+ (instancetype)defaultInstance;
/**
Creates a default `NSURLCache` with common usage parameter values.
@returns The default `NSURLCache` instance.
*/
+ (NSURLCache *)defaultURLCache;
/**
The default `NSURLSessionConfiguration` with common usage parameter values.
*/
+ (NSURLSessionConfiguration *)defaultURLSessionConfiguration;
/**
Default initializer
@return An instance of `AFImageDownloader` initialized with default values.
*/
- (instancetype)init;
/**
Initializer with specific `URLSessionConfiguration`
@param configuration The `NSURLSessionConfiguration` to be be used
@return An instance of `AFImageDownloader` initialized with default values and custom `NSURLSessionConfiguration`
*/
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration;
/**
Initializes the `AFImageDownloader` instance with the given session manager, download prioritization, maximum active download count and image cache.
@param sessionManager The session manager to use to download images.
@param downloadPrioritization The download prioritization of the download queue.
@param maximumActiveDownloads The maximum number of active downloads allowed at any given time. Recommend `4`.
@param imageCache The image cache used to store all downloaded images in.
@return The new `AFImageDownloader` instance.
*/
- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager
downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
maximumActiveDownloads:(NSInteger)maximumActiveDownloads
imageCache:(nullable id <AFImageRequestCache>)imageCache;
/**
Creates a data task using the `sessionManager` instance for the specified URL request.
If the same data task is already in the queue or currently being downloaded, the success and failure blocks are
appended to the already existing task. Once the task completes, all success or failure blocks attached to the
task are executed in the order they were added.
@param request The URL request.
@param success A block to be executed when the image data task finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the response parameter will be `nil`.
@param failure A block object to be executed when the image data task finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred.
@return The image download receipt for the data task if available. `nil` if the image is stored in the cache.
cache and the URL request cache policy allows the cache to be used.
*/
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;
/**
Creates a data task using the `sessionManager` instance for the specified URL request.
If the same data task is already in the queue or currently being downloaded, the success and failure blocks are
appended to the already existing task. Once the task completes, all success or failure blocks attached to the
task are executed in the order they were added.
@param request The URL request.
@param receiptID The identifier to use for the download receipt that will be created for this request. This must be a unique identifier that does not represent any other request.
@param success A block to be executed when the image data task finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the response parameter will be `nil`.
@param failure A block object to be executed when the image data task finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred.
@return The image download receipt for the data task if available. `nil` if the image is stored in the cache.
cache and the URL request cache policy allows the cache to be used.
*/
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
withReceiptID:(NSUUID *)receiptID
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;
/**
Cancels the data task in the receipt by removing the corresponding success and failure blocks and cancelling the data task if necessary.
If the data task is pending in the queue, it will be cancelled if no other success and failure blocks are registered with the data task. If the data task is currently executing or is already completed, the success and failure blocks are removed and will not be called when the task finishes.
@param imageDownloadReceipt The image download receipt to cancel.
*/
- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt;
@end
#endif
NS_ASSUME_NONNULL_END
// AFImageDownloader.m
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 <TargetConditionals.h>
#if TARGET_OS_IOS || TARGET_OS_TV
#import "AFImageDownloader.h"
#import "AFHTTPSessionManager.h"
@interface AFImageDownloaderResponseHandler : NSObject
@property (nonatomic, strong) NSUUID *uuid;
@property (nonatomic, copy) void (^successBlock)(NSURLRequest *, NSHTTPURLResponse *, UIImage *);
@property (nonatomic, copy) void (^failureBlock)(NSURLRequest *, NSHTTPURLResponse *, NSError *);
@end
@implementation AFImageDownloaderResponseHandler
- (instancetype)initWithUUID:(NSUUID *)uuid
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure {
if (self = [self init]) {
self.uuid = uuid;
self.successBlock = success;
self.failureBlock = failure;
}
return self;
}
- (NSString *)description {
return [NSString stringWithFormat: @"<AFImageDownloaderResponseHandler>UUID: %@", [self.uuid UUIDString]];
}
@end
@interface AFImageDownloaderMergedTask : NSObject
@property (nonatomic, strong) NSString *URLIdentifier;
@property (nonatomic, strong) NSUUID *identifier;
@property (nonatomic, strong) NSURLSessionDataTask *task;
@property (nonatomic, strong) NSMutableArray <AFImageDownloaderResponseHandler*> *responseHandlers;
@end
@implementation AFImageDownloaderMergedTask
- (instancetype)initWithURLIdentifier:(NSString *)URLIdentifier identifier:(NSUUID *)identifier task:(NSURLSessionDataTask *)task {
if (self = [self init]) {
self.URLIdentifier = URLIdentifier;
self.task = task;
self.identifier = identifier;
self.responseHandlers = [[NSMutableArray alloc] init];
}
return self;
}
- (void)addResponseHandler:(AFImageDownloaderResponseHandler *)handler {
[self.responseHandlers addObject:handler];
}
- (void)removeResponseHandler:(AFImageDownloaderResponseHandler *)handler {
[self.responseHandlers removeObject:handler];
}
@end
@implementation AFImageDownloadReceipt
- (instancetype)initWithReceiptID:(NSUUID *)receiptID task:(NSURLSessionDataTask *)task {
if (self = [self init]) {
self.receiptID = receiptID;
self.task = task;
}
return self;
}
@end
@interface AFImageDownloader ()
@property (nonatomic, strong) dispatch_queue_t synchronizationQueue;
@property (nonatomic, strong) dispatch_queue_t responseQueue;
@property (nonatomic, assign) NSInteger maximumActiveDownloads;
@property (nonatomic, assign) NSInteger activeRequestCount;
@property (nonatomic, strong) NSMutableArray *queuedMergedTasks;
@property (nonatomic, strong) NSMutableDictionary *mergedTasks;
@end
@implementation AFImageDownloader
+ (NSURLCache *)defaultURLCache {
NSUInteger memoryCapacity = 20 * 1024 * 1024; // 20MB
NSUInteger diskCapacity = 150 * 1024 * 1024; // 150MB
NSURL *cacheURL = [[[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:YES
error:nil]
URLByAppendingPathComponent:@"com.alamofire.imagedownloader"];
#if TARGET_OS_MACCATALYST
return [[NSURLCache alloc] initWithMemoryCapacity:memoryCapacity
diskCapacity:diskCapacity
directoryURL:cacheURL];
#else
return [[NSURLCache alloc] initWithMemoryCapacity:memoryCapacity
diskCapacity:diskCapacity
diskPath:[cacheURL path]];
#endif
}
+ (NSURLSessionConfiguration *)defaultURLSessionConfiguration {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
//TODO set the default HTTP headers
configuration.HTTPShouldSetCookies = YES;
configuration.HTTPShouldUsePipelining = NO;
configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
configuration.allowsCellularAccess = YES;
configuration.timeoutIntervalForRequest = 60.0;
configuration.URLCache = [AFImageDownloader defaultURLCache];
return configuration;
}
- (instancetype)init {
NSURLSessionConfiguration *defaultConfiguration = [self.class defaultURLSessionConfiguration];
return [self initWithSessionConfiguration:defaultConfiguration];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
sessionManager.responseSerializer = [AFImageResponseSerializer serializer];
return [self initWithSessionManager:sessionManager
downloadPrioritization:AFImageDownloadPrioritizationFIFO
maximumActiveDownloads:4
imageCache:[[AFAutoPurgingImageCache alloc] init]];
}
- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager
downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
maximumActiveDownloads:(NSInteger)maximumActiveDownloads
imageCache:(id <AFImageRequestCache>)imageCache {
if (self = [super init]) {
self.sessionManager = sessionManager;
self.downloadPrioritization = downloadPrioritization;
self.maximumActiveDownloads = maximumActiveDownloads;
self.imageCache = imageCache;
self.queuedMergedTasks = [[NSMutableArray alloc] init];
self.mergedTasks = [[NSMutableDictionary alloc] init];
self.activeRequestCount = 0;
NSString *name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.synchronizationqueue-%@", [[NSUUID UUID] UUIDString]];
self.synchronizationQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);
name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.responsequeue-%@", [[NSUUID UUID] UUIDString]];
self.responseQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
+ (instancetype)defaultInstance {
static AFImageDownloader *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
success:(void (^)(NSURLRequest * _Nonnull, NSHTTPURLResponse * _Nullable, UIImage * _Nonnull))success
failure:(void (^)(NSURLRequest * _Nonnull, NSHTTPURLResponse * _Nullable, NSError * _Nonnull))failure {
return [self downloadImageForURLRequest:request withReceiptID:[NSUUID UUID] success:success failure:failure];
}
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
withReceiptID:(nonnull NSUUID *)receiptID
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure {
__block NSURLSessionDataTask *task = nil;
dispatch_sync(self.synchronizationQueue, ^{
NSString *URLIdentifier = request.URL.absoluteString;
if (URLIdentifier == nil) {
if (failure) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
dispatch_async(dispatch_get_main_queue(), ^{
failure(request, nil, error);
});
}
return;
}
// 1) Append the success and failure blocks to a pre-existing request if it already exists
AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
if (existingMergedTask != nil) {
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure];
[existingMergedTask addResponseHandler:handler];
task = existingMergedTask.task;
return;
}
// 2) Attempt to load the image from the image cache if the cache policy allows it
switch (request.cachePolicy) {
case NSURLRequestUseProtocolCachePolicy:
case NSURLRequestReturnCacheDataElseLoad:
case NSURLRequestReturnCacheDataDontLoad: {
UIImage *cachedImage = [self.imageCache imageforRequest:request withAdditionalIdentifier:nil];
if (cachedImage != nil) {
if (success) {
dispatch_async(dispatch_get_main_queue(), ^{
success(request, nil, cachedImage);
});
}
return;
}
break;
}
default:
break;
}
// 3) Create the request and set up authentication, validation and response serialization
NSUUID *mergedTaskIdentifier = [NSUUID UUID];
NSURLSessionDataTask *createdTask;
__weak __typeof__(self) weakSelf = self;
createdTask = [self.sessionManager
dataTaskWithRequest:request
uploadProgress:nil
downloadProgress:nil
completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
dispatch_async(self.responseQueue, ^{
__strong __typeof__(weakSelf) strongSelf = weakSelf;
AFImageDownloaderMergedTask *mergedTask = [strongSelf safelyGetMergedTask:URLIdentifier];
if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {
mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier];
if (error) {
for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
if (handler.failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.failureBlock(request, (NSHTTPURLResponse *)response, error);
});
}
}
} else {
if ([strongSelf.imageCache shouldCacheImage:responseObject forRequest:request withAdditionalIdentifier:nil]) {
[strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];
}
for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
if (handler.successBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.successBlock(request, (NSHTTPURLResponse *)response, responseObject);
});
}
}
}
}
[strongSelf safelyDecrementActiveTaskCount];
[strongSelf safelyStartNextTaskIfNecessary];
});
}];
// 4) Store the response handler for use when the request completes
AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
success:success
failure:failure];
AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc]
initWithURLIdentifier:URLIdentifier
identifier:mergedTaskIdentifier
task:createdTask];
[mergedTask addResponseHandler:handler];
self.mergedTasks[URLIdentifier] = mergedTask;
// 5) Either start the request or enqueue it depending on the current active request count
if ([self isActiveRequestCountBelowMaximumLimit]) {
[self startMergedTask:mergedTask];
} else {
[self enqueueMergedTask:mergedTask];
}
task = mergedTask.task;
});
if (task) {
return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
} else {
return nil;
}
}
- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
dispatch_sync(self.synchronizationQueue, ^{
NSString *URLIdentifier = imageDownloadReceipt.task.originalRequest.URL.absoluteString;
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
NSUInteger index = [mergedTask.responseHandlers indexOfObjectPassingTest:^BOOL(AFImageDownloaderResponseHandler * _Nonnull handler, __unused NSUInteger idx, __unused BOOL * _Nonnull stop) {
return handler.uuid == imageDownloadReceipt.receiptID;
}];
if (index != NSNotFound) {
AFImageDownloaderResponseHandler *handler = mergedTask.responseHandlers[index];
[mergedTask removeResponseHandler:handler];
NSString *failureReason = [NSString stringWithFormat:@"ImageDownloader cancelled URL request: %@",imageDownloadReceipt.task.originalRequest.URL.absoluteString];
NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey:failureReason};
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo];
if (handler.failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
handler.failureBlock(imageDownloadReceipt.task.originalRequest, nil, error);
});
}
}
if (mergedTask.responseHandlers.count == 0) {
[mergedTask.task cancel];
[self removeMergedTaskWithURLIdentifier:URLIdentifier];
}
});
}
- (AFImageDownloaderMergedTask *)safelyRemoveMergedTaskWithURLIdentifier:(NSString *)URLIdentifier {
__block AFImageDownloaderMergedTask *mergedTask = nil;
dispatch_sync(self.synchronizationQueue, ^{
mergedTask = [self removeMergedTaskWithURLIdentifier:URLIdentifier];
});
return mergedTask;
}
//This method should only be called from safely within the synchronizationQueue
- (AFImageDownloaderMergedTask *)removeMergedTaskWithURLIdentifier:(NSString *)URLIdentifier {
AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
[self.mergedTasks removeObjectForKey:URLIdentifier];
return mergedTask;
}
- (void)safelyDecrementActiveTaskCount {
dispatch_sync(self.synchronizationQueue, ^{
if (self.activeRequestCount > 0) {
self.activeRequestCount -= 1;
}
});
}
- (void)safelyStartNextTaskIfNecessary {
dispatch_sync(self.synchronizationQueue, ^{
if ([self isActiveRequestCountBelowMaximumLimit]) {
while (self.queuedMergedTasks.count > 0) {
AFImageDownloaderMergedTask *mergedTask = [self dequeueMergedTask];
if (mergedTask.task.state == NSURLSessionTaskStateSuspended) {
[self startMergedTask:mergedTask];
break;
}
}
}
});
}
- (void)startMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
[mergedTask.task resume];
++self.activeRequestCount;
}
- (void)enqueueMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
switch (self.downloadPrioritization) {
case AFImageDownloadPrioritizationFIFO:
[self.queuedMergedTasks addObject:mergedTask];
break;
case AFImageDownloadPrioritizationLIFO:
[self.queuedMergedTasks insertObject:mergedTask atIndex:0];
break;
}
}
- (AFImageDownloaderMergedTask *)dequeueMergedTask {
AFImageDownloaderMergedTask *mergedTask = nil;
mergedTask = [self.queuedMergedTasks firstObject];
[self.queuedMergedTasks removeObject:mergedTask];
return mergedTask;
}
- (BOOL)isActiveRequestCountBelowMaximumLimit {
return self.activeRequestCount < self.maximumActiveDownloads;
}
- (AFImageDownloaderMergedTask *)safelyGetMergedTask:(NSString *)URLIdentifier {
__block AFImageDownloaderMergedTask *mergedTask;
dispatch_sync(self.synchronizationQueue, ^(){
mergedTask = self.mergedTasks[URLIdentifier];
});
return mergedTask;
}
@end
#endif
// AFNetworkActivityIndicatorManager.h
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 <Foundation/Foundation.h>
#import <TargetConditionals.h>
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
`AFNetworkActivityIndicatorManager` manages the state of the network activity indicator in the status bar. When enabled, it will listen for notifications indicating that a session task has started or finished, and start or stop animating the indicator accordingly. The number of active requests is incremented and decremented much like a stack or a semaphore, and the activity indicator will animate so long as that number is greater than zero.
You should enable the shared instance of `AFNetworkActivityIndicatorManager` when your application finishes launching. In `AppDelegate application:didFinishLaunchingWithOptions:` you can do so with the following code:
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
By setting `enabled` to `YES` for `sharedManager`, the network activity indicator will show and hide automatically as requests start and finish. You should not ever need to call `incrementActivityCount` or `decrementActivityCount` yourself.
See the Apple Human Interface Guidelines section about the Network Activity Indicator for more information:
http://developer.apple.com/library/iOS/#documentation/UserExperience/Conceptual/MobileHIG/UIElementGuidelines/UIElementGuidelines.html#//apple_ref/doc/uid/TP40006556-CH13-SW44
*/
NS_EXTENSION_UNAVAILABLE_IOS("Use view controller based solutions where appropriate instead.")
@interface AFNetworkActivityIndicatorManager : NSObject
/**
A Boolean value indicating whether the manager is enabled.
If YES, the manager will change status bar network activity indicator according to network operation notifications it receives. The default value is NO.
*/
@property (nonatomic, assign, getter = isEnabled) BOOL enabled;
/**
A Boolean value indicating whether the network activity indicator manager is currently active.
*/
@property (readonly, nonatomic, assign, getter=isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible;
/**
A time interval indicating the minimum duration of networking activity that should occur before the activity indicator is displayed. The default value 1 second. If the network activity indicator should be displayed immediately when network activity occurs, this value should be set to 0 seconds.
Apple's HIG describes the following:
> Display the network activity indicator to provide feedback when your app accesses the network for more than a couple of seconds. If the operation finishes sooner than that, you don’t have to show the network activity indicator, because the indicator is likely to disappear before users notice its presence.
*/
@property (nonatomic, assign) NSTimeInterval activationDelay;
/**
A time interval indicating the duration of time of no networking activity required before the activity indicator is disabled. This allows for continuous display of the network activity indicator across multiple requests. The default value is 0.17 seconds.
*/
@property (nonatomic, assign) NSTimeInterval completionDelay;
/**
Returns the shared network activity indicator manager object for the system.
@return The systemwide network activity indicator manager.
*/
+ (instancetype)sharedManager;
/**
Increments the number of active network requests. If this number was zero before incrementing, this will start animating the status bar network activity indicator.
*/
- (void)incrementActivityCount;
/**
Decrements the number of active network requests. If this number becomes zero after decrementing, this will stop animating the status bar network activity indicator.
*/
- (void)decrementActivityCount;
/**
Set the a custom method to be executed when the network activity indicator manager should be hidden/shown. By default, this is null, and the UIApplication Network Activity Indicator will be managed automatically. If this block is set, it is the responsiblity of the caller to manager the network activity indicator going forward.
@param block A block to be executed when the network activity indicator status changes.
*/
- (void)setNetworkingActivityActionWithBlock:(nullable void (^)(BOOL networkActivityIndicatorVisible))block;
@end
NS_ASSUME_NONNULL_END
#endif
// AFNetworkActivityIndicatorManager.m
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 "AFNetworkActivityIndicatorManager.h"
#if TARGET_OS_IOS
#import "AFURLSessionManager.h"
typedef NS_ENUM(NSInteger, AFNetworkActivityManagerState) {
AFNetworkActivityManagerStateNotActive,
AFNetworkActivityManagerStateDelayingStart,
AFNetworkActivityManagerStateActive,
AFNetworkActivityManagerStateDelayingEnd
};
static NSTimeInterval const kDefaultAFNetworkActivityManagerActivationDelay = 1.0;
static NSTimeInterval const kDefaultAFNetworkActivityManagerCompletionDelay = 0.17;
static NSURLRequest * AFNetworkRequestFromNotification(NSNotification *notification) {
if ([[notification object] respondsToSelector:@selector(originalRequest)]) {
return [(NSURLSessionTask *)[notification object] originalRequest];
} else {
return nil;
}
}
typedef void (^AFNetworkActivityActionBlock)(BOOL networkActivityIndicatorVisible);
@interface AFNetworkActivityIndicatorManager ()
@property (readwrite, nonatomic, assign) NSInteger activityCount;
@property (readwrite, nonatomic, strong) NSTimer *activationDelayTimer;
@property (readwrite, nonatomic, strong) NSTimer *completionDelayTimer;
@property (readonly, nonatomic, getter = isNetworkActivityOccurring) BOOL networkActivityOccurring;
@property (nonatomic, copy) AFNetworkActivityActionBlock networkActivityActionBlock;
@property (nonatomic, assign) AFNetworkActivityManagerState currentState;
@property (nonatomic, assign, getter=isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible;
- (void)updateCurrentStateForNetworkActivityChange;
@end
@implementation AFNetworkActivityIndicatorManager
+ (instancetype)sharedManager {
static AFNetworkActivityIndicatorManager *_sharedManager = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedManager = [[self alloc] init];
});
return _sharedManager;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.currentState = AFNetworkActivityManagerStateNotActive;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingTaskDidResumeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidSuspendNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidCompleteNotification object:nil];
self.activationDelay = kDefaultAFNetworkActivityManagerActivationDelay;
self.completionDelay = kDefaultAFNetworkActivityManagerCompletionDelay;
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_activationDelayTimer invalidate];
[_completionDelayTimer invalidate];
}
- (void)setEnabled:(BOOL)enabled {
_enabled = enabled;
if (enabled == NO) {
[self setCurrentState:AFNetworkActivityManagerStateNotActive];
}
}
- (void)setNetworkingActivityActionWithBlock:(void (^)(BOOL networkActivityIndicatorVisible))block {
self.networkActivityActionBlock = block;
}
- (BOOL)isNetworkActivityOccurring {
@synchronized(self) {
return self.activityCount > 0;
}
}
- (void)setNetworkActivityIndicatorVisible:(BOOL)networkActivityIndicatorVisible {
if (_networkActivityIndicatorVisible != networkActivityIndicatorVisible) {
@synchronized(self) {
_networkActivityIndicatorVisible = networkActivityIndicatorVisible;
}
if (self.networkActivityActionBlock) {
self.networkActivityActionBlock(networkActivityIndicatorVisible);
} else {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:networkActivityIndicatorVisible];
}
}
}
- (void)incrementActivityCount {
@synchronized(self) {
self.activityCount++;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self updateCurrentStateForNetworkActivityChange];
});
}
- (void)decrementActivityCount {
@synchronized(self) {
self.activityCount = MAX(_activityCount - 1, 0);
}
dispatch_async(dispatch_get_main_queue(), ^{
[self updateCurrentStateForNetworkActivityChange];
});
}
- (void)networkRequestDidStart:(NSNotification *)notification {
if ([AFNetworkRequestFromNotification(notification) URL]) {
[self incrementActivityCount];
}
}
- (void)networkRequestDidFinish:(NSNotification *)notification {
if ([AFNetworkRequestFromNotification(notification) URL]) {
[self decrementActivityCount];
}
}
#pragma mark - Internal State Management
- (void)setCurrentState:(AFNetworkActivityManagerState)currentState {
@synchronized(self) {
if (_currentState != currentState) {
_currentState = currentState;
switch (currentState) {
case AFNetworkActivityManagerStateNotActive:
[self cancelActivationDelayTimer];
[self cancelCompletionDelayTimer];
[self setNetworkActivityIndicatorVisible:NO];
break;
case AFNetworkActivityManagerStateDelayingStart:
[self startActivationDelayTimer];
break;
case AFNetworkActivityManagerStateActive:
[self cancelCompletionDelayTimer];
[self setNetworkActivityIndicatorVisible:YES];
break;
case AFNetworkActivityManagerStateDelayingEnd:
[self startCompletionDelayTimer];
break;
}
}
}
}
- (void)updateCurrentStateForNetworkActivityChange {
if (self.enabled) {
switch (self.currentState) {
case AFNetworkActivityManagerStateNotActive:
if (self.isNetworkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateDelayingStart];
}
break;
case AFNetworkActivityManagerStateDelayingStart:
//No op. Let the delay timer finish out.
break;
case AFNetworkActivityManagerStateActive:
if (!self.isNetworkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateDelayingEnd];
}
break;
case AFNetworkActivityManagerStateDelayingEnd:
if (self.isNetworkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateActive];
}
break;
}
}
}
- (void)startActivationDelayTimer {
self.activationDelayTimer = [NSTimer
timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes];
}
- (void)activationDelayTimerFired {
if (self.networkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateActive];
} else {
[self setCurrentState:AFNetworkActivityManagerStateNotActive];
}
}
- (void)startCompletionDelayTimer {
[self.completionDelayTimer invalidate];
self.completionDelayTimer = [NSTimer timerWithTimeInterval:self.completionDelay target:self selector:@selector(completionDelayTimerFired) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:self.completionDelayTimer forMode:NSRunLoopCommonModes];
}
- (void)completionDelayTimerFired {
[self setCurrentState:AFNetworkActivityManagerStateNotActive];
}
- (void)cancelActivationDelayTimer {
[self.activationDelayTimer invalidate];
}
- (void)cancelCompletionDelayTimer {
[self.completionDelayTimer invalidate];
}
@end
#endif
// UIActivityIndicatorView+AFNetworking.h
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 <Foundation/Foundation.h>
#import <TargetConditionals.h>
#if TARGET_OS_IOS || TARGET_OS_TV
#import <UIKit/UIKit.h>
/**
This category adds methods to the UIKit framework's `UIActivityIndicatorView` class. The methods in this category provide support for automatically starting and stopping animation depending on the loading state of a session task.
*/
@interface UIActivityIndicatorView (AFNetworking)
///----------------------------------
/// @name Animating for Session Tasks
///----------------------------------
/**
Binds the animating state to the state of the specified task.
@param task The task. If `nil`, automatic updating from any previously specified operation will be disabled.
*/
- (void)setAnimatingWithStateOfTask:(nullable NSURLSessionTask *)task;
@end
#endif
// UIActivityIndicatorView+AFNetworking.m
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 "UIActivityIndicatorView+AFNetworking.h"
#import <objc/runtime.h>
#if TARGET_OS_IOS || TARGET_OS_TV
#import "AFURLSessionManager.h"
@interface AFActivityIndicatorViewNotificationObserver : NSObject
@property (readonly, nonatomic, weak) UIActivityIndicatorView *activityIndicatorView;
- (instancetype)initWithActivityIndicatorView:(UIActivityIndicatorView *)activityIndicatorView;
- (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task;
@end
@implementation UIActivityIndicatorView (AFNetworking)
- (AFActivityIndicatorViewNotificationObserver *)af_notificationObserver {
AFActivityIndicatorViewNotificationObserver *notificationObserver = objc_getAssociatedObject(self, @selector(af_notificationObserver));
if (notificationObserver == nil) {
notificationObserver = [[AFActivityIndicatorViewNotificationObserver alloc] initWithActivityIndicatorView:self];
objc_setAssociatedObject(self, @selector(af_notificationObserver), notificationObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return notificationObserver;
}
- (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task {
[[self af_notificationObserver] setAnimatingWithStateOfTask:task];
}
@end
@implementation AFActivityIndicatorViewNotificationObserver
- (instancetype)initWithActivityIndicatorView:(UIActivityIndicatorView *)activityIndicatorView
{
self = [super init];
if (self) {
_activityIndicatorView = activityIndicatorView;
}
return self;
}
- (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidResumeNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidSuspendNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidCompleteNotification object:nil];
if (task) {
if (task.state != NSURLSessionTaskStateCompleted) {
UIActivityIndicatorView *activityIndicatorView = self.activityIndicatorView;
if (task.state == NSURLSessionTaskStateRunning) {
[activityIndicatorView startAnimating];
} else {
[activityIndicatorView stopAnimating];
}
[notificationCenter addObserver:self selector:@selector(af_startAnimating) name:AFNetworkingTaskDidResumeNotification object:task];
[notificationCenter addObserver:self selector:@selector(af_stopAnimating) name:AFNetworkingTaskDidCompleteNotification object:task];
[notificationCenter addObserver:self selector:@selector(af_stopAnimating) name:AFNetworkingTaskDidSuspendNotification object:task];
}
}
}
#pragma mark -
- (void)af_startAnimating {
dispatch_async(dispatch_get_main_queue(), ^{
[self.activityIndicatorView startAnimating];
});
}
- (void)af_stopAnimating {
dispatch_async(dispatch_get_main_queue(), ^{
[self.activityIndicatorView stopAnimating];
});
}
#pragma mark -
- (void)dealloc {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidCompleteNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidResumeNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidSuspendNotification object:nil];
}
@end
#endif
// UIButton+AFNetworking.h
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 <Foundation/Foundation.h>
#import <TargetConditionals.h>
#if TARGET_OS_IOS || TARGET_OS_TV
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class AFImageDownloader;
/**
This category adds methods to the UIKit framework's `UIButton` class. The methods in this category provide support for loading remote images and background images asynchronously from a URL.
@warning Compound values for control `state` (such as `UIControlStateHighlighted | UIControlStateDisabled`) are unsupported.
*/
@interface UIButton (AFNetworking)
///------------------------------------
/// @name Accessing the Image Downloader
///------------------------------------
/**
Set the shared image downloader used to download images.
@param imageDownloader The shared image downloader used to download images.
*/
+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader;
/**
The shared image downloader used to download images.
*/
+ (AFImageDownloader *)sharedImageDownloader;
///--------------------
/// @name Setting Image
///--------------------
/**
Asynchronously downloads an image from the specified URL, and sets it as the image for the specified state once the request is finished. Any previous image request for the receiver will be cancelled.
If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
@param state The control state.
@param url The URL used for the image request.
*/
- (void)setImageForState:(UIControlState)state
withURL:(NSURL *)url;
/**
Asynchronously downloads an image from the specified URL, and sets it as the image for the specified state once the request is finished. Any previous image request for the receiver will be cancelled.
If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
@param state The control state.
@param url The URL used for the image request.
@param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the button will not change its image until the image request finishes.
*/
- (void)setImageForState:(UIControlState)state
withURL:(NSURL *)url
placeholderImage:(nullable UIImage *)placeholderImage;
/**
Asynchronously downloads an image from the specified URL request, and sets it as the image for the specified state once the request is finished. Any previous image request for the receiver will be cancelled.
If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
If a success block is specified, it is the responsibility of the block to set the image of the button before returning. If no success block is specified, the default behavior of setting the image with `setImage:forState:` is applied.
@param state The control state.
@param urlRequest The URL request used for the image request.
@param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the button will not change its image until the image request finishes.
@param success A block to be executed when the image data task finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the response parameter will be `nil`.
@param failure A block object to be executed when the image data task finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred.
*/
- (void)setImageForState:(UIControlState)state
withURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(nullable UIImage *)placeholderImage
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;
///-------------------------------
/// @name Setting Background Image
///-------------------------------
/**
Asynchronously downloads an image from the specified URL, and sets it as the background image for the specified state once the request is finished. Any previous background image request for the receiver will be cancelled.
If the background image is cached locally, the background image is set immediately, otherwise the specified placeholder background image will be set immediately, and then the remote background image will be set once the request is finished.
@param state The control state.
@param url The URL used for the background image request.
*/
- (void)setBackgroundImageForState:(UIControlState)state
withURL:(NSURL *)url;
/**
Asynchronously downloads an image from the specified URL, and sets it as the background image for the specified state once the request is finished. Any previous image request for the receiver will be cancelled.
If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
@param state The control state.
@param url The URL used for the background image request.
@param placeholderImage The background image to be set initially, until the background image request finishes. If `nil`, the button will not change its background image until the background image request finishes.
*/
- (void)setBackgroundImageForState:(UIControlState)state
withURL:(NSURL *)url
placeholderImage:(nullable UIImage *)placeholderImage;
/**
Asynchronously downloads an image from the specified URL request, and sets it as the image for the specified state once the request is finished. Any previous image request for the receiver will be cancelled.
If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
If a success block is specified, it is the responsibility of the block to set the image of the button before returning. If no success block is specified, the default behavior of setting the image with `setBackgroundImage:forState:` is applied.
@param state The control state.
@param urlRequest The URL request used for the image request.
@param placeholderImage The background image to be set initially, until the background image request finishes. If `nil`, the button will not change its background image until the background image request finishes.
@param success A block to be executed when the image data task finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the response parameter will be `nil`.
@param failure A block object to be executed when the image data task finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred.
*/
- (void)setBackgroundImageForState:(UIControlState)state
withURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(nullable UIImage *)placeholderImage
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;
///------------------------------
/// @name Canceling Image Loading
///------------------------------
/**
Cancels any executing image task for the specified control state of the receiver, if one exists.
@param state The control state.
*/
- (void)cancelImageDownloadTaskForState:(UIControlState)state;
/**
Cancels any executing background image task for the specified control state of the receiver, if one exists.
@param state The control state.
*/
- (void)cancelBackgroundImageDownloadTaskForState:(UIControlState)state;
@end
NS_ASSUME_NONNULL_END
#endif
// UIButton+AFNetworking.m
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 "UIButton+AFNetworking.h"
#import <objc/runtime.h>
#if TARGET_OS_IOS || TARGET_OS_TV
#import "UIImageView+AFNetworking.h"
#import "AFImageDownloader.h"
@interface UIButton (_AFNetworking)
@end
@implementation UIButton (_AFNetworking)
#pragma mark -
static char AFImageDownloadReceiptNormal;
static char AFImageDownloadReceiptHighlighted;
static char AFImageDownloadReceiptSelected;
static char AFImageDownloadReceiptDisabled;
static const char * af_imageDownloadReceiptKeyForState(UIControlState state) {
switch (state) {
case UIControlStateHighlighted:
return &AFImageDownloadReceiptHighlighted;
case UIControlStateSelected:
return &AFImageDownloadReceiptSelected;
case UIControlStateDisabled:
return &AFImageDownloadReceiptDisabled;
case UIControlStateNormal:
default:
return &AFImageDownloadReceiptNormal;
}
}
- (AFImageDownloadReceipt *)af_imageDownloadReceiptForState:(UIControlState)state {
return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, af_imageDownloadReceiptKeyForState(state));
}
- (void)af_setImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt
forState:(UIControlState)state
{
objc_setAssociatedObject(self, af_imageDownloadReceiptKeyForState(state), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark -
static char AFBackgroundImageDownloadReceiptNormal;
static char AFBackgroundImageDownloadReceiptHighlighted;
static char AFBackgroundImageDownloadReceiptSelected;
static char AFBackgroundImageDownloadReceiptDisabled;
static const char * af_backgroundImageDownloadReceiptKeyForState(UIControlState state) {
switch (state) {
case UIControlStateHighlighted:
return &AFBackgroundImageDownloadReceiptHighlighted;
case UIControlStateSelected:
return &AFBackgroundImageDownloadReceiptSelected;
case UIControlStateDisabled:
return &AFBackgroundImageDownloadReceiptDisabled;
case UIControlStateNormal:
default:
return &AFBackgroundImageDownloadReceiptNormal;
}
}
- (AFImageDownloadReceipt *)af_backgroundImageDownloadReceiptForState:(UIControlState)state {
return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, af_backgroundImageDownloadReceiptKeyForState(state));
}
- (void)af_setBackgroundImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt
forState:(UIControlState)state
{
objc_setAssociatedObject(self, af_backgroundImageDownloadReceiptKeyForState(state), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
#pragma mark -
@implementation UIButton (AFNetworking)
+ (AFImageDownloader *)sharedImageDownloader {
return objc_getAssociatedObject([UIButton class], @selector(sharedImageDownloader)) ?: [AFImageDownloader defaultInstance];
}
+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader {
objc_setAssociatedObject([UIButton class], @selector(sharedImageDownloader), imageDownloader, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark -
- (void)setImageForState:(UIControlState)state
withURL:(NSURL *)url
{
[self setImageForState:state withURL:url placeholderImage:nil];
}
- (void)setImageForState:(UIControlState)state
withURL:(NSURL *)url
placeholderImage:(UIImage *)placeholderImage
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
[self setImageForState:state withURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}
- (void)setImageForState:(UIControlState)state
withURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(nullable UIImage *)placeholderImage
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure
{
if ([self isActiveTaskURLEqualToURLRequest:urlRequest forState:state]) {
return;
}
[self cancelImageDownloadTaskForState:state];
AFImageDownloader *downloader = [[self class] sharedImageDownloader];
id <AFImageRequestCache> imageCache = downloader.imageCache;
//Use the image from the image cache if it exists
UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];
if (cachedImage) {
if (success) {
success(urlRequest, nil, cachedImage);
} else {
[self setImage:cachedImage forState:state];
}
[self af_setImageDownloadReceipt:nil forState:state];
} else {
if (placeholderImage) {
[self setImage:placeholderImage forState:state];
}
__weak __typeof(self)weakSelf = self;
NSUUID *downloadID = [NSUUID UUID];
AFImageDownloadReceipt *receipt;
receipt = [downloader
downloadImageForURLRequest:urlRequest
withReceiptID:downloadID
success:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, UIImage * _Nonnull responseObject) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if ([[strongSelf af_imageDownloadReceiptForState:state].receiptID isEqual:downloadID]) {
if (success) {
success(request, response, responseObject);
} else if (responseObject) {
[strongSelf setImage:responseObject forState:state];
}
[strongSelf af_setImageDownloadReceipt:nil forState:state];
}
}
failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if ([[strongSelf af_imageDownloadReceiptForState:state].receiptID isEqual:downloadID]) {
if (failure) {
failure(request, response, error);
}
[strongSelf af_setImageDownloadReceipt:nil forState:state];
}
}];
[self af_setImageDownloadReceipt:receipt forState:state];
}
}
#pragma mark -
- (void)setBackgroundImageForState:(UIControlState)state
withURL:(NSURL *)url
{
[self setBackgroundImageForState:state withURL:url placeholderImage:nil];
}
- (void)setBackgroundImageForState:(UIControlState)state
withURL:(NSURL *)url
placeholderImage:(nullable UIImage *)placeholderImage
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
[self setBackgroundImageForState:state withURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}
- (void)setBackgroundImageForState:(UIControlState)state
withURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(nullable UIImage *)placeholderImage
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure
{
if ([self isActiveBackgroundTaskURLEqualToURLRequest:urlRequest forState:state]) {
return;
}
[self cancelBackgroundImageDownloadTaskForState:state];
AFImageDownloader *downloader = [[self class] sharedImageDownloader];
id <AFImageRequestCache> imageCache = downloader.imageCache;
//Use the image from the image cache if it exists
UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];
if (cachedImage) {
if (success) {
success(urlRequest, nil, cachedImage);
} else {
[self setBackgroundImage:cachedImage forState:state];
}
[self af_setBackgroundImageDownloadReceipt:nil forState:state];
} else {
if (placeholderImage) {
[self setBackgroundImage:placeholderImage forState:state];
}
__weak __typeof(self)weakSelf = self;
NSUUID *downloadID = [NSUUID UUID];
AFImageDownloadReceipt *receipt;
receipt = [downloader
downloadImageForURLRequest:urlRequest
withReceiptID:downloadID
success:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, UIImage * _Nonnull responseObject) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if ([[strongSelf af_backgroundImageDownloadReceiptForState:state].receiptID isEqual:downloadID]) {
if (success) {
success(request, response, responseObject);
} else if (responseObject) {
[strongSelf setBackgroundImage:responseObject forState:state];
}
[strongSelf af_setBackgroundImageDownloadReceipt:nil forState:state];
}
}
failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if ([[strongSelf af_backgroundImageDownloadReceiptForState:state].receiptID isEqual:downloadID]) {
if (failure) {
failure(request, response, error);
}
[strongSelf af_setBackgroundImageDownloadReceipt:nil forState:state];
}
}];
[self af_setBackgroundImageDownloadReceipt:receipt forState:state];
}
}
#pragma mark -
- (void)cancelImageDownloadTaskForState:(UIControlState)state {
AFImageDownloadReceipt *receipt = [self af_imageDownloadReceiptForState:state];
if (receipt != nil) {
[[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:receipt];
[self af_setImageDownloadReceipt:nil forState:state];
}
}
- (void)cancelBackgroundImageDownloadTaskForState:(UIControlState)state {
AFImageDownloadReceipt *receipt = [self af_backgroundImageDownloadReceiptForState:state];
if (receipt != nil) {
[[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:receipt];
[self af_setBackgroundImageDownloadReceipt:nil forState:state];
}
}
- (BOOL)isActiveTaskURLEqualToURLRequest:(NSURLRequest *)urlRequest forState:(UIControlState)state {
AFImageDownloadReceipt *receipt = [self af_imageDownloadReceiptForState:state];
return [receipt.task.originalRequest.URL.absoluteString isEqualToString:urlRequest.URL.absoluteString];
}
- (BOOL)isActiveBackgroundTaskURLEqualToURLRequest:(NSURLRequest *)urlRequest forState:(UIControlState)state {
AFImageDownloadReceipt *receipt = [self af_backgroundImageDownloadReceiptForState:state];
return [receipt.task.originalRequest.URL.absoluteString isEqualToString:urlRequest.URL.absoluteString];
}
@end
#endif
// UIImageView+AFNetworking.h
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 <Foundation/Foundation.h>
#import <TargetConditionals.h>
#if TARGET_OS_IOS || TARGET_OS_TV
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class AFImageDownloader;
/**
This category adds methods to the UIKit framework's `UIImageView` class. The methods in this category provide support for loading remote images asynchronously from a URL.
*/
@interface UIImageView (AFNetworking)
///------------------------------------
/// @name Accessing the Image Downloader
///------------------------------------
/**
Set the shared image downloader used to download images.
@param imageDownloader The shared image downloader used to download images.
*/
+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader;
/**
The shared image downloader used to download images.
*/
+ (AFImageDownloader *)sharedImageDownloader;
///--------------------
/// @name Setting Image
///--------------------
/**
Asynchronously downloads an image from the specified URL, and sets it once the request is finished. Any previous image request for the receiver will be cancelled.
If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
By default, URL requests have a `Accept` header field value of "image / *", a cache policy of `NSURLCacheStorageAllowed` and a timeout interval of 30 seconds, and are set not handle cookies. To configure URL requests differently, use `setImageWithURLRequest:placeholderImage:success:failure:`
@param url The URL used for the image request.
*/
- (void)setImageWithURL:(NSURL *)url;
/**
Asynchronously downloads an image from the specified URL, and sets it once the request is finished. Any previous image request for the receiver will be cancelled.
If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
By default, URL requests have a `Accept` header field value of "image / *", a cache policy of `NSURLCacheStorageAllowed` and a timeout interval of 30 seconds, and are set not handle cookies. To configure URL requests differently, use `setImageWithURLRequest:placeholderImage:success:failure:`
@param url The URL used for the image request.
@param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the image view will not change its image until the image request finishes.
*/
- (void)setImageWithURL:(NSURL *)url
placeholderImage:(nullable UIImage *)placeholderImage;
/**
Asynchronously downloads an image from the specified URL request, and sets it once the request is finished. Any previous image request for the receiver will be cancelled.
If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
If a success block is specified, it is the responsibility of the block to set the image of the image view before returning. If no success block is specified, the default behavior of setting the image with `self.image = image` is applied.
@param urlRequest The URL request used for the image request.
@param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the image view will not change its image until the image request finishes.
@param success A block to be executed when the image data task finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the response parameter will be `nil`.
@param failure A block object to be executed when the image data task finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred.
*/
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(nullable UIImage *)placeholderImage
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;
/**
Cancels any executing image operation for the receiver, if one exists.
*/
- (void)cancelImageDownloadTask;
@end
NS_ASSUME_NONNULL_END
#endif
// UIImageView+AFNetworking.m
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 "UIImageView+AFNetworking.h"
#import <objc/runtime.h>
#if TARGET_OS_IOS || TARGET_OS_TV
#import "AFImageDownloader.h"
@interface UIImageView (_AFNetworking)
@property (readwrite, nonatomic, strong, setter = af_setActiveImageDownloadReceipt:) AFImageDownloadReceipt *af_activeImageDownloadReceipt;
@end
@implementation UIImageView (_AFNetworking)
- (AFImageDownloadReceipt *)af_activeImageDownloadReceipt {
return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, @selector(af_activeImageDownloadReceipt));
}
- (void)af_setActiveImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
objc_setAssociatedObject(self, @selector(af_activeImageDownloadReceipt), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
#pragma mark -
@implementation UIImageView (AFNetworking)
+ (AFImageDownloader *)sharedImageDownloader {
return objc_getAssociatedObject([UIImageView class], @selector(sharedImageDownloader)) ?: [AFImageDownloader defaultInstance];
}
+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader {
objc_setAssociatedObject([UIImageView class], @selector(sharedImageDownloader), imageDownloader, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark -
- (void)setImageWithURL:(NSURL *)url {
[self setImageWithURL:url placeholderImage:nil];
}
- (void)setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholderImage
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
[self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(UIImage *)placeholderImage
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure
{
if ([urlRequest URL] == nil) {
self.image = placeholderImage;
if (failure) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
failure(urlRequest, nil, error);
}
return;
}
if ([self isActiveTaskURLEqualToURLRequest:urlRequest]) {
return;
}
[self cancelImageDownloadTask];
AFImageDownloader *downloader = [[self class] sharedImageDownloader];
id <AFImageRequestCache> imageCache = downloader.imageCache;
//Use the image from the image cache if it exists
UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];
if (cachedImage) {
if (success) {
success(urlRequest, nil, cachedImage);
} else {
self.image = cachedImage;
}
[self clearActiveDownloadInformation];
} else {
if (placeholderImage) {
self.image = placeholderImage;
}
__weak __typeof(self)weakSelf = self;
NSUUID *downloadID = [NSUUID UUID];
AFImageDownloadReceipt *receipt;
receipt = [downloader
downloadImageForURLRequest:urlRequest
withReceiptID:downloadID
success:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, UIImage * _Nonnull responseObject) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
if (success) {
success(request, response, responseObject);
} else if (responseObject) {
strongSelf.image = responseObject;
}
[strongSelf clearActiveDownloadInformation];
}
}
failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
if (failure) {
failure(request, response, error);
}
[strongSelf clearActiveDownloadInformation];
}
}];
self.af_activeImageDownloadReceipt = receipt;
}
}
- (void)cancelImageDownloadTask {
if (self.af_activeImageDownloadReceipt != nil) {
[[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt];
[self clearActiveDownloadInformation];
}
}
- (void)clearActiveDownloadInformation {
self.af_activeImageDownloadReceipt = nil;
}
- (BOOL)isActiveTaskURLEqualToURLRequest:(NSURLRequest *)urlRequest {
return [self.af_activeImageDownloadReceipt.task.originalRequest.URL.absoluteString isEqualToString:urlRequest.URL.absoluteString];
}
@end
#endif
// UIKit+AFNetworking.h
//
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 <TargetConditionals.h>
#ifndef _UIKIT_AFNETWORKING_
#define _UIKIT_AFNETWORKING_
#if TARGET_OS_IOS || TARGET_OS_TV
#import "AFAutoPurgingImageCache.h"
#import "AFImageDownloader.h"
#import "UIActivityIndicatorView+AFNetworking.h"
#import "UIButton+AFNetworking.h"
#import "UIImageView+AFNetworking.h"
#import "UIProgressView+AFNetworking.h"
#endif
#if TARGET_OS_IOS
#import "AFNetworkActivityIndicatorManager.h"
#import "UIRefreshControl+AFNetworking.h"
#import "WKWebView+AFNetworking.h"
#endif
#endif /* _UIKIT_AFNETWORKING_ */
// UIProgressView+AFNetworking.h
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 <Foundation/Foundation.h>
#import <TargetConditionals.h>
#if TARGET_OS_IOS || TARGET_OS_TV
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
This category adds methods to the UIKit framework's `UIProgressView` class. The methods in this category provide support for binding the progress to the upload and download progress of a session task.
*/
@interface UIProgressView (AFNetworking)
///------------------------------------
/// @name Setting Session Task Progress
///------------------------------------
/**
Binds the progress to the upload progress of the specified session task.
@param task The session task.
@param animated `YES` if the change should be animated, `NO` if the change should happen immediately.
*/
- (void)setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task
animated:(BOOL)animated;
/**
Binds the progress to the download progress of the specified session task.
@param task The session task.
@param animated `YES` if the change should be animated, `NO` if the change should happen immediately.
*/
- (void)setProgressWithDownloadProgressOfTask:(NSURLSessionDownloadTask *)task
animated:(BOOL)animated;
@end
NS_ASSUME_NONNULL_END
#endif
// UIProgressView+AFNetworking.m
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 "UIProgressView+AFNetworking.h"
#import <objc/runtime.h>
#if TARGET_OS_IOS || TARGET_OS_TV
#import "AFURLSessionManager.h"
static void * AFTaskCountOfBytesSentContext = &AFTaskCountOfBytesSentContext;
static void * AFTaskCountOfBytesReceivedContext = &AFTaskCountOfBytesReceivedContext;
#pragma mark -
@implementation UIProgressView (AFNetworking)
- (BOOL)af_uploadProgressAnimated {
return [(NSNumber *)objc_getAssociatedObject(self, @selector(af_uploadProgressAnimated)) boolValue];
}
- (void)af_setUploadProgressAnimated:(BOOL)animated {
objc_setAssociatedObject(self, @selector(af_uploadProgressAnimated), @(animated), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)af_downloadProgressAnimated {
return [(NSNumber *)objc_getAssociatedObject(self, @selector(af_downloadProgressAnimated)) boolValue];
}
- (void)af_setDownloadProgressAnimated:(BOOL)animated {
objc_setAssociatedObject(self, @selector(af_downloadProgressAnimated), @(animated), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark -
- (void)setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task
animated:(BOOL)animated
{
if (task.state == NSURLSessionTaskStateCompleted) {
return;
}
[task addObserver:self forKeyPath:@"state" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesSentContext];
[task addObserver:self forKeyPath:@"countOfBytesSent" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesSentContext];
[self af_setUploadProgressAnimated:animated];
}
- (void)setProgressWithDownloadProgressOfTask:(NSURLSessionDownloadTask *)task
animated:(BOOL)animated
{
if (task.state == NSURLSessionTaskStateCompleted) {
return;
}
[task addObserver:self forKeyPath:@"state" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesReceivedContext];
[task addObserver:self forKeyPath:@"countOfBytesReceived" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesReceivedContext];
[self af_setDownloadProgressAnimated:animated];
}
#pragma mark - NSKeyValueObserving
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(__unused NSDictionary *)change
context:(void *)context
{
if (context == AFTaskCountOfBytesSentContext || context == AFTaskCountOfBytesReceivedContext) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
if ([object countOfBytesExpectedToSend] > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setProgress:[object countOfBytesSent] / ([object countOfBytesExpectedToSend] * 1.0f) animated:self.af_uploadProgressAnimated];
});
}
}
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
if ([object countOfBytesExpectedToReceive] > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setProgress:[object countOfBytesReceived] / ([object countOfBytesExpectedToReceive] * 1.0f) animated:self.af_downloadProgressAnimated];
});
}
}
if ([keyPath isEqualToString:NSStringFromSelector(@selector(state))]) {
if ([(NSURLSessionTask *)object state] == NSURLSessionTaskStateCompleted) {
@try {
[object removeObserver:self forKeyPath:NSStringFromSelector(@selector(state))];
if (context == AFTaskCountOfBytesSentContext) {
[object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))];
}
if (context == AFTaskCountOfBytesReceivedContext) {
[object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))];
}
}
@catch (NSException * __unused exception) {}
}
}
}
}
@end
#endif
// UIRefreshControl+AFNetworking.m
//
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 <Foundation/Foundation.h>
#import <TargetConditionals.h>
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
This category adds methods to the UIKit framework's `UIRefreshControl` class. The methods in this category provide support for automatically beginning and ending refreshing depending on the loading state of a session task.
*/
@interface UIRefreshControl (AFNetworking)
///-----------------------------------
/// @name Refreshing for Session Tasks
///-----------------------------------
/**
Binds the refreshing state to the state of the specified task.
@param task The task. If `nil`, automatic updating from any previously specified operation will be disabled.
*/
- (void)setRefreshingWithStateOfTask:(NSURLSessionTask *)task;
@end
NS_ASSUME_NONNULL_END
#endif
// UIRefreshControl+AFNetworking.m
//
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 "UIRefreshControl+AFNetworking.h"
#import <objc/runtime.h>
#if TARGET_OS_IOS
#import "AFURLSessionManager.h"
@interface AFRefreshControlNotificationObserver : NSObject
@property (readonly, nonatomic, weak) UIRefreshControl *refreshControl;
- (instancetype)initWithActivityRefreshControl:(UIRefreshControl *)refreshControl;
- (void)setRefreshingWithStateOfTask:(NSURLSessionTask *)task;
@end
@implementation UIRefreshControl (AFNetworking)
- (AFRefreshControlNotificationObserver *)af_notificationObserver {
AFRefreshControlNotificationObserver *notificationObserver = objc_getAssociatedObject(self, @selector(af_notificationObserver));
if (notificationObserver == nil) {
notificationObserver = [[AFRefreshControlNotificationObserver alloc] initWithActivityRefreshControl:self];
objc_setAssociatedObject(self, @selector(af_notificationObserver), notificationObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return notificationObserver;
}
- (void)setRefreshingWithStateOfTask:(NSURLSessionTask *)task {
[[self af_notificationObserver] setRefreshingWithStateOfTask:task];
}
@end
@implementation AFRefreshControlNotificationObserver
- (instancetype)initWithActivityRefreshControl:(UIRefreshControl *)refreshControl
{
self = [super init];
if (self) {
_refreshControl = refreshControl;
}
return self;
}
- (void)setRefreshingWithStateOfTask:(NSURLSessionTask *)task {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidResumeNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidSuspendNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidCompleteNotification object:nil];
if (task) {
UIRefreshControl *refreshControl = self.refreshControl;
if (task.state == NSURLSessionTaskStateRunning) {
[refreshControl beginRefreshing];
[notificationCenter addObserver:self selector:@selector(af_beginRefreshing) name:AFNetworkingTaskDidResumeNotification object:task];
[notificationCenter addObserver:self selector:@selector(af_endRefreshing) name:AFNetworkingTaskDidCompleteNotification object:task];
[notificationCenter addObserver:self selector:@selector(af_endRefreshing) name:AFNetworkingTaskDidSuspendNotification object:task];
} else {
[refreshControl endRefreshing];
}
}
}
#pragma mark -
- (void)af_beginRefreshing {
dispatch_async(dispatch_get_main_queue(), ^{
[self.refreshControl beginRefreshing];
});
}
- (void)af_endRefreshing {
dispatch_async(dispatch_get_main_queue(), ^{
[self.refreshControl endRefreshing];
});
}
#pragma mark -
- (void)dealloc {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidCompleteNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidResumeNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidSuspendNotification object:nil];
}
@end
#endif
// WkWebView+AFNetworking.h
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 <Foundation/Foundation.h>
#import <TargetConditionals.h>
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>
NS_ASSUME_NONNULL_BEGIN
@class AFHTTPSessionManager;
@interface WKWebView (AFNetworking)
/**
The session manager used to download all request
*/
@property (nonatomic, strong) AFHTTPSessionManager *sessionManager;
/**
Asynchronously loads the specified request.
@param request A URL request identifying the location of the content to load. This must not be `nil`.
@param navigation The WKNavigation object that containts information for tracking the loading progress of a webpage. This must not be `nil`.
@param progress A progress object monitoring the current download progress.
@param success A block object to be executed when the request finishes loading successfully. This block returns the HTML string to be loaded by the web view, and takes two arguments: the response, and the response string.
@param failure A block object to be executed when the data task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the error that occurred.
*/
- (void)loadRequest:(NSURLRequest *)request
navigation:(WKNavigation * _Nonnull)navigation
progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
success:(nullable NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success
failure:(nullable void (^)(NSError *error))failure;
/**
Asynchronously loads the data associated with a particular request with a specified MIME type and text encoding.
@param request A URL request identifying the location of the content to load. This must not be `nil`.
@param navigation The WKNavigation object that containts information for tracking the loading progress of a webpage. This must not be `nil`.
@param MIMEType The MIME type of the content. Defaults to the content type of the response if not specified.
@param textEncodingName The IANA encoding name, as in `utf-8` or `utf-16`. Defaults to the response text encoding if not specified.
@param progress A progress object monitoring the current download progress.
@param success A block object to be executed when the request finishes loading successfully. This block returns the data to be loaded by the web view and takes two arguments: the response, and the downloaded data.
@param failure A block object to be executed when the data task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the error that occurred.
*/
- (void)loadRequest:(NSURLRequest *)request
navigation:(WKNavigation * _Nonnull)navigation
MIMEType:(nullable NSString *)MIMEType
textEncodingName:(nullable NSString *)textEncodingName
progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
success:(nullable NSData * (^)(NSHTTPURLResponse *response, NSData *data))success
failure:(nullable void (^)(NSError *error))failure;
@end
NS_ASSUME_NONNULL_END
#endif
// WkWebView+AFNetworking.m
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
//
// 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 "WKWebView+AFNetworking.h"
#import <objc/runtime.h>
#if TARGET_OS_IOS
#import "AFHTTPSessionManager.h"
#import "AFURLResponseSerialization.h"
#import "AFURLRequestSerialization.h"
@interface WKWebView (_AFNetworking)
@property (readwrite, nonatomic, strong, setter = af_setURLSessionTask:) NSURLSessionDataTask *af_URLSessionTask;
@end
@implementation WKWebView (_AFNetworking)
- (NSURLSessionDataTask *)af_URLSessionTask {
return (NSURLSessionDataTask *)objc_getAssociatedObject(self, @selector(af_URLSessionTask));
}
- (void)af_setURLSessionTask:(NSURLSessionDataTask *)af_URLSessionTask {
objc_setAssociatedObject(self, @selector(af_URLSessionTask), af_URLSessionTask, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
#pragma mark -
@implementation WKWebView (AFNetworking)
- (AFHTTPSessionManager *)sessionManager {
static AFHTTPSessionManager *_af_defaultHTTPSessionManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_af_defaultHTTPSessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
_af_defaultHTTPSessionManager.requestSerializer = [AFHTTPRequestSerializer serializer];
_af_defaultHTTPSessionManager.responseSerializer = [AFHTTPResponseSerializer serializer];
});
return objc_getAssociatedObject(self, @selector(sessionManager)) ?: _af_defaultHTTPSessionManager;
}
- (void)setSessionManager:(AFHTTPSessionManager *)sessionManager {
objc_setAssociatedObject(self, @selector(sessionManager), sessionManager, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (AFHTTPResponseSerializer <AFURLResponseSerialization> *)responseSerializer {
static AFHTTPResponseSerializer <AFURLResponseSerialization> *_af_defaultResponseSerializer = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_af_defaultResponseSerializer = [AFHTTPResponseSerializer serializer];
});
return objc_getAssociatedObject(self, @selector(responseSerializer)) ?: _af_defaultResponseSerializer;
}
- (void)setResponseSerializer:(AFHTTPResponseSerializer<AFURLResponseSerialization> *)responseSerializer {
objc_setAssociatedObject(self, @selector(responseSerializer), responseSerializer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark -
- (void)loadRequest:(NSURLRequest *)request
navigation:(WKNavigation * _Nonnull)navigation
progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
success:(nullable NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success
failure:(nullable void (^)(NSError *error))failure {
[self loadRequest:request navigation:navigation MIMEType:nil textEncodingName:nil progress:progress success:^NSData * _Nonnull(NSHTTPURLResponse * _Nonnull response, NSData * _Nonnull data) {
NSStringEncoding stringEncoding = NSUTF8StringEncoding;
if (response.textEncodingName) {
CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
if (encoding != kCFStringEncodingInvalidId) {
stringEncoding = CFStringConvertEncodingToNSStringEncoding(encoding);
}
}
NSString *string = [[NSString alloc] initWithData:data encoding:stringEncoding];
if (success) {
string = success(response, string);
}
return [string dataUsingEncoding:stringEncoding];
} failure:failure];
}
- (void)loadRequest:(NSURLRequest *)request
navigation:(WKNavigation * _Nonnull)navigation
MIMEType:(nullable NSString *)MIMEType
textEncodingName:(nullable NSString *)textEncodingName
progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
success:(nullable NSData * (^)(NSHTTPURLResponse *response, NSData *data))success
failure:(nullable void (^)(NSError *error))failure {
NSParameterAssert(request);
if (self.af_URLSessionTask.state == NSURLSessionTaskStateRunning || self.af_URLSessionTask.state == NSURLSessionTaskStateSuspended) {
[self.af_URLSessionTask cancel];
}
self.af_URLSessionTask = nil;
__weak __typeof(self)weakSelf = self;
__block NSURLSessionDataTask *dataTask;
__strong __typeof(weakSelf) strongSelf = weakSelf;
__strong __typeof(weakSelf.navigationDelegate) strongSelfDelegate = strongSelf.navigationDelegate;
dataTask = [self.sessionManager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
if (error) {
if (failure) {
failure(error);
}
} else {
if (success) {
success((NSHTTPURLResponse *)response, responseObject);
}
[strongSelf loadData:responseObject MIMEType:MIMEType characterEncodingName:textEncodingName baseURL:[dataTask.currentRequest URL]];
if ([strongSelfDelegate respondsToSelector:@selector(webView:didFinishNavigation:)]) {
[strongSelfDelegate webView:strongSelf didFinishNavigation:navigation];
}
}
}];
self.af_URLSessionTask = dataTask;
if (progress != nil) {
*progress = [self.sessionManager downloadProgressForTask:dataTask];
}
[self.af_URLSessionTask resume];
if ([strongSelfDelegate respondsToSelector:@selector(webView:didStartProvisionalNavigation:)]) {
[strongSelfDelegate webView:self didStartProvisionalNavigation:navigation];
}
}
@end
#endif
//
// IQNSArray+Sort.h
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 <Foundation/NSArray.h>
@class UIView;
/**
UIView.subviews sorting category.
*/
@interface NSArray (IQ_NSArray_Sort)
///--------------
/// @name Sorting
///--------------
/**
Returns the array by sorting the UIView's by their tag property.
*/
@property (nonnull, nonatomic, readonly, copy) NSArray<__kindof UIView*> * sortedArrayByTag;
/**
Returns the array by sorting the UIView's by their tag property.
*/
@property (nonnull, nonatomic, readonly, copy) NSArray<__kindof UIView*> * sortedArrayByPosition;
@end
//
// IQNSArray+Sort.m
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 "IQNSArray+Sort.h"
#import "IQUIView+Hierarchy.h"
#import <UIKit/UIView.h>
@implementation NSArray (IQ_NSArray_Sort)
- (NSArray<UIView*>*)sortedArrayByTag
{
return [self sortedArrayUsingComparator:^NSComparisonResult(UIView *view1, UIView *view2) {
if ([view1 respondsToSelector:@selector(tag)] && [view2 respondsToSelector:@selector(tag)])
{
if ([view1 tag] < [view2 tag]) return NSOrderedAscending;
else if ([view1 tag] > [view2 tag]) return NSOrderedDescending;
else return NSOrderedSame;
}
else
return NSOrderedSame;
}];
}
- (NSArray<UIView*>*)sortedArrayByPosition
{
return [self sortedArrayUsingComparator:^NSComparisonResult(UIView *view1, UIView *view2) {
CGFloat x1 = CGRectGetMinX(view1.frame);
CGFloat y1 = CGRectGetMinY(view1.frame);
CGFloat x2 = CGRectGetMinX(view2.frame);
CGFloat y2 = CGRectGetMinY(view2.frame);
if (y1 < y2) return NSOrderedAscending;
else if (y1 > y2) return NSOrderedDescending;
//Else both y are same so checking for x positions
else if (x1 < x2) return NSOrderedAscending;
else if (x1 > x2) return NSOrderedDescending;
else return NSOrderedSame;
}];
}
@end
//
// IQUIScrollView+Additions.h
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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/UITableView.h>
#import <UIKit/UICollectionView.h>
@interface UIScrollView (Additions)
/**
If YES, then scrollview will ignore scrolling (simply not scroll it) for adjusting textfield position. Default is NO.
*/
@property(nonatomic, assign) BOOL shouldIgnoreScrollingAdjustment;
/**
If YES, then scrollview will ignore content inset adjustment (simply not updating it) when keyboard is shown. Default is NO.
*/
@property(nonatomic, assign) BOOL shouldIgnoreContentInsetAdjustment;
/**
Restore scrollViewContentOffset when resigning from scrollView. Default is NO.
*/
@property(nonatomic, assign) BOOL shouldRestoreScrollViewContentOffset;
@end
@interface UITableView (PreviousNextIndexPath)
-(nullable NSIndexPath*)previousIndexPathOfIndexPath:(nonnull NSIndexPath*)indexPath;
//-(nullable NSIndexPath*)nextIndexPathOfIndexPath:(nonnull NSIndexPath*)indexPath;
@end
@interface UICollectionView (PreviousNextIndexPath)
-(nullable NSIndexPath*)previousIndexPathOfIndexPath:(nonnull NSIndexPath*)indexPath;
//-(nullable NSIndexPath*)nextIndexPathOfIndexPath:(nonnull NSIndexPath*)indexPath;
@end
//
// IQUIScrollView+Additions.m
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 "IQUIScrollView+Additions.h"
#import <objc/runtime.h>
@implementation UIScrollView (Additions)
-(void)setShouldIgnoreScrollingAdjustment:(BOOL)shouldIgnoreScrollingAdjustment
{
objc_setAssociatedObject(self, @selector(shouldIgnoreScrollingAdjustment), @(shouldIgnoreScrollingAdjustment), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(BOOL)shouldIgnoreScrollingAdjustment
{
NSNumber *shouldIgnoreScrollingAdjustment = objc_getAssociatedObject(self, @selector(shouldIgnoreScrollingAdjustment));
return [shouldIgnoreScrollingAdjustment boolValue];
}
-(void)setShouldIgnoreContentInsetAdjustment:(BOOL)shouldIgnoreContentInsetAdjustment
{
objc_setAssociatedObject(self, @selector(shouldIgnoreContentInsetAdjustment), @(shouldIgnoreContentInsetAdjustment), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(BOOL)shouldIgnoreContentInsetAdjustment
{
NSNumber *shouldIgnoreContentInsetAdjustment = objc_getAssociatedObject(self, @selector(shouldIgnoreContentInsetAdjustment));
return [shouldIgnoreContentInsetAdjustment boolValue];
}
-(void)setShouldRestoreScrollViewContentOffset:(BOOL)shouldRestoreScrollViewContentOffset
{
objc_setAssociatedObject(self, @selector(shouldRestoreScrollViewContentOffset), @(shouldRestoreScrollViewContentOffset), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(BOOL)shouldRestoreScrollViewContentOffset
{
NSNumber *shouldRestoreScrollViewContentOffset = objc_getAssociatedObject(self, @selector(shouldRestoreScrollViewContentOffset));
return [shouldRestoreScrollViewContentOffset boolValue];
}
@end
@implementation UITableView (PreviousNextIndexPath)
-(nullable NSIndexPath*)previousIndexPathOfIndexPath:(nonnull NSIndexPath*)indexPath
{
NSInteger previousRow = indexPath.row - 1;
NSInteger previousSection = indexPath.section;
//Fixing indexPath
if (previousRow < 0)
{
previousSection -= 1;
if (previousSection >= 0)
{
previousRow = [self numberOfRowsInSection:previousSection]-1;
}
}
if (previousRow >= 0 && previousSection >= 0)
{
return [NSIndexPath indexPathForRow:previousRow inSection:previousSection];
}
return nil;
}
//-(nullable NSIndexPath*)nextIndexPathOfIndexPath:(nonnull NSIndexPath*)indexPath
//{
// NSInteger nextRow = indexPath.row + 1;
// NSInteger nextSection = indexPath.section;
//
// //Fixing indexPath
// if (nextRow >= [self numberOfRowsInSection:nextSection])
// {
// nextRow = 0;
// nextSection += 1;
// }
//
// if (self.numberOfSections > nextSection && [self numberOfRowsInSection:nextSection] > nextRow)
// {
// return [NSIndexPath indexPathForItem:nextRow inSection:nextSection];
// }
//
// return nil;
//}
//
@end
@implementation UICollectionView (PreviousNextIndexPath)
-(nullable NSIndexPath*)previousIndexPathOfIndexPath:(nonnull NSIndexPath*)indexPath
{
NSInteger previousRow = indexPath.row - 1;
NSInteger previousSection = indexPath.section;
//Fixing indexPath
if (previousRow < 0)
{
previousSection -= 1;
if (previousSection >= 0)
{
previousRow = [self numberOfItemsInSection:previousSection]-1;
}
}
if (previousRow >= 0 && previousSection >= 0)
{
return [NSIndexPath indexPathForItem:previousRow inSection:previousSection];
}
return nil;
}
//-(nullable NSIndexPath*)nextIndexPathOfIndexPath:(nonnull NSIndexPath*)indexPath
//{
// NSInteger nextRow = indexPath.row + 1;
// NSInteger nextSection = indexPath.section;
//
// //Fixing indexPath
// if (nextRow >= [self numberOfItemsInSection:nextSection])
// {
// nextRow = 0;
// nextSection += 1;
// }
//
// if (self.numberOfSections > nextSection && [self numberOfItemsInSection:nextSection] > nextRow)
// {
// return [NSIndexPath indexPathForItem:nextRow inSection:nextSection];
// }
//
// return nil;
//}
@end
//
// IQUITextFieldView+Additions.h
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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/UIView.h>
#import "IQKeyboardManagerConstants.h"
/**
UIView category for managing UITextField/UITextView
*/
@interface UIView (Additions)
/**
To set customized distance from keyboard for textField/textView. Can't be less than zero
*/
@property(nonatomic, assign) CGFloat keyboardDistanceFromTextField;
/**
If shouldIgnoreSwitchingByNextPrevious is YES then library will ignore this textField/textView while moving to other textField/textView using keyboard toolbar next previous buttons. Default is NO
*/
@property(nonatomic, assign) BOOL ignoreSwitchingByNextPrevious;
///**
// Override Enable/disable managing distance between keyboard and textField behaviour for this particular textField.
// */
@property(nonatomic, assign) IQEnableMode enableMode;
/**
Override resigns Keyboard on touching outside of UITextField/View behaviour for this particular textField.
*/
@property(nonatomic, assign) IQEnableMode shouldResignOnTouchOutsideMode;
@end
///-------------------------------------------
/// @name Custom KeyboardDistanceFromTextField
///-------------------------------------------
/**
Uses default keyboard distance for textField.
*/
extern CGFloat const kIQUseDefaultKeyboardDistance;
//
// IQUITextFieldView+Additions.m
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 "IQUITextFieldView+Additions.h"
#import <objc/runtime.h>
@implementation UIView (Additions)
-(void)setKeyboardDistanceFromTextField:(CGFloat)keyboardDistanceFromTextField
{
//Can't be less than zero. Minimum is zero.
keyboardDistanceFromTextField = MAX(keyboardDistanceFromTextField, 0);
objc_setAssociatedObject(self, @selector(keyboardDistanceFromTextField), @(keyboardDistanceFromTextField), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(CGFloat)keyboardDistanceFromTextField
{
NSNumber *keyboardDistanceFromTextField = objc_getAssociatedObject(self, @selector(keyboardDistanceFromTextField));
return (keyboardDistanceFromTextField != nil)?[keyboardDistanceFromTextField floatValue]:kIQUseDefaultKeyboardDistance;
}
-(void)setIgnoreSwitchingByNextPrevious:(BOOL)ignoreSwitchingByNextPrevious
{
objc_setAssociatedObject(self, @selector(ignoreSwitchingByNextPrevious), @(ignoreSwitchingByNextPrevious), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(BOOL)ignoreSwitchingByNextPrevious
{
NSNumber *ignoreSwitchingByNextPrevious = objc_getAssociatedObject(self, @selector(ignoreSwitchingByNextPrevious));
return [ignoreSwitchingByNextPrevious boolValue];
}
-(void)setEnableMode:(IQEnableMode)enableMode
{
objc_setAssociatedObject(self, @selector(enableMode), @(enableMode), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(IQEnableMode)enableMode
{
NSNumber *enableMode = objc_getAssociatedObject(self, @selector(enableMode));
return [enableMode unsignedIntegerValue];
}
-(void)setShouldResignOnTouchOutsideMode:(IQEnableMode)shouldResignOnTouchOutsideMode
{
objc_setAssociatedObject(self, @selector(shouldResignOnTouchOutsideMode), @(shouldResignOnTouchOutsideMode), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(IQEnableMode)shouldResignOnTouchOutsideMode
{
NSNumber *shouldResignOnTouchOutsideMode = objc_getAssociatedObject(self, @selector(shouldResignOnTouchOutsideMode));
return [shouldResignOnTouchOutsideMode unsignedIntegerValue];
}
@end
///------------------------------------
/// @name keyboardDistanceFromTextField
///------------------------------------
/**
Uses default keyboard distance for textField.
*/
CGFloat const kIQUseDefaultKeyboardDistance = CGFLOAT_MAX;
//
// IQUIView+Hierarchy.h
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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/UIView.h>
#import <UIKit/UIViewController.h>
#import "IQKeyboardManagerConstants.h"
@class UICollectionView, UIScrollView, UITableView, UISearchBar, NSArray;
/**
UIView hierarchy category.
*/
@interface UIView (IQ_UIView_Hierarchy)
///----------------------
/// @name viewControllers
///----------------------
/**
Returns the UIViewController object that manages the receiver.
*/
@property (nullable, nonatomic, readonly, strong) UIViewController *viewContainingController;
/**
Returns the topMost UIViewController object in hierarchy.
*/
@property (nullable, nonatomic, readonly, strong) UIViewController *topMostController;
/**
Returns the UIViewController object that is actually the parent of this object. Most of the time it's the viewController object which actually contains it, but result may be different if it's viewController is added as childViewController of another viewController.
*/
@property (nullable, nonatomic, readonly, strong) UIViewController *parentContainerViewController;
///-----------------------------------
/// @name Superviews/Subviews/Siglings
///-----------------------------------
/**
Returns the superView of provided class type.
@param classType class type of the object which is to be search in above hierarchy and return
@param belowView view object in upper hierarchy where method should stop searching and return nil
*/
-(nullable __kindof UIView*)superviewOfClassType:(nonnull Class)classType belowView:(nullable UIView*)belowView;
-(nullable __kindof UIView*)superviewOfClassType:(nonnull Class)classType;
/**
Returns all siblings of the receiver which canBecomeFirstResponder.
*/
@property (nonnull, nonatomic, readonly, copy) NSArray<__kindof UIView*> *responderSiblings;
/**
Returns all deep subViews of the receiver which canBecomeFirstResponder.
*/
@property (nonnull, nonatomic, readonly, copy) NSArray<__kindof UIView*> *deepResponderViews;
///-------------------------
/// @name Special TextFields
///-------------------------
/**
Returns searchBar if receiver object is UISearchBarTextField, otherwise return nil.
*/
@property (nullable, nonatomic, readonly) UISearchBar *textFieldSearchBar;
/**
Returns YES if the receiver object is UIAlertSheetTextField, otherwise return NO.
*/
@property (nonatomic, getter=isAlertViewTextField, readonly) BOOL alertViewTextField;
///----------------
/// @name Transform
///----------------
/**
Returns current view transform with respect to the 'toView'.
*/
-(CGAffineTransform)convertTransformToView:(nullable UIView*)toView;
///-----------------
/// @name Hierarchy
///-----------------
/**
Returns a string that represent the information about it's subview's hierarchy. You can use this method to debug the subview's positions.
*/
@property (nonnull, nonatomic, readonly, copy) NSString *subHierarchy;
/**
Returns an string that represent the information about it's upper hierarchy. You can use this method to debug the superview's positions.
*/
@property (nonnull, nonatomic, readonly, copy) NSString *superHierarchy;
/**
Returns an string that represent the information about it's frame positions. You can use this method to debug self positions.
*/
@property (nonnull, nonatomic, readonly, copy) NSString *debugHierarchy;
@end
/**
NSObject category to used for logging purposes
*/
@interface NSObject (IQ_Logging)
/**
Short description for logging purpose.
*/
@property (nonnull, nonatomic, readonly, copy) NSString *_IQDescription;
@end
//
// IQUIView+Hierarchy.m
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 "IQUIView+Hierarchy.h"
#import "IQUITextFieldView+Additions.h"
#import "IQUIViewController+Additions.h"
#import <UIKit/UICollectionView.h>
#import <UIKit/UIAlertController.h>
#import <UIKit/UITableView.h>
#import <UIKit/UITextView.h>
#import <UIKit/UITextField.h>
#import <UIKit/UISearchBar.h>
#import <UIKit/UINavigationController.h>
#import <UIKit/UITabBarController.h>
#import <UIKit/UISplitViewController.h>
#import <UIKit/UIWindow.h>
#import <objc/runtime.h>
#import "IQNSArray+Sort.h"
@implementation UIView (IQ_UIView_Hierarchy)
-(UIViewController*)viewContainingController
{
UIResponder *nextResponder = self;
do
{
nextResponder = [nextResponder nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController*)nextResponder;
} while (nextResponder);
return nil;
}
-(UIViewController *)topMostController
{
NSMutableArray<UIViewController*> *controllersHierarchy = [[NSMutableArray alloc] init];
UIViewController *topController = self.window.rootViewController;
if (topController)
{
[controllersHierarchy addObject:topController];
}
while ([topController presentedViewController]) {
topController = [topController presentedViewController];
[controllersHierarchy addObject:topController];
}
UIViewController *matchController = [self viewContainingController];
while (matchController && [controllersHierarchy containsObject:matchController] == NO)
{
do
{
matchController = (UIViewController*)[matchController nextResponder];
} while (matchController && [matchController isKindOfClass:[UIViewController class]] == NO);
}
return matchController;
}
-(UIViewController *)parentContainerViewController
{
UIViewController *matchController = [self viewContainingController];
UIViewController *parentContainerViewController = nil;
if (matchController.navigationController)
{
UINavigationController *navController = matchController.navigationController;
while (navController.navigationController) {
navController = navController.navigationController;
}
UIViewController *parentController = navController;
UIViewController *parentParentController = parentController.parentViewController;
while (parentParentController &&
([parentParentController isKindOfClass:[UINavigationController class]] == NO &&
[parentParentController isKindOfClass:[UITabBarController class]] == NO &&
[parentParentController isKindOfClass:[UISplitViewController class]] == NO))
{
parentController = parentParentController;
parentParentController = parentController.parentViewController;
}
if (navController == parentController)
{
parentContainerViewController = navController.topViewController;
}
else
{
parentContainerViewController = parentController;
}
}
else if (matchController.tabBarController)
{
if ([matchController.tabBarController.selectedViewController isKindOfClass:[UINavigationController class]])
{
parentContainerViewController = [(UINavigationController*)matchController.tabBarController.selectedViewController topViewController];
}
else
{
parentContainerViewController = matchController.tabBarController.selectedViewController;
}
}
else
{
UIViewController *matchParentController = matchController.parentViewController;
while (matchParentController &&
([matchParentController isKindOfClass:[UINavigationController class]] == NO &&
[matchParentController isKindOfClass:[UITabBarController class]] == NO &&
[matchParentController isKindOfClass:[UISplitViewController class]] == NO))
{
matchController = matchParentController;
matchParentController = matchController.parentViewController;
}
parentContainerViewController = matchController;
}
UIViewController *finalController = [parentContainerViewController parentIQContainerViewController] ?: parentContainerViewController;
return finalController;
}
-(UIView*)superviewOfClassType:(nonnull Class)classType
{
return [self superviewOfClassType:classType belowView:nil];
}
-(nullable __kindof UIView*)superviewOfClassType:(nonnull Class)classType belowView:(nullable UIView*)belowView
{
UIView *superview = self.superview;
while (superview)
{
if ([superview isKindOfClass:classType])
{
//If it's UIScrollView, then validating for special cases
if ([superview isKindOfClass:[UIScrollView class]])
{
NSString *classNameString = NSStringFromClass([superview class]);
// If it's not UITableViewWrapperView class, this is internal class which is actually manage in UITableview. The speciality of this class is that it's superview is UITableView.
// If it's not UITableViewCellScrollView class, this is internal class which is actually manage in UITableviewCell. The speciality of this class is that it's superview is UITableViewCell.
//If it's not _UIQueuingScrollView class, actually we validate for _ prefix which usually used by Apple internal classes
if ([superview.superview isKindOfClass:[UITableView class]] == NO &&
[superview.superview isKindOfClass:[UITableViewCell class]] == NO &&
[classNameString hasPrefix:@"_"] == NO)
{
return superview;
}
}
else
{
return superview;
}
}
else if (belowView == superview)
{
return nil;
}
superview = superview.superview;
}
return nil;
}
-(BOOL)_IQcanBecomeFirstResponder
{
BOOL _IQcanBecomeFirstResponder = NO;
if ([self conformsToProtocol:@protocol(UITextInput)]) {
if ([self respondsToSelector:@selector(isEditable)] && [self isKindOfClass:[UIScrollView class]])
{
_IQcanBecomeFirstResponder = [(UITextView*)self isEditable];
}
else if ([self respondsToSelector:@selector(isEnabled)])
{
_IQcanBecomeFirstResponder = [(UITextField*)self isEnabled];
}
}
if (_IQcanBecomeFirstResponder == YES)
{
_IQcanBecomeFirstResponder = ([self isUserInteractionEnabled] && ![self isHidden] && [self alpha]!=0.0 && ![self isAlertViewTextField] && !self.textFieldSearchBar);
}
return _IQcanBecomeFirstResponder;
}
- (NSArray<UIView*>*)responderSiblings
{
// Getting all siblings
NSArray<UIView*> *siblings = self.superview.subviews;
//Array of (UITextField/UITextView's).
NSMutableArray<UIView*> *tempTextFields = [[NSMutableArray alloc] init];
for (UIView *textField in siblings)
if ((textField == self || textField.ignoreSwitchingByNextPrevious == NO) && [textField _IQcanBecomeFirstResponder])
[tempTextFields addObject:textField];
return tempTextFields;
}
- (NSArray<UIView*>*)deepResponderViews
{
NSMutableArray<UIView*> *textFields = [[NSMutableArray alloc] init];
for (UIView *textField in self.subviews)
{
if ((textField == self || textField.ignoreSwitchingByNextPrevious == NO) && [textField _IQcanBecomeFirstResponder])
{
[textFields addObject:textField];
}
//Sometimes there are hidden or disabled views and textField inside them still recorded, so we added some more validations here (Bug ID: #458)
//Uncommented else (Bug ID: #625)
else if (textField.subviews.count && [textField isUserInteractionEnabled] && ![textField isHidden] && [textField alpha]!=0.0)
{
[textFields addObjectsFromArray:[textField deepResponderViews]];
}
}
//subviews are returning in incorrect order. Sorting according the frames 'y'.
return [textFields sortedArrayUsingComparator:^NSComparisonResult(UIView *view1, UIView *view2) {
CGRect frame1 = [view1 convertRect:view1.bounds toView:self];
CGRect frame2 = [view2 convertRect:view2.bounds toView:self];
CGFloat x1 = CGRectGetMinX(frame1);
CGFloat y1 = CGRectGetMinY(frame1);
CGFloat x2 = CGRectGetMinX(frame2);
CGFloat y2 = CGRectGetMinY(frame2);
if (y1 < y2) return NSOrderedAscending;
else if (y1 > y2) return NSOrderedDescending;
//Else both y are same so checking for x positions
else if (x1 < x2) return NSOrderedAscending;
else if (x1 > x2) return NSOrderedDescending;
else return NSOrderedSame;
}];
return textFields;
}
-(CGAffineTransform)convertTransformToView:(UIView*)toView
{
if (toView == nil)
{
toView = self.window;
}
CGAffineTransform myTransform = CGAffineTransformIdentity;
//My Transform
{
UIView *superView = [self superview];
if (superView) myTransform = CGAffineTransformConcat(self.transform, [superView convertTransformToView:nil]);
else myTransform = self.transform;
}
CGAffineTransform viewTransform = CGAffineTransformIdentity;
//view Transform
{
UIView *superView = [toView superview];
if (superView) viewTransform = CGAffineTransformConcat(toView.transform, [superView convertTransformToView:nil]);
else if (toView) viewTransform = toView.transform;
}
return CGAffineTransformConcat(myTransform, CGAffineTransformInvert(viewTransform));
}
- (NSInteger)depth
{
NSInteger depth = 0;
if ([self superview])
{
depth = [[self superview] depth] + 1;
}
return depth;
}
- (NSString *)subHierarchy
{
NSMutableString *debugInfo = [[NSMutableString alloc] initWithString:@"\n"];
NSInteger depth = [self depth];
for (int counter = 0; counter < depth; counter ++) [debugInfo appendString:@"| "];
[debugInfo appendString:[self debugHierarchy]];
for (UIView *subview in self.subviews)
{
[debugInfo appendString:[subview subHierarchy]];
}
return debugInfo;
}
- (NSString *)superHierarchy
{
NSMutableString *debugInfo = [[NSMutableString alloc] init];
if (self.superview)
{
[debugInfo appendString:[self.superview superHierarchy]];
}
else
{
[debugInfo appendString:@"\n"];
}
NSInteger depth = [self depth];
for (int counter = 0; counter < depth; counter ++) [debugInfo appendString:@"| "];
[debugInfo appendString:[self debugHierarchy]];
[debugInfo appendString:@"\n"];
return debugInfo;
}
-(NSString *)debugHierarchy
{
NSMutableString *debugInfo = [[NSMutableString alloc] init];
[debugInfo appendFormat:@"%@: ( %.0f, %.0f, %.0f, %.0f )",NSStringFromClass([self class]), CGRectGetMinX(self.frame), CGRectGetMinY(self.frame), CGRectGetWidth(self.frame), CGRectGetHeight(self.frame)];
if ([self isKindOfClass:[UIScrollView class]])
{
UIScrollView *scrollView = (UIScrollView*)self;
[debugInfo appendFormat:@"%@: ( %.0f, %.0f )",NSStringFromSelector(@selector(contentSize)),scrollView.contentSize.width,scrollView.contentSize.height];
}
if (CGAffineTransformEqualToTransform(self.transform, CGAffineTransformIdentity) == false)
{
[debugInfo appendFormat:@"%@: %@",NSStringFromSelector(@selector(transform)),NSStringFromCGAffineTransform(self.transform)];
}
return debugInfo;
}
-(UISearchBar *)textFieldSearchBar
{
UIResponder *searchBar = [self nextResponder];
while (searchBar)
{
if ([searchBar isKindOfClass:[UISearchBar class]])
{
return (UISearchBar*)searchBar;
}
else if ([searchBar isKindOfClass:[UIViewController class]]) //If found viewcontroller but still not found UISearchBar then it's not the search bar textfield
{
break;
}
searchBar = [searchBar nextResponder];
}
return nil;
}
-(BOOL)isAlertViewTextField
{
UIResponder *alertViewController = [self viewContainingController];
BOOL isAlertViewTextField = NO;
while (alertViewController && isAlertViewTextField == NO)
{
if ([alertViewController isKindOfClass:[UIAlertController class]])
{
isAlertViewTextField = YES;
break;
}
alertViewController = [alertViewController nextResponder];
}
return isAlertViewTextField;
}
@end
@implementation NSObject (IQ_Logging)
-(NSString *)_IQDescription
{
return [NSString stringWithFormat:@"<%@ %p>",NSStringFromClass([self class]),self];
}
@end
//
// IQUIViewController+Additions.h
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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/UIViewController.h>
@class NSLayoutConstraint;
@interface UIViewController (Additions)
/**
This method is provided to override by viewController's if the library lifts a viewController which you doesn't want to lift . This may happen if you have implemented side menu feature in your app and the library try to lift the side menu controller. Overriding this method in side menu class to return correct controller should fix the problem.
*/
-(nullable UIViewController*)parentIQContainerViewController;
/**
Top/Bottom Layout constraint which help library to manage keyboardTextField distance
@deprecated Due to change in core-logic of handling distance between textField and keyboard distance, this layout contraint tweak is no longer needed and things will just work out of the box regardless of constraint pinned with safeArea/layoutGuide/superview.
*/
@property(nullable, nonatomic, strong) IBOutlet NSLayoutConstraint *IQLayoutGuideConstraint __attribute__((deprecated("Due to change in core-logic of handling distance between textField and keyboard distance, this layout contraint tweak is no longer needed and things will just work out of the box regardless of constraint pinned with safeArea/layoutGuide/superview.")));
@end
//
// IQUIViewController+Additions.m
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 "IQUIViewController+Additions.h"
#import <UIKit/NSLayoutConstraint.h>
#import <objc/runtime.h>
@implementation UIViewController (Additions)
-(nullable UIViewController*)parentIQContainerViewController
{
return self;
}
-(void)setIQLayoutGuideConstraint:(NSLayoutConstraint *)IQLayoutGuideConstraint
{
objc_setAssociatedObject(self, @selector(IQLayoutGuideConstraint), IQLayoutGuideConstraint, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSLayoutConstraint *)IQLayoutGuideConstraint
{
return objc_getAssociatedObject(self, @selector(IQLayoutGuideConstraint));
}
@end
//
// IQKeyboardManagerConstants.h
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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.
#ifndef IQKeyboardManagerConstants_h
#define IQKeyboardManagerConstants_h
#import <Foundation/NSObjCRuntime.h>
///-----------------------------------
/// @name IQAutoToolbarManageBehaviour
///-----------------------------------
/**
`IQAutoToolbarBySubviews`
Creates Toolbar according to subview's hirarchy of Textfield's in view.
`IQAutoToolbarByTag`
Creates Toolbar according to tag property of TextField's.
`IQAutoToolbarByPosition`
Creates Toolbar according to the y,x position of textField in it's superview coordinate.
*/
typedef NS_ENUM(NSInteger, IQAutoToolbarManageBehaviour) {
IQAutoToolbarBySubviews,
IQAutoToolbarByTag,
IQAutoToolbarByPosition,
};
/**
`IQPreviousNextDisplayModeDefault`
Show NextPrevious when there are more than 1 textField otherwise hide.
`IQPreviousNextDisplayModeAlwaysHide`
Do not show NextPrevious buttons in any case.
`IQPreviousNextDisplayModeAlwaysShow`
Always show nextPrevious buttons, if there are more than 1 textField then both buttons will be visible but will be shown as disabled.
*/
typedef NS_ENUM(NSUInteger, IQPreviousNextDisplayMode) {
IQPreviousNextDisplayModeDefault,
IQPreviousNextDisplayModeAlwaysHide,
IQPreviousNextDisplayModeAlwaysShow,
};
/**
`IQEnableModeDefault`
Pick default settings.
`IQEnableModeEnabled`
setting is enabled.
`IQEnableModeDisabled`
setting is disabled.
*/
typedef NS_ENUM(NSUInteger, IQEnableMode) {
IQEnableModeDefault,
IQEnableModeEnabled,
IQEnableModeDisabled,
};
#endif
/*
/---------------------------------------------------------------------------------------------------\
\---------------------------------------------------------------------------------------------------/
| iOS NSNotification Mechanism |
/---------------------------------------------------------------------------------------------------\
\---------------------------------------------------------------------------------------------------/
------------------------------------------------------------
When UITextField become first responder
------------------------------------------------------------
- UITextFieldTextDidBeginEditingNotification (UITextField)
- UIKeyboardWillShowNotification
- UIKeyboardDidShowNotification
------------------------------------------------------------
When UITextView become first responder
------------------------------------------------------------
- UIKeyboardWillShowNotification
- UITextViewTextDidBeginEditingNotification (UITextView)
- UIKeyboardDidShowNotification
------------------------------------------------------------
When switching focus from UITextField to another UITextField
------------------------------------------------------------
- UITextFieldTextDidEndEditingNotification (UITextField1)
- UITextFieldTextDidBeginEditingNotification (UITextField2)
- UIKeyboardWillShowNotification
- UIKeyboardDidShowNotification
------------------------------------------------------------
When switching focus from UITextView to another UITextView
------------------------------------------------------------
- UITextViewTextDidEndEditingNotification : (UITextView1)
- UIKeyboardWillShowNotification
- UITextViewTextDidBeginEditingNotification : (UITextView2)
- UIKeyboardDidShowNotification
------------------------------------------------------------
When switching focus from UITextField to UITextView
------------------------------------------------------------
- UITextFieldTextDidEndEditingNotification (UITextField)
- UIKeyboardWillShowNotification
- UITextViewTextDidBeginEditingNotification (UITextView)
- UIKeyboardDidShowNotification
------------------------------------------------------------
When switching focus from UITextView to UITextField
------------------------------------------------------------
- UITextViewTextDidEndEditingNotification (UITextView)
- UITextFieldTextDidBeginEditingNotification (UITextField)
- UIKeyboardWillShowNotification
- UIKeyboardDidShowNotification
------------------------------------------------------------
When opening/closing UIKeyboard Predictive bar
------------------------------------------------------------
- UIKeyboardWillShowNotification
- UIKeyboardDidShowNotification
------------------------------------------------------------
On orientation change
------------------------------------------------------------
- UIApplicationWillChangeStatusBarOrientationNotification
- UIKeyboardWillHideNotification
- UIKeyboardDidHideNotification
- UIApplicationDidChangeStatusBarOrientationNotification
- UIKeyboardWillShowNotification
- UIKeyboardDidShowNotification
- UIKeyboardWillShowNotification
- UIKeyboardDidShowNotification
*/
//
// IQKeyboardManagerConstantsInternal.h
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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.
#ifndef IQKeyboardManagerConstantsInternal_h
#define IQKeyboardManagerConstantsInternal_h
#endif
//
// IQKeyboardManager.h
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 "IQKeyboardManagerConstants.h"
#import "IQUIView+IQKeyboardToolbar.h"
#import "IQPreviousNextView.h"
#import "IQUIViewController+Additions.h"
#import "IQKeyboardReturnKeyHandler.h"
#import "IQTextView.h"
#import "IQToolbar.h"
#import "IQUIScrollView+Additions.h"
#import "IQUITextFieldView+Additions.h"
#import "IQBarButtonItem.h"
#import "IQTitleBarButtonItem.h"
#import "IQUIView+Hierarchy.h"
#import <CoreGraphics/CGBase.h>
#import <Foundation/NSObject.h>
#import <Foundation/NSObjCRuntime.h>
#import <Foundation/NSSet.h>
#import <UIKit/UITextInputTraits.h>
@class UIFont, UIColor, UITapGestureRecognizer, UIView, UIImage;
@class NSString;
///---------------------
/// @name IQToolbar tags
///---------------------
/**
Default tag for toolbar with Done button -1002.
*/
extern NSInteger const kIQDoneButtonToolbarTag;
/**
Default tag for toolbar with Previous/Next buttons -1005.
*/
extern NSInteger const kIQPreviousNextButtonToolbarTag;
/**
Codeless drop-in universal library allows to prevent issues of keyboard sliding up and cover UITextField/UITextView. Neither need to write any code nor any setup required and much more. A generic version of KeyboardManagement. https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html
*/
@interface IQKeyboardManager : NSObject
///--------------------------
/// @name UIKeyboard handling
///--------------------------
/**
Returns the default singleton instance. You are not allowed to create your own instances of this class.
*/
+ (nonnull instancetype)sharedManager;
/**
Enable/disable managing distance between keyboard and textField. Default is YES(Enabled when class loads in `+(void)load` method).
*/
@property(nonatomic, assign, getter = isEnabled) BOOL enable;
/**
To set keyboard distance from textField. can't be less than zero. Default is 10.0.
*/
@property(nonatomic, assign) CGFloat keyboardDistanceFromTextField;
/**
Refreshes textField/textView position if any external changes is explicitly made by user.
*/
- (void)reloadLayoutIfNeeded;
/**
Boolean to know if keyboard is showing.
*/
@property(nonatomic, assign, readonly, getter = isKeyboardShowing) BOOL keyboardShowing;
/**
moved distance to the top used to maintain distance between keyboard and textField. Most of the time this will be a positive value.
*/
@property(nonatomic, assign, readonly) CGFloat movedDistance;
/**
Will be called then movedDistance will be changed.
*/
@property(nullable, nonatomic, copy) void (^movedDistanceChanged)(CGFloat movedDistance);
///-------------------------
/// @name IQToolbar handling
///-------------------------
/**
Automatic add IQToolbar functionality. Default is YES.
*/
@property(nonatomic, assign, getter = isEnableAutoToolbar) BOOL enableAutoToolbar;
/**
IQAutoToolbarBySubviews: Creates Toolbar according to subview's hirarchy of Textfield's in view.
IQAutoToolbarByTag: Creates Toolbar according to tag property of TextField's.
IQAutoToolbarByPosition: Creates Toolbar according to the y,x position of textField in it's superview coordinate.
Default is IQAutoToolbarBySubviews.
*/
@property(nonatomic, assign) IQAutoToolbarManageBehaviour toolbarManageBehaviour;
/**
If YES, then uses textField's tintColor property for IQToolbar, otherwise tint color is nil. Default is NO.
*/
@property(nonatomic, assign) BOOL shouldToolbarUsesTextFieldTintColor;
/**
This is used for toolbar.tintColor when textfield.keyboardAppearance is UIKeyboardAppearanceDefault. If shouldToolbarUsesTextFieldTintColor is YES then this property is ignored. Default is nil.
*/
@property(nullable, nonatomic, strong) UIColor *toolbarTintColor;
/**
This is used for toolbar.barTintColor. Default is nil.
*/
@property(nullable, nonatomic, strong) UIColor *toolbarBarTintColor;
/**
IQPreviousNextDisplayModeDefault: Show NextPrevious when there are more than 1 textField otherwise hide.
IQPreviousNextDisplayModeAlwaysHide: Do not show NextPrevious buttons in any case.
IQPreviousNextDisplayModeAlwaysShow: Always show nextPrevious buttons, if there are more than 1 textField then both buttons will be visible but will be shown as disabled.
*/
@property(nonatomic, assign) IQPreviousNextDisplayMode previousNextDisplayMode;
/**
Toolbar previous/next/done button icon, If nothing is provided then check toolbarDoneBarButtonItemText to draw done button.
*/
@property(nullable, nonatomic, strong) UIImage *toolbarPreviousBarButtonItemImage;
@property(nullable, nonatomic, strong) UIImage *toolbarNextBarButtonItemImage;
@property(nullable, nonatomic, strong) UIImage *toolbarDoneBarButtonItemImage;
/**
Toolbar previous/next/done button text, If nothing is provided then system default 'UIBarButtonSystemItemDone' will be used.
*/
@property(nullable, nonatomic, strong) NSString *toolbarPreviousBarButtonItemText;
@property(nullable, nonatomic, strong) NSString *toolbarPreviousBarButtonItemAccessibilityLabel;
@property(nullable, nonatomic, strong) NSString *toolbarNextBarButtonItemText;
@property(nullable, nonatomic, strong) NSString *toolbarNextBarButtonItemAccessibilityLabel;
@property(nullable, nonatomic, strong) NSString *toolbarDoneBarButtonItemText;
@property(nullable, nonatomic, strong) NSString *toolbarDoneBarButtonItemAccessibilityLabel;
/**
If YES, then it add the textField's placeholder text on IQToolbar. Default is YES.
*/
@property(nonatomic, assign) BOOL shouldShowToolbarPlaceholder;
/**
Placeholder Font. Default is nil.
*/
@property(nullable, nonatomic, strong) UIFont *placeholderFont;
/**
Placeholder Color. Default is nil. Which means lightGray
*/
@property(nullable, nonatomic, strong) UIColor *placeholderColor;
/**
Placeholder Button Color when it's treated as button. Default is nil
*/
@property(nullable, nonatomic, strong) UIColor *placeholderButtonColor;
/**
Reload all toolbar buttons on the fly.
*/
- (void)reloadInputViews;
///---------------------------------------
/// @name UIKeyboard appearance overriding
///---------------------------------------
/**
Override the keyboardAppearance for all textField/textView. Default is NO.
*/
@property(nonatomic, assign) BOOL overrideKeyboardAppearance;
/**
If overrideKeyboardAppearance is YES, then all the textField keyboardAppearance is set using this property.
*/
@property(nonatomic, assign) UIKeyboardAppearance keyboardAppearance;
///-----------------------------------------------------------
/// @name UITextField/UITextView Next/Previous/Resign handling
///-----------------------------------------------------------
/**
Resigns Keyboard on touching outside of UITextField/View. Default is NO.
*/
@property(nonatomic, assign) BOOL shouldResignOnTouchOutside;
/** TapGesture to resign keyboard on view's touch. It's a readonly property and exposed only for adding/removing dependencies if your added gesture does have collision with this one */
@property(nonnull, nonatomic, strong, readonly) UITapGestureRecognizer *resignFirstResponderGesture;
/**
Resigns currently first responder field.
*/
- (BOOL)resignFirstResponder;
/**
Returns YES if can navigate to previous responder textField/textView, otherwise NO.
*/
@property (nonatomic, readonly) BOOL canGoPrevious;
/**
Returns YES if can navigate to next responder textField/textView, otherwise NO.
*/
@property (nonatomic, readonly) BOOL canGoNext;
/**
Navigate to previous responder textField/textView.
*/
- (BOOL)goPrevious;
/**
Navigate to next responder textField/textView.
*/
- (BOOL)goNext;
///-----------------------
/// @name UISound handling
///-----------------------
/**
If YES, then it plays inputClick sound on next/previous/done click. Default is YES.
*/
@property(nonatomic, assign) BOOL shouldPlayInputClicks;
///---------------------------
/// @name UIAnimation handling
///---------------------------
/**
If YES, then calls 'setNeedsLayout' and 'layoutIfNeeded' on any frame update of to viewController's view.
*/
@property(nonatomic, assign) BOOL layoutIfNeededOnUpdate;
///---------------------------------------------
/// @name Class Level enabling/disabling methods
///---------------------------------------------
/**
Disable distance handling within the scope of disabled distance handling viewControllers classes. Within this scope, 'enabled' property is ignored. Class should be kind of UIViewController. Default is [UITableViewController, UIAlertController, _UIAlertControllerTextFieldViewController].
*/
@property(nonatomic, strong, nonnull, readonly) NSMutableSet<Class> *disabledDistanceHandlingClasses;
/**
Enable distance handling within the scope of enabled distance handling viewControllers classes. Within this scope, 'enabled' property is ignored. Class should be kind of UIViewController. Default is [].
*/
@property(nonatomic, strong, nonnull, readonly) NSMutableSet<Class> *enabledDistanceHandlingClasses;
/**
Disable automatic toolbar creation within the scope of disabled toolbar viewControllers classes. Within this scope, 'enableAutoToolbar' property is ignored. Class should be kind of UIViewController. Default is [UIAlertController, _UIAlertControllerTextFieldViewController].
*/
@property(nonatomic, strong, nonnull, readonly) NSMutableSet<Class> *disabledToolbarClasses;
/**
Enable automatic toolbar creation within the scope of enabled toolbar viewControllers classes. Within this scope, 'enableAutoToolbar' property is ignored. Class should be kind of UIViewController. Default is [].
*/
@property(nonatomic, strong, nonnull, readonly) NSMutableSet<Class> *enabledToolbarClasses;
/**
Allowed subclasses of UIView to add all inner textField, this will allow to navigate between textField contains in different superview. Class should be kind of UIView. Default is [UITableView, UICollectionView, IQPreviousNextView].
*/
@property(nonatomic, strong, nonnull, readonly) NSMutableSet<Class> *toolbarPreviousNextAllowedClasses;
/**
Disabled classes to ignore 'shouldResignOnTouchOutside' property, Class should be kind of UIViewController. Default is [UIAlertController, UIAlertControllerTextFieldViewController]
*/
@property(nonatomic, strong, nonnull, readonly) NSMutableSet<Class> *disabledTouchResignedClasses;
/**
Enabled classes to forcefully enable 'shouldResignOnTouchOutsite' property. Class should be kind of UIViewController. Default is [].
*/
@property(nonatomic, strong, nonnull, readonly) NSMutableSet<Class> *enabledTouchResignedClasses;
/**
if shouldResignOnTouchOutside is enabled then you can customise the behaviour to not recognise gesture touches on some specific view subclasses. Class should be kind of UIView. Default is [UIControl, UINavigationBar]
*/
@property(nonatomic, strong, nonnull, readonly) NSMutableSet<Class> *touchResignedGestureIgnoreClasses;
///-------------------------------------------
/// @name Third Party Library support
/// Add TextField/TextView Notifications customised NSNotifications. For example while using YYTextView https://github.com/ibireme/YYText
///-------------------------------------------
/**
Add/Remove customised Notification for third party customised TextField/TextView. Please be aware that the NSNotification object must be identical to UITextField/UITextView NSNotification objects and customised TextField/TextView support must be identical to UITextField/UITextView.
@param didBeginEditingNotificationName This should be identical to UITextViewTextDidBeginEditingNotification
@param didEndEditingNotificationName This should be identical to UITextViewTextDidEndEditingNotification
*/
-(void)registerTextFieldViewClass:(nonnull Class)aClass
didBeginEditingNotificationName:(nonnull NSString *)didBeginEditingNotificationName
didEndEditingNotificationName:(nonnull NSString *)didEndEditingNotificationName;
-(void)unregisterTextFieldViewClass:(nonnull Class)aClass
didBeginEditingNotificationName:(nonnull NSString *)didBeginEditingNotificationName
didEndEditingNotificationName:(nonnull NSString *)didEndEditingNotificationName;
///----------------------------------------
/// @name Debugging & Developer options
///----------------------------------------
@property(nonatomic, assign) BOOL enableDebugging;
/**
@warning Use these methods to completely enable/disable notifications registered by library internally. Please keep in mind that library is totally dependent on NSNotification of UITextField, UITextField, Keyboard etc. If you do unregisterAllNotifications then library will not work at all. You should only use below methods if you want to completely disable all library functions. You should use below methods at your own risk.
*/
-(void)registerAllNotifications;
-(void)unregisterAllNotifications;
///----------------------------------------
/// @name Must not be used for subclassing.
///----------------------------------------
/**
Unavailable. Please use sharedManager method
*/
-(nonnull instancetype)init NS_UNAVAILABLE;
/**
Unavailable. Please use sharedManager method
*/
+ (nonnull instancetype)new NS_UNAVAILABLE;
@end
//
// IQKeyboardReturnKeyHandler.h
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 "IQKeyboardManagerConstants.h"
#import <Foundation/NSObject.h>
#import <Foundation/NSObjCRuntime.h>
#import <UIKit/UITextInputTraits.h>
@class UITextField, UIView, UIViewController;
@protocol UITextFieldDelegate, UITextViewDelegate;
/**
Manages the return key to work like next/done in a view hierarchy.
*/
@interface IQKeyboardReturnKeyHandler : NSObject
///----------------------
/// @name Initializations
///----------------------
/**
Add all the textFields available in UIViewController's view.
*/
-(nonnull instancetype)initWithViewController:(nullable UIViewController*)controller NS_DESIGNATED_INITIALIZER;
/**
Unavailable. Please use initWithViewController: or init method
*/
-(nonnull instancetype)initWithCoder:(nullable NSCoder *)aDecoder NS_UNAVAILABLE;
///---------------
/// @name Settings
///---------------
/**
Delegate of textField/textView.
*/
@property(nullable, nonatomic, weak) id<UITextFieldDelegate,UITextViewDelegate> delegate;
/**
Set the last textfield return key type. Default is UIReturnKeyDefault.
*/
@property(nonatomic, assign) UIReturnKeyType lastTextFieldReturnKeyType;
///----------------------------------------------
/// @name Registering/Unregistering textFieldView
///----------------------------------------------
/**
Should pass UITextField/UITextView instance. Assign textFieldView delegate to self, change it's returnKeyType.
@param textFieldView UITextField/UITextView object to register.
*/
-(void)addTextFieldView:(nonnull UIView*)textFieldView;
/**
Should pass UITextField/UITextView instance. Restore it's textFieldView delegate and it's returnKeyType.
@param textFieldView UITextField/UITextView object to unregister.
*/
-(void)removeTextFieldView:(nonnull UIView*)textFieldView;
/**
Add all the UITextField/UITextView responderView's.
@param view object to register all it's responder subviews.
*/
-(void)addResponderFromView:(nonnull UIView*)view;
/**
Remove all the UITextField/UITextView responderView's.
@param view object to unregister all it's responder subviews.
*/
-(void)removeResponderFromView:(nonnull UIView*)view;
@end
//
// IQKeyboardReturnKeyHandler.m
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 "IQKeyboardReturnKeyHandler.h"
#import "IQKeyboardManager.h"
#import "IQUIView+Hierarchy.h"
#import "IQNSArray+Sort.h"
#import <UIKit/UITextField.h>
#import <UIKit/UITextView.h>
#import <UIKit/UIViewController.h>
@interface IQTextFieldViewInfoModal : NSObject
@property(nullable, nonatomic, weak) UIView *textFieldView;
@property(nullable, nonatomic, weak) id<UITextFieldDelegate> textFieldDelegate;
@property(nullable, nonatomic, weak) id<UITextViewDelegate> textViewDelegate;
@property(nonatomic) UIReturnKeyType originalReturnKeyType;
@end
@implementation IQTextFieldViewInfoModal
-(instancetype)initWithTextFieldView:(UIView*)textFieldView textFieldDelegate:(id<UITextFieldDelegate>)textFieldDelegate textViewDelegate:(id<UITextViewDelegate>)textViewDelegate originalReturnKey:(UIReturnKeyType)returnKeyType
{
self = [super init];
if (self)
{
_textFieldView = textFieldView;
_textFieldDelegate = textFieldDelegate;
_textViewDelegate = textViewDelegate;
_originalReturnKeyType = returnKeyType;
}
return self;
}
@end
@interface IQKeyboardReturnKeyHandler ()<UITextFieldDelegate,UITextViewDelegate>
-(void)updateReturnKeyTypeOnTextField:(UIView*)textField;
@end
@implementation IQKeyboardReturnKeyHandler
{
NSMutableSet<IQTextFieldViewInfoModal*> *textFieldInfoCache;
}
@synthesize lastTextFieldReturnKeyType = _lastTextFieldReturnKeyType;
@synthesize delegate = _delegate;
- (instancetype)init
{
self = [self initWithViewController:nil];
return self;
}
-(instancetype)initWithViewController:(nullable UIViewController*)controller
{
self = [super init];
if (self)
{
textFieldInfoCache = [[NSMutableSet alloc] init];
if (controller.view)
{
[self addResponderFromView:controller.view];
}
}
return self;
}
-(IQTextFieldViewInfoModal*)textFieldViewCachedInfo:(UIView*)textField
{
for (IQTextFieldViewInfoModal *modal in textFieldInfoCache)
if (modal.textFieldView == textField) return modal;
return nil;
}
#pragma mark - Add/Remove TextFields
-(void)addResponderFromView:(UIView*)view
{
NSArray<UIView*> *textFields = [view deepResponderViews];
for (UIView *textField in textFields) [self addTextFieldView:textField];
}
-(void)removeResponderFromView:(UIView*)view
{
NSArray<UIView*> *textFields = [view deepResponderViews];
for (UIView *textField in textFields) [self removeTextFieldView:textField];
}
-(void)removeTextFieldView:(UIView*)view
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:view];
if (modal)
{
UITextField *textField = (UITextField*)view;
if ([view respondsToSelector:@selector(setReturnKeyType:)])
{
textField.returnKeyType = modal.originalReturnKeyType;
}
if ([view respondsToSelector:@selector(setDelegate:)])
{
textField.delegate = modal.textFieldDelegate;
}
[textFieldInfoCache removeObject:modal];
}
}
-(void)addTextFieldView:(UIView*)view
{
IQTextFieldViewInfoModal *modal = [[IQTextFieldViewInfoModal alloc] initWithTextFieldView:view textFieldDelegate:nil textViewDelegate:nil originalReturnKey:UIReturnKeyDefault];
UITextField *textField = (UITextField*)view;
if ([view respondsToSelector:@selector(setReturnKeyType:)])
{
modal.originalReturnKeyType = textField.returnKeyType;
}
if ([view respondsToSelector:@selector(setDelegate:)])
{
modal.textFieldDelegate = textField.delegate;
[textField setDelegate:self];
}
[textFieldInfoCache addObject:modal];
}
-(void)updateReturnKeyTypeOnTextField:(UIView*)textField
{
UIView *superConsideredView;
//If find any consider responderView in it's upper hierarchy then will get deepResponderView. (Bug ID: #347)
for (Class consideredClass in [[IQKeyboardManager sharedManager] toolbarPreviousNextAllowedClasses])
{
superConsideredView = [textField superviewOfClassType:consideredClass];
if (superConsideredView)
break;
}
NSArray<UIView*> *textFields = nil;
//If there is a tableView in view's hierarchy, then fetching all it's subview that responds. No sorting for tableView, it's by subView position.
if (superConsideredView) // // (Enhancement ID: #22)
{
textFields = [superConsideredView deepResponderViews];
}
//Otherwise fetching all the siblings
else
{
textFields = [textField responderSiblings];
//Sorting textFields according to behaviour
switch ([[IQKeyboardManager sharedManager] toolbarManageBehaviour])
{
//If needs to sort it by tag
case IQAutoToolbarByTag:
textFields = [textFields sortedArrayByTag];
break;
//If needs to sort it by Position
case IQAutoToolbarByPosition:
textFields = [textFields sortedArrayByPosition];
break;
default:
break;
}
}
//If it's the last textField in responder view, else next
[(UITextField*)textField setReturnKeyType:(([textFields lastObject] == textField) ? self.lastTextFieldReturnKeyType : UIReturnKeyNext)];
}
#pragma mark - Goto next or Resign.
-(BOOL)goToNextResponderOrResign:(UIView*)textField
{
UIView *superConsideredView;
//If find any consider responderView in it's upper hierarchy then will get deepResponderView. (Bug ID: #347)
for (Class consideredClass in [[IQKeyboardManager sharedManager] toolbarPreviousNextAllowedClasses])
{
superConsideredView = [textField superviewOfClassType:consideredClass];
if (superConsideredView)
break;
}
NSArray<UIView*> *textFields = nil;
//If there is a tableView in view's hierarchy, then fetching all it's subview that responds. No sorting for tableView, it's by subView position.
if (superConsideredView) // // (Enhancement ID: #22)
{
textFields = [superConsideredView deepResponderViews];
}
//Otherwise fetching all the siblings
else
{
textFields = [textField responderSiblings];
//Sorting textFields according to behaviour
switch ([[IQKeyboardManager sharedManager] toolbarManageBehaviour])
{
//If needs to sort it by tag
case IQAutoToolbarByTag:
textFields = [textFields sortedArrayByTag];
break;
//If needs to sort it by Position
case IQAutoToolbarByPosition:
textFields = [textFields sortedArrayByPosition];
break;
default:
break;
}
}
//Getting index of current textField.
NSUInteger index = [textFields indexOfObject:textField];
//If it is not last textField. then it's next object becomeFirstResponder.
if (index != NSNotFound && index < textFields.count-1)
{
[textFields[index+1] becomeFirstResponder];
return NO;
}
else
{
[textField resignFirstResponder];
return YES;
}
}
#pragma mark - TextField delegate
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
id<UITextFieldDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textField];
delegate = modal.textFieldDelegate;
}
if ([delegate respondsToSelector:@selector(textFieldShouldBeginEditing:)])
return [delegate textFieldShouldBeginEditing:textField];
else
return YES;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
[self updateReturnKeyTypeOnTextField:textField];
id<UITextFieldDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textField];
delegate = modal.textFieldDelegate;
}
if ([delegate respondsToSelector:@selector(textFieldDidBeginEditing:)])
[delegate textFieldDidBeginEditing:textField];
}
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
id<UITextFieldDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textField];
delegate = modal.textFieldDelegate;
}
if ([delegate respondsToSelector:@selector(textFieldShouldEndEditing:)])
return [delegate textFieldShouldEndEditing:textField];
else
return YES;
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
id<UITextFieldDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textField];
delegate = modal.textFieldDelegate;
}
if ([delegate respondsToSelector:@selector(textFieldDidEndEditing:)])
[delegate textFieldDidEndEditing:textField];
}
- (void)textFieldDidEndEditing:(UITextField *)textField reason:(UITextFieldDidEndEditingReason)reason NS_AVAILABLE_IOS(10_0);
{
id<UITextFieldDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textField];
delegate = modal.textFieldDelegate;
}
if (@available(iOS 10.0, *)) {
if ([delegate respondsToSelector:@selector(textFieldDidEndEditing:reason:)])
[delegate textFieldDidEndEditing:textField reason:reason];
}
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
id<UITextFieldDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textField];
delegate = modal.textFieldDelegate;
}
if ([delegate respondsToSelector:@selector(textField:shouldChangeCharactersInRange:replacementString:)])
return [delegate textField:textField shouldChangeCharactersInRange:range replacementString:string];
else
return YES;
}
- (BOOL)textFieldShouldClear:(UITextField *)textField
{
id<UITextFieldDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textField];
delegate = modal.textFieldDelegate;
}
if ([delegate respondsToSelector:@selector(textFieldShouldClear:)])
return [delegate textFieldShouldClear:textField];
else
return YES;
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
id<UITextFieldDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textField];
delegate = modal.textFieldDelegate;
}
if ([delegate respondsToSelector:@selector(textFieldShouldReturn:)])
{
BOOL shouldReturn = [delegate textFieldShouldReturn:textField];
if (shouldReturn)
{
shouldReturn = [self goToNextResponderOrResign:textField];
}
return shouldReturn;
}
else
{
return [self goToNextResponderOrResign:textField];
}
}
#pragma mark - TextView delegate
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
{
id<UITextViewDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
delegate = modal.textViewDelegate;
}
if ([delegate respondsToSelector:@selector(textViewShouldBeginEditing:)])
return [delegate textViewShouldBeginEditing:textView];
else
return YES;
}
- (BOOL)textViewShouldEndEditing:(UITextView *)textView
{
id<UITextViewDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
delegate = modal.textViewDelegate;
}
if ([delegate respondsToSelector:@selector(textViewShouldEndEditing:)])
return [delegate textViewShouldEndEditing:textView];
else
return YES;
}
- (void)textViewDidBeginEditing:(UITextView *)textView
{
[self updateReturnKeyTypeOnTextField:textView];
id<UITextViewDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
delegate = modal.textViewDelegate;
}
if ([delegate respondsToSelector:@selector(textViewDidBeginEditing:)])
[delegate textViewDidBeginEditing:textView];
}
- (void)textViewDidEndEditing:(UITextView *)textView
{
id<UITextViewDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
delegate = modal.textViewDelegate;
}
if ([delegate respondsToSelector:@selector(textViewDidEndEditing:)])
[delegate textViewDidEndEditing:textView];
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
id<UITextViewDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
delegate = modal.textViewDelegate;
}
BOOL shouldReturn = YES;
if ([delegate respondsToSelector:@selector(textView:shouldChangeTextInRange:replacementText:)])
shouldReturn = [delegate textView:textView shouldChangeTextInRange:range replacementText:text];
if (shouldReturn && [text isEqualToString:@"\n"])
{
shouldReturn = [self goToNextResponderOrResign:textView];
}
return shouldReturn;
}
- (void)textViewDidChange:(UITextView *)textView
{
id<UITextViewDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
delegate = modal.textViewDelegate;
}
if ([delegate respondsToSelector:@selector(textViewDidChange:)])
[delegate textViewDidChange:textView];
}
- (void)textViewDidChangeSelection:(UITextView *)textView
{
id<UITextViewDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
delegate = modal.textViewDelegate;
}
if ([delegate respondsToSelector:@selector(textViewDidChangeSelection:)])
[delegate textViewDidChangeSelection:textView];
}
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction NS_AVAILABLE_IOS(10_0);
{
id<UITextViewDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
delegate = modal.textViewDelegate;
}
if (@available(iOS 10.0, *)) {
if ([delegate respondsToSelector:@selector(textView:shouldInteractWithURL:inRange:interaction:)])
return [delegate textView:textView shouldInteractWithURL:URL inRange:characterRange interaction:interaction];
}
return YES;
}
- (BOOL)textView:(UITextView *)textView shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction NS_AVAILABLE_IOS(10_0);
{
id<UITextViewDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
delegate = modal.textViewDelegate;
}
if (@available(iOS 10.0, *)) {
if ([delegate respondsToSelector:@selector(textView:shouldInteractWithTextAttachment:inRange:interaction:)])
return [delegate textView:textView shouldInteractWithTextAttachment:textAttachment inRange:characterRange interaction:interaction];
}
return YES;
}
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 100000
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange
{
id<UITextViewDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
delegate = modal.textViewDelegate;
}
if ([delegate respondsToSelector:@selector(textView:shouldInteractWithURL:inRange:)])
return [delegate textView:textView shouldInteractWithURL:URL inRange:characterRange];
else
return YES;
}
- (BOOL)textView:(UITextView *)textView shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment inRange:(NSRange)characterRange
{
id<UITextViewDelegate> delegate = self.delegate;
if (delegate == nil)
{
IQTextFieldViewInfoModal *modal = [self textFieldViewCachedInfo:textView];
delegate = modal.textViewDelegate;
}
if ([delegate respondsToSelector:@selector(textView:shouldInteractWithTextAttachment:inRange:)])
return [delegate textView:textView shouldInteractWithTextAttachment:textAttachment inRange:characterRange];
else
return YES;
}
#endif
-(void)dealloc
{
for (IQTextFieldViewInfoModal *modal in textFieldInfoCache)
{
UITextField *textField = (UITextField*)modal.textFieldView;
if ([textField respondsToSelector:@selector(setReturnKeyType:)])
{
textField.returnKeyType = modal.originalReturnKeyType;
}
if ([textField respondsToSelector:@selector(setDelegate:)])
{
textField.delegate = modal.textFieldDelegate;
}
}
[textFieldInfoCache removeAllObjects];
}
@end
//
// IQTextView.h
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 "IQKeyboardManagerConstants.h"
#import <UIKit/UITextView.h>
/**
UITextView with placeholder support
*/
@interface IQTextView : UITextView
/**
Set textView's placeholder text. Default is nil.
*/
@property(nullable, nonatomic,copy) IBInspectable NSString *placeholder;
/**
Set textView's placeholder attributed text. Default is nil.
*/
@property(nullable, nonatomic,copy) IBInspectable NSAttributedString *attributedPlaceholder;
/**
To set textView's placeholder text color. Default is nil.
*/
@property(nullable, nonatomic,copy) IBInspectable UIColor *placeholderTextColor;
@end
//
// IQTextView.m
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 "IQTextView.h"
#import <UIKit/NSTextContainer.h>
#import <UIKit/UIAccessibility.h>
#import <UIKit/UILabel.h>
#import <UIKit/UINibLoading.h>
@interface IQTextView ()
@property(nullable, nonatomic, strong) UILabel *IQ_PlaceholderLabel;
@end
@implementation IQTextView
@synthesize placeholder = _placeholder;
@synthesize IQ_PlaceholderLabel = _IQ_PlaceholderLabel;
@synthesize placeholderTextColor = _placeholderTextColor;
-(void)initialize
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshPlaceholder) name:UITextViewTextDidChangeNotification object:self];
}
-(void)dealloc
{
[_IQ_PlaceholderLabel removeFromSuperview];
_IQ_PlaceholderLabel = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (instancetype)init
{
self = [super init];
if (self) {
[self initialize];
}
return self;
}
-(void)awakeFromNib
{
[super awakeFromNib];
[self initialize];
}
-(void)refreshPlaceholder
{
if([[self text] length] || [[self attributedText] length])
{
if (self.IQ_PlaceholderLabel.alpha != 0) {
[self.IQ_PlaceholderLabel setAlpha:0];
[self setNeedsLayout];
[self layoutIfNeeded];
}
}
else if(self.IQ_PlaceholderLabel.alpha != 1)
{
[self.IQ_PlaceholderLabel setAlpha:1];
[self setNeedsLayout];
[self layoutIfNeeded];
}
}
- (void)setText:(NSString *)text
{
[super setText:text];
[self refreshPlaceholder];
}
-(void)setAttributedText:(NSAttributedString *)attributedText
{
[super setAttributedText:attributedText];
[self refreshPlaceholder];
}
-(void)setFont:(UIFont *)font
{
[super setFont:font];
self.IQ_PlaceholderLabel.font = self.font;
[self setNeedsLayout];
[self layoutIfNeeded];
}
-(void)setTextAlignment:(NSTextAlignment)textAlignment
{
[super setTextAlignment:textAlignment];
self.IQ_PlaceholderLabel.textAlignment = textAlignment;
[self setNeedsLayout];
[self layoutIfNeeded];
}
-(void)layoutSubviews
{
[super layoutSubviews];
self.IQ_PlaceholderLabel.frame = [self placeholderExpectedFrame];
}
-(void)setPlaceholder:(NSString *)placeholder
{
_placeholder = placeholder;
self.IQ_PlaceholderLabel.text = placeholder;
[self refreshPlaceholder];
}
-(void)setAttributedPlaceholder:(NSAttributedString *)attributedPlaceholder
{
_attributedPlaceholder = attributedPlaceholder;
self.IQ_PlaceholderLabel.attributedText = attributedPlaceholder;
[self refreshPlaceholder];
}
-(void)setPlaceholderTextColor:(UIColor*)placeholderTextColor
{
_placeholderTextColor = placeholderTextColor;
self.IQ_PlaceholderLabel.textColor = placeholderTextColor;
}
-(UIEdgeInsets)placeholderInsets
{
return UIEdgeInsetsMake(self.textContainerInset.top, self.textContainerInset.left + self.textContainer.lineFragmentPadding, self.textContainerInset.bottom, self.textContainerInset.right + self.textContainer.lineFragmentPadding);
}
-(CGRect)placeholderExpectedFrame
{
UIEdgeInsets placeholderInsets = [self placeholderInsets];
CGFloat maxWidth = CGRectGetWidth(self.frame)-placeholderInsets.left-placeholderInsets.right;
CGSize expectedSize = [self.IQ_PlaceholderLabel sizeThatFits:CGSizeMake(maxWidth, CGRectGetHeight(self.frame)-placeholderInsets.top-placeholderInsets.bottom)];
return CGRectMake(placeholderInsets.left, placeholderInsets.top, maxWidth, expectedSize.height);
}
-(UILabel*)IQ_PlaceholderLabel
{
if (_IQ_PlaceholderLabel == nil)
{
_IQ_PlaceholderLabel = [[UILabel alloc] init];
_IQ_PlaceholderLabel.autoresizingMask = (UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight);
_IQ_PlaceholderLabel.lineBreakMode = NSLineBreakByWordWrapping;
_IQ_PlaceholderLabel.numberOfLines = 0;
_IQ_PlaceholderLabel.font = self.font;
_IQ_PlaceholderLabel.textAlignment = self.textAlignment;
_IQ_PlaceholderLabel.backgroundColor = [UIColor clearColor];
_IQ_PlaceholderLabel.isAccessibilityElement = NO;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
if (@available(iOS 13.0, *)) {
_IQ_PlaceholderLabel.textColor = [UIColor systemGrayColor];
} else
#endif
{
_IQ_PlaceholderLabel.textColor = [UIColor lightTextColor];
}
_IQ_PlaceholderLabel.alpha = 0;
[self addSubview:_IQ_PlaceholderLabel];
}
return _IQ_PlaceholderLabel;
}
//When any text changes on textField, the delegate getter is called. At this time we refresh the textView's placeholder
-(id<UITextViewDelegate>)delegate
{
[self refreshPlaceholder];
return [super delegate];
}
-(CGSize)intrinsicContentSize
{
if (self.hasText) {
return [super intrinsicContentSize];
}
UIEdgeInsets placeholderInsets = [self placeholderInsets];
CGSize newSize = [super intrinsicContentSize];
newSize.height = [self placeholderExpectedFrame].size.height + placeholderInsets.top + placeholderInsets.bottom;
return newSize;
}
@end
//
// IQBarButtonItem.h
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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/UIBarButtonItem.h>
@class NSInvocation;
/**
IQBarButtonItem used for IQToolbar.
*/
@interface IQBarButtonItem : UIBarButtonItem
/**
Boolean to know if it's a system item or custom item
*/
@property (nonatomic, readonly) BOOL isSystemItem;
/**
Additional target & action to do get callback action. Note that setting custom target & selector doesn't affect native functionality, this is just an additional target to get a callback.
@param target Target object.
@param action Target Selector.
*/
-(void)setTarget:(nullable id)target action:(nullable SEL)action;
/**
Customized Invocation to be called when button is pressed. invocation is internally created using setTarget:action: method.
*/
@property (nullable, strong, nonatomic) NSInvocation *invocation;
@end
//
// IQBarButtonItem.m
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 "IQBarButtonItem.h"
#import "IQKeyboardManagerConstantsInternal.h"
#import <UIKit/NSAttributedString.h>
@implementation IQBarButtonItem
+(void)initialize
{
[super initialize];
IQBarButtonItem *appearanceProxy = [self appearance];
NSArray <NSNumber*> *states = @[@(UIControlStateNormal),@(UIControlStateHighlighted),@(UIControlStateDisabled),@(UIControlStateSelected),@(UIControlStateApplication),@(UIControlStateReserved)];
for (NSNumber *state in states)
{
UIControlState controlState = [state unsignedIntegerValue];
[appearanceProxy setBackgroundImage:nil forState:controlState barMetrics:UIBarMetricsDefault];
[appearanceProxy setBackgroundImage:nil forState:controlState style:UIBarButtonItemStyleDone barMetrics:UIBarMetricsDefault];
[appearanceProxy setBackgroundImage:nil forState:controlState style:UIBarButtonItemStylePlain barMetrics:UIBarMetricsDefault];
[appearanceProxy setBackButtonBackgroundImage:nil forState:controlState barMetrics:UIBarMetricsDefault];
}
[appearanceProxy setTitlePositionAdjustment:UIOffsetZero forBarMetrics:UIBarMetricsDefault];
[appearanceProxy setBackgroundVerticalPositionAdjustment:0 forBarMetrics:UIBarMetricsDefault];
[appearanceProxy setBackButtonBackgroundVerticalPositionAdjustment:0 forBarMetrics:UIBarMetricsDefault];
}
-(void)setTintColor:(UIColor *)tintColor
{
[super setTintColor:tintColor];
//titleTextAttributes tweak is to overcome an issue comes with iOS11 where appearanceProxy set for NSForegroundColorAttributeName and bar button texts start appearing in appearance proxy color
NSMutableDictionary *textAttributes = [[self titleTextAttributesForState:UIControlStateNormal] mutableCopy]?:[NSMutableDictionary new];
textAttributes[NSForegroundColorAttributeName] = tintColor;
[self setTitleTextAttributes:textAttributes forState:UIControlStateNormal];
}
- (instancetype)initWithBarButtonSystemItem:(UIBarButtonSystemItem)systemItem target:(nullable id)target action:(nullable SEL)action
{
self = [super initWithBarButtonSystemItem:systemItem target:target action:action];
if (self)
{
_isSystemItem = YES;
}
return self;
}
-(void)setTarget:(nullable id)target action:(nullable SEL)action
{
NSInvocation *invocation = nil;
if (target && action)
{
invocation = [NSInvocation invocationWithMethodSignature:[target methodSignatureForSelector:action]];
invocation.target = target;
invocation.selector = action;
}
self.invocation = invocation;
}
-(void)dealloc
{
self.target = nil;
self.invocation = nil;
}
@end
//
// IQPreviousNextView.h
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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/UIView.h>
/**
If you need to enable previous/next toolbar button with some complex hierarchy where your textFields are not in same view, then make the top view as IQPreviousNextView.
*/
@interface IQPreviousNextView : UIView
@end
//
// IQPreviousNextView.m
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 "IQPreviousNextView.h"
@implementation IQPreviousNextView
@end
//
// IQTitleBarButtonItem.h
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 "IQKeyboardManagerConstants.h"
#import "IQBarButtonItem.h"
#import <Foundation/NSObjCRuntime.h>
/**
BarButtonItem with title text.
*/
@interface IQTitleBarButtonItem : IQBarButtonItem
/**
Font to be used in bar button. Default is (system font 12.0 bold).
*/
@property(nullable, nonatomic, strong) UIFont *titleFont;
/**
titleColor to be used for displaying button text when displaying title (disabled state).
*/
@property(nullable, nonatomic, strong) UIColor *titleColor;
/**
selectableTitleColor to be used for displaying button text when button is enabled.
*/
@property(nullable, nonatomic, strong) UIColor *selectableTitleColor;
/**
Initialize with frame and title.
@param title Title of barButtonItem.
*/
-(nonnull instancetype)initWithTitle:(nullable NSString *)title NS_DESIGNATED_INITIALIZER;
/**
Unavailable. Please use initWithFrame:title: method
*/
-(nonnull instancetype)init NS_UNAVAILABLE;
/**
Unavailable. Please use initWithFrame:title: method
*/
-(nonnull instancetype)initWithCoder:(nullable NSCoder *)aDecoder NS_UNAVAILABLE;
/**
Unavailable. Please use initWithFrame:title: method
*/
+ (nonnull instancetype)new NS_UNAVAILABLE;
@end
//
// IQTitleBarButtonItem.m
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 "IQTitleBarButtonItem.h"
#import "IQKeyboardManagerConstants.h"
#import "IQKeyboardManagerConstantsInternal.h"
#import <UIKit/UILabel.h>
#import <UIKit/UIButton.h>
@interface IQTitleBarButtonItem ()
@property(nullable, nonatomic, strong) UIView *titleView;
@property(nullable, nonatomic, strong) UIButton *titleButton;
@end
@implementation IQTitleBarButtonItem
-(nonnull instancetype)initWithTitle:(nullable NSString *)title
{
self = [super init];
if (self)
{
_titleView = [[UIView alloc] init];
_titleView.backgroundColor = [UIColor clearColor];
_titleButton = [UIButton buttonWithType:UIButtonTypeSystem];
_titleButton.enabled = NO;
_titleButton.titleLabel.numberOfLines = 3;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
if (@available(iOS 13.0, *)) {
[_titleButton setTitleColor:[UIColor systemBlueColor] forState:UIControlStateNormal];
} else
#endif
{
[_titleButton setTitleColor:[UIColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:1.0] forState:UIControlStateNormal];
}
[_titleButton setTitleColor:[UIColor lightGrayColor] forState:UIControlStateDisabled];
[_titleButton setBackgroundColor:[UIColor clearColor]];
[_titleButton.titleLabel setTextAlignment:NSTextAlignmentCenter];
[self setTitle:title];
[self setTitleFont:[UIFont systemFontOfSize:13.0]];
[_titleView addSubview:_titleButton];
if (@available(iOS 11.0, *))
{
CGFloat layoutDefaultLowPriority = UILayoutPriorityDefaultLow-1;
CGFloat layoutDefaultHighPriority = UILayoutPriorityDefaultHigh-1;
_titleView.translatesAutoresizingMaskIntoConstraints = NO;
[_titleView setContentHuggingPriority:layoutDefaultLowPriority forAxis:UILayoutConstraintAxisVertical];
[_titleView setContentHuggingPriority:layoutDefaultLowPriority forAxis:UILayoutConstraintAxisHorizontal];
[_titleView setContentCompressionResistancePriority:layoutDefaultHighPriority forAxis:UILayoutConstraintAxisVertical];
[_titleView setContentCompressionResistancePriority:layoutDefaultHighPriority forAxis:UILayoutConstraintAxisHorizontal];
_titleButton.translatesAutoresizingMaskIntoConstraints = NO;
[_titleButton setContentHuggingPriority:layoutDefaultLowPriority forAxis:UILayoutConstraintAxisVertical];
[_titleButton setContentHuggingPriority:layoutDefaultLowPriority forAxis:UILayoutConstraintAxisHorizontal];
[_titleButton setContentCompressionResistancePriority:layoutDefaultHighPriority forAxis:UILayoutConstraintAxisVertical];
[_titleButton setContentCompressionResistancePriority:layoutDefaultHighPriority forAxis:UILayoutConstraintAxisHorizontal];
NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:_titleButton attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:_titleView attribute:NSLayoutAttributeTop multiplier:1 constant:0];
NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:_titleButton attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:_titleView attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:_titleButton attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:_titleView attribute:NSLayoutAttributeLeading multiplier:1 constant:0];
NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:_titleButton attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:_titleView attribute:NSLayoutAttributeTrailing multiplier:1 constant:0];
[_titleView addConstraints:@[top,bottom,leading,trailing]];
}
else
{
_titleView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
_titleButton.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
}
self.customView = _titleView;
}
return self;
}
-(void)setTitleFont:(UIFont *)titleFont
{
_titleFont = titleFont;
if (titleFont)
{
_titleButton.titleLabel.font = titleFont;
}
else
{
_titleButton.titleLabel.font = [UIFont systemFontOfSize:13];
}
}
-(void)setTitle:(NSString *)title
{
[super setTitle:title];
[_titleButton setTitle:title forState:UIControlStateNormal];
}
-(void)setTitleColor:(UIColor*)titleColor
{
_titleColor = titleColor;
[_titleButton setTitleColor:_titleColor?:[UIColor lightGrayColor] forState:UIControlStateDisabled];
}
-(void)setSelectableTitleColor:(UIColor*)selectableTitleColor
{
_selectableTitleColor = selectableTitleColor;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
if (@available(iOS 13.0, *)) {
[_titleButton setTitleColor:_selectableTitleColor?:[UIColor systemBlueColor] forState:UIControlStateNormal];
} else
#endif
{
[_titleButton setTitleColor:_selectableTitleColor?:[UIColor colorWithRed:0.0 green:0.5 blue:1.0 alpha:1.0] forState:UIControlStateNormal];
}
}
-(void)setInvocation:(NSInvocation *)invocation
{
[super setInvocation:invocation];
if (invocation.target == nil || invocation.selector == NULL)
{
self.enabled = NO;
_titleButton.enabled = NO;
[_titleButton removeTarget:nil action:NULL forControlEvents:UIControlEventTouchUpInside];
}
else
{
self.enabled = YES;
_titleButton.enabled = YES;
[_titleButton addTarget:invocation.target action:invocation.selector forControlEvents:UIControlEventTouchUpInside];
}
}
-(void)dealloc
{
self.customView = nil;
[_titleButton removeTarget:nil action:NULL forControlEvents:UIControlEventTouchUpInside];
_titleView = nil;
_titleButton = nil;
}
@end
//
// IQToolbar.h
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 "IQTitleBarButtonItem.h"
#import <UIKit/UIToolbar.h>
#import <UIKit/UIDevice.h>
/**
IQToolbar for IQKeyboardManager.
*/
@interface IQToolbar : UIToolbar <UIInputViewAudioFeedback>
/**
Previous bar button of toolbar.
*/
@property(nonnull, nonatomic, strong) IQBarButtonItem *previousBarButton;
/**
Next bar button of toolbar.
*/
@property(nonnull, nonatomic, strong) IQBarButtonItem *nextBarButton;
/**
Title bar button of toolbar.
*/
@property(nonnull, nonatomic, strong, readonly) IQTitleBarButtonItem *titleBarButton;
/**
Done bar button of toolbar.
*/
@property(nonnull, nonatomic, strong) IQBarButtonItem *doneBarButton;
/**
Fixed space bar button of toolbar.
*/
@property(nonnull, nonatomic, strong) IQBarButtonItem *fixedSpaceBarButton;
@end
//
// IQToolbar.m
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 "IQToolbar.h"
#import "IQKeyboardManagerConstantsInternal.h"
#import "IQUIView+Hierarchy.h"
#import <UIKit/UIButton.h>
#import <UIKit/UIAccessibility.h>
#import <UIKit/UIViewController.h>
@implementation IQToolbar
@synthesize previousBarButton = _previousBarButton;
@synthesize nextBarButton = _nextBarButton;
@synthesize titleBarButton = _titleBarButton;
@synthesize doneBarButton = _doneBarButton;
@synthesize fixedSpaceBarButton = _fixedSpaceBarButton;
+(void)initialize
{
[super initialize];
IQToolbar *appearanceProxy = [self appearance];
NSArray <NSNumber*> *positions = @[@(UIBarPositionAny),@(UIBarPositionBottom),@(UIBarPositionTop),@(UIBarPositionTopAttached)];
for (NSNumber *position in positions)
{
UIToolbarPosition toolbarPosition = [position unsignedIntegerValue];
[appearanceProxy setBackgroundImage:nil forToolbarPosition:toolbarPosition barMetrics:UIBarMetricsDefault];
[appearanceProxy setShadowImage:nil forToolbarPosition:toolbarPosition];
}
}
-(void)initialize
{
[self sizeToFit];
self.autoresizingMask = UIViewAutoresizingFlexibleWidth;// | UIViewAutoresizingFlexibleHeight;
self.translucent = YES;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[self initialize];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self)
{
[self initialize];
}
return self;
}
-(void)dealloc
{
self.items = nil;
}
-(IQBarButtonItem *)previousBarButton
{
if (_previousBarButton == nil)
{
_previousBarButton = [[IQBarButtonItem alloc] initWithImage:nil style:UIBarButtonItemStylePlain target:nil action:nil];
}
return _previousBarButton;
}
-(IQBarButtonItem *)nextBarButton
{
if (_nextBarButton == nil)
{
_nextBarButton = [[IQBarButtonItem alloc] initWithImage:nil style:UIBarButtonItemStylePlain target:nil action:nil];
}
return _nextBarButton;
}
-(IQTitleBarButtonItem *)titleBarButton
{
if (_titleBarButton == nil)
{
_titleBarButton = [[IQTitleBarButtonItem alloc] initWithTitle:nil];
_titleBarButton.accessibilityLabel = @"Title";
_titleBarButton.accessibilityIdentifier = _titleBarButton.accessibilityLabel;
}
return _titleBarButton;
}
-(IQBarButtonItem *)doneBarButton
{
if (_doneBarButton == nil)
{
_doneBarButton = [[IQBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:nil action:nil];
}
return _doneBarButton;
}
-(IQBarButtonItem *)fixedSpaceBarButton
{
if (_fixedSpaceBarButton == nil)
{
_fixedSpaceBarButton = [[IQBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
if (@available(iOS 10.0, *))
{
[_fixedSpaceBarButton setWidth:6];
}
else
{
[_fixedSpaceBarButton setWidth:20];
}
}
return _fixedSpaceBarButton;
}
-(CGSize)sizeThatFits:(CGSize)size
{
CGSize sizeThatFit = [super sizeThatFits:size];
sizeThatFit.height = 44;
return sizeThatFit;
}
-(void)setTintColor:(UIColor *)tintColor
{
[super setTintColor:tintColor];
for (UIBarButtonItem *item in self.items)
{
[item setTintColor:tintColor];
}
}
-(void)layoutSubviews
{
[super layoutSubviews];
if (@available(iOS 11.0, *)) {}
else {
CGRect leftRect = CGRectNull;
CGRect rightRect = CGRectNull;
BOOL isTitleBarButtonFound = NO;
NSArray<UIView*> *subviews = [self.subviews sortedArrayUsingComparator:^NSComparisonResult(UIView *view1, UIView *view2) {
CGFloat x1 = CGRectGetMinX(view1.frame);
CGFloat y1 = CGRectGetMinY(view1.frame);
CGFloat x2 = CGRectGetMinX(view2.frame);
CGFloat y2 = CGRectGetMinY(view2.frame);
if (x1 < x2) return NSOrderedAscending;
else if (x1 > x2) return NSOrderedDescending;
//Else both y are same so checking for x positions
else if (y1 < y2) return NSOrderedAscending;
else if (y1 > y2) return NSOrderedDescending;
else return NSOrderedSame;
}];
for (UIView *barButtonItemView in subviews)
{
if (isTitleBarButtonFound == YES)
{
rightRect = barButtonItemView.frame;
break;
}
else if (barButtonItemView == self.titleBarButton.customView)
{
isTitleBarButtonFound = YES;
}
//If it's UIToolbarButton or UIToolbarTextButton (which actually UIBarButtonItem)
else if ([barButtonItemView isKindOfClass:[UIControl class]])
{
leftRect = barButtonItemView.frame;
}
}
CGFloat titleMargin = 16;
CGFloat maxWidth = CGRectGetWidth(self.frame) - titleMargin*2 - (CGRectIsNull(leftRect)?0:CGRectGetMaxX(leftRect)) - (CGRectIsNull(rightRect)?0:CGRectGetWidth(self.frame)-CGRectGetMinX(rightRect));
CGFloat maxHeight = self.frame.size.height;
CGSize sizeThatFits = [self.titleBarButton.customView sizeThatFits:CGSizeMake(maxWidth, maxHeight)];
CGRect titleRect = CGRectZero;
CGFloat x = titleMargin;
if (sizeThatFits.width > 0 && sizeThatFits.height > 0)
{
CGFloat width = MIN(sizeThatFits.width, maxWidth);
CGFloat height = MIN(sizeThatFits.height, maxHeight);
if (CGRectIsNull(leftRect) == false)
{
x = titleMargin + CGRectGetMaxX(leftRect) + ((maxWidth - width)/2);
}
CGFloat y = (maxHeight - height)/2;
titleRect = CGRectMake(x, y, width, height);
}
else
{
if (CGRectIsNull(leftRect) == false)
{
x = titleMargin + CGRectGetMaxX(leftRect);
}
CGFloat width = CGRectGetWidth(self.frame) - titleMargin*2 - (CGRectIsNull(leftRect)?0:CGRectGetMaxX(leftRect)) - (CGRectIsNull(rightRect)?0:CGRectGetWidth(self.frame)-CGRectGetMinX(rightRect));
titleRect = CGRectMake(x, 0, width, maxHeight);
}
self.titleBarButton.customView.frame = titleRect;
}
}
#pragma mark - UIInputViewAudioFeedback delegate
- (BOOL) enableInputClicksWhenVisible
{
return YES;
}
@end
//
// IQUIView+IQKeyboardToolbar.h
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 "IQToolbar.h"
#import <UIKit/UIView.h>
#import <UIKit/UIImage.h>
@interface IQBarButtonItemConfiguration : NSObject
-(nonnull instancetype)initWithBarButtonSystemItem:(UIBarButtonSystemItem)barButtonSystemItem action:(nullable SEL)action;
-(nonnull instancetype)initWithImage:(nonnull UIImage*)image action:(nullable SEL)action;
-(nonnull instancetype)initWithTitle:(nonnull NSString*)title action:(nullable SEL)action;
@property (readonly, nonatomic) UIBarButtonSystemItem barButtonSystemItem; //System Item to be used to instantiate bar button
@property (readonly, nonatomic, nullable) UIImage *image; //Image to show on bar button item if it's not a system item.
@property (readonly, nonatomic, nullable) NSString *title; //Title to show on bar button item if it's not a system item.
@property (readonly, nonatomic, nullable) SEL action; //action for bar button item. Usually 'doneAction:(IQBarButtonItem*)item'.
@end
@interface UIImage (IQKeyboardToolbarNextPreviousImage)
+(nullable UIImage*)keyboardLeftImage;
+(nullable UIImage*)keyboardRightImage;
+(nullable UIImage*)keyboardUpImage;
+(nullable UIImage*)keyboardDownImage;
+(nullable UIImage*)keyboardPreviousImage;
+(nullable UIImage*)keyboardNextImage;
@end
/**
UIView category methods to add IQToolbar on UIKeyboard.
*/
@interface UIView (IQToolbarAddition)
///-------------------------
/// @name Toolbar Title
///-------------------------
/**
IQToolbar references for better customization control.
*/
@property (readonly, nonatomic, nonnull) IQToolbar *keyboardToolbar;
/**
If `shouldHideToolbarPlaceholder` is YES, then title will not be added to the toolbar. Default to NO.
*/
@property (assign, nonatomic) BOOL shouldHideToolbarPlaceholder;
/**
`toolbarPlaceholder` to override default `placeholder` text when drawing text on toolbar.
*/
@property (nullable, strong, nonatomic) NSString* toolbarPlaceholder;
/**
`drawingToolbarPlaceholder` will be actual text used to draw on toolbar. This would either `placeholder` or `toolbarPlaceholder`.
*/
@property (nullable, strong, nonatomic, readonly) NSString* drawingToolbarPlaceholder;
///-------------
/// MARK: Common
///-------------
- (void)addKeyboardToolbarWithTarget:(nullable id)target titleText:(nullable NSString*)titleText rightBarButtonConfiguration:(nullable IQBarButtonItemConfiguration*)rightBarButtonConfiguration previousBarButtonConfiguration:(nullable IQBarButtonItemConfiguration*)previousBarButtonConfiguration nextBarButtonConfiguration:(nullable IQBarButtonItemConfiguration*)nextBarButtonConfiguration;
///------------
/// @name Done
///------------
- (void)addDoneOnKeyboardWithTarget:(nullable id)target action:(nullable SEL)action;
- (void)addDoneOnKeyboardWithTarget:(nullable id)target action:(nullable SEL)action shouldShowPlaceholder:(BOOL)shouldShowPlaceholder;
- (void)addDoneOnKeyboardWithTarget:(nullable id)target action:(nullable SEL)action titleText:(nullable NSString*)titleText;
///------------
/// @name Right
///------------
- (void)addRightButtonOnKeyboardWithText:(nullable NSString*)text target:(nullable id)target action:(nullable SEL)action;
- (void)addRightButtonOnKeyboardWithText:(nullable NSString*)text target:(nullable id)target action:(nullable SEL)action shouldShowPlaceholder:(BOOL)shouldShowPlaceholder;
- (void)addRightButtonOnKeyboardWithText:(nullable NSString*)text target:(nullable id)target action:(nullable SEL)action titleText:(nullable NSString*)titleText;
- (void)addRightButtonOnKeyboardWithImage:(nullable UIImage*)image target:(nullable id)target action:(nullable SEL)action;
- (void)addRightButtonOnKeyboardWithImage:(nullable UIImage*)image target:(nullable id)target action:(nullable SEL)action shouldShowPlaceholder:(BOOL)shouldShowPlaceholder;
- (void)addRightButtonOnKeyboardWithImage:(nullable UIImage*)image target:(nullable id)target action:(nullable SEL)action titleText:(nullable NSString*)titleText;
///------------------
/// @name Cancel/Done
///------------------
- (void)addCancelDoneOnKeyboardWithTarget:(nullable id)target cancelAction:(nullable SEL)cancelAction doneAction:(nullable SEL)doneAction;
- (void)addCancelDoneOnKeyboardWithTarget:(nullable id)target cancelAction:(nullable SEL)cancelAction doneAction:(nullable SEL)doneAction shouldShowPlaceholder:(BOOL)shouldShowPlaceholder;
- (void)addCancelDoneOnKeyboardWithTarget:(nullable id)target cancelAction:(nullable SEL)cancelAction doneAction:(nullable SEL)doneAction titleText:(nullable NSString*)titleText;
///-----------------
/// @name Right/Left
///-----------------
- (void)addLeftRightOnKeyboardWithTarget:(nullable id)target leftButtonTitle:(nullable NSString*)leftButtonTitle rightButtonTitle:(nullable NSString*)rightButtonTitle leftButtonAction:(nullable SEL)leftButtonAction rightButtonAction:(nullable SEL)rightButtonAction;
- (void)addLeftRightOnKeyboardWithTarget:(nullable id)target leftButtonTitle:(nullable NSString*)leftButtonTitle rightButtonTitle:(nullable NSString*)rightButtonTitle leftButtonAction:(nullable SEL)leftButtonAction rightButtonAction:(nullable SEL)rightButtonAction shouldShowPlaceholder:(BOOL)shouldShowPlaceholder;
- (void)addLeftRightOnKeyboardWithTarget:(nullable id)target leftButtonTitle:(nullable NSString*)leftButtonTitle rightButtonTitle:(nullable NSString*)rightButtonTitle leftButtonAction:(nullable SEL)leftButtonAction rightButtonAction:(nullable SEL)rightButtonAction titleText:(nullable NSString*)titleText;
///-------------------------
/// @name Previous/Next/Done
///-------------------------
- (void)addPreviousNextDoneOnKeyboardWithTarget:(nullable id)target previousAction:(nullable SEL)previousAction nextAction:(nullable SEL)nextAction doneAction:(nullable SEL)doneAction;
- (void)addPreviousNextDoneOnKeyboardWithTarget:(nullable id)target previousAction:(nullable SEL)previousAction nextAction:(nullable SEL)nextAction doneAction:(nullable SEL)doneAction shouldShowPlaceholder:(BOOL)shouldShowPlaceholder;
- (void)addPreviousNextDoneOnKeyboardWithTarget:(nullable id)target previousAction:(nullable SEL)previousAction nextAction:(nullable SEL)nextAction doneAction:(nullable SEL)doneAction titleText:(nullable NSString*)titleText;
///--------------------------
/// @name Previous/Next/Right
///--------------------------
- (void)addPreviousNextRightOnKeyboardWithTarget:(nullable id)target rightButtonTitle:(nullable NSString*)rightButtonTitle previousAction:(nullable SEL)previousAction nextAction:(nullable SEL)nextAction rightButtonAction:(nullable SEL)rightButtonAction;
- (void)addPreviousNextRightOnKeyboardWithTarget:(nullable id)target rightButtonTitle:(nullable NSString*)rightButtonTitle previousAction:(nullable SEL)previousAction nextAction:(nullable SEL)nextAction rightButtonAction:(nullable SEL)rightButtonAction shouldShowPlaceholder:(BOOL)shouldShowPlaceholder;
- (void)addPreviousNextRightOnKeyboardWithTarget:(nullable id)target rightButtonTitle:(nullable NSString*)rightButtonTitle previousAction:(nullable SEL)previousAction nextAction:(nullable SEL)nextAction rightButtonAction:(nullable SEL)rightButtonAction titleText:(nullable NSString*)titleText;
- (void)addPreviousNextRightOnKeyboardWithTarget:(nullable id)target rightButtonImage:(nullable UIImage*)rightButtonImage previousAction:(nullable SEL)previousAction nextAction:(nullable SEL)nextAction rightButtonAction:(nullable SEL)rightButtonAction;
- (void)addPreviousNextRightOnKeyboardWithTarget:(nullable id)target rightButtonImage:(nullable UIImage*)rightButtonImage previousAction:(nullable SEL)previousAction nextAction:(nullable SEL)nextAction rightButtonAction:(nullable SEL)rightButtonAction shouldShowPlaceholder:(BOOL)shouldShowPlaceholder;
- (void)addPreviousNextRightOnKeyboardWithTarget:(nullable id)target rightButtonImage:(nullable UIImage*)rightButtonImage previousAction:(nullable SEL)previousAction nextAction:(nullable SEL)nextAction rightButtonAction:(nullable SEL)rightButtonAction titleText:(nullable NSString*)titleText;
@end
//
// IQUIView+IQKeyboardToolbar.m
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-16 Iftekhar Qurashi.
//
// 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 "IQUIView+IQKeyboardToolbar.h"
#import "IQKeyboardManagerConstantsInternal.h"
#import "IQKeyboardManager.h"
#import <objc/runtime.h>
#import <Foundation/NSData.h>
#import <UIKit/UIImage.h>
#import <UIKit/UILabel.h>
#import <UIKit/UIScreen.h>
#import <UIKit/UIAccessibility.h>
@implementation IQBarButtonItemConfiguration
-(instancetype)initWithBarButtonSystemItem:(UIBarButtonSystemItem)barButtonSystemItem action:(SEL)action
{
self = [super init];
if (self) {
_barButtonSystemItem = barButtonSystemItem;
_action = action;
}
return self;
}
-(instancetype)initWithImage:(UIImage *)image action:(SEL)action
{
self = [super init];
if (self) {
_image = image;
_action = action;
}
return self;
}
-(instancetype)initWithTitle:(NSString *)title action:(SEL)action
{
self = [super init];
if (self) {
_title = title;
_action = action;
}
return self;
}
@end
@implementation UIImage (IQKeyboardToolbarNextPreviousImage)
+(UIImage*)keyboardLeftImage
{
static UIImage *keyboardLeftImage = nil;
if (keyboardLeftImage == nil)
{
NSString *base64Data = @"iVBORw0KGgoAAAANSUhEUgAAACQAAAA/CAYAAACIEWrAAAAAAXNSR0IArs4c6QAABtFJREFUaAXFmV1oHFUUx++d3SSbj/0k6Uc2u7Ob7QeVSqBSP7AUm1JpS0tb+6nFYhELxfahDxVU9KmgD0UU7ENRLLRQodRqNbVJY5IGXwRBEPHBh2x2ZpPQaDC7W2qSzc5c/3ebDTN3d5Pd7Gw6L3PPOcM5vzn33I+5Q8gTvJqbm52RYPAdIEg5DFuusdz3dq/X7XA6ewiVTvrcnvBkMvE9GNgTAQoGg16pztFLKX02mwhKOrwe99rJZPL2sgO1tbX5aiWpDzDPGHuFEvq01+2ZpEZltdutra3NjpranxC0Q4zFCLsVVZRjdtFQLTmycuUKZq/pA8zGvBiM3IiqynHoM8sCFGoJrSIO1o9u2SDCIDPXAXMCeo3bqg4UCARaJYkMEELXiTCEkauAOQm9nrPNj/+cwso7aiZQS6VBdFMeDDLz1ZAaM8Hw2FXLUHj1apnaawYIpWHxJRkjl5GZ09Az0VYVIFmWw6iXAWRGFgMynV2KxpWzhWD4s5Z3GeaZNXZGeTflwzDyGWDOFIPhQJZmqN3vX0clG7qJtHLnpktnFwFz3qQrIFgGJK+WN+D1+jGaVolxGNM/jsbVd0V9IdkSoEggsJFJlE96K8Qgus4uDMfVD0R9MbniGgr7/R1YsXkB58FgEH04HFdKhuGQFWUIo2kTZaQXQ9snvjGG9nsY2h+J+sXkJQO1BwKbMYv0YNX2ikF0ws4Pq8pFUV+KvCSgkD/0PCaMbnSTWwyCzJwDzKeivlS5bCBsOV/EsL6LAE5jEMYvSs4C5pJRX267LKBwILAVw/oOgjQZAz1mYaejinrZqF9Ku+QdY0SWOzkMaqbRGAgwOjJzKqqqXxj1S22jDBa/wsHgDqxNtwFTb3w6C0PYyWFVvWrUV9JetMsibfIuRuktkDuMgQCjYRdzYnhEvW7UV9peEKg9GNyDOeYmYOpMgRjLYD9zHDA3THoLhKIzdSgQ2k+p9A1imGEImUXNHEM3WQ7D36dghlAzhyRKeFfU8IcMV1rTtSOxePy2QWdpMw8oEggdwxp0DVFE2wy66SBg+LCv2mUa9mFZfhORrmA0mWCwz5zWdW0/uolPiFW95msIMGckQr8EjAkSo2mKMH0vMtNTVZI559lMtAdC5zCSPhEDAuaRppG9yqg6INqqJVNk5m1k5nMxAGAYYLYro8qywXAGiWYyvYSxUREIXUdtdnIKelM9ic9ZLWeXDnxdRmppdnMeEAMgUTex0XoN+lnRVg05C8Qd828pW5FvKUwD3w0pylE8lq4GhNHnPBBX+v3+tjpbTT+lZK3xId5GprqQqUNozog2K2UTEHfMDwdqJBtOKsh6MRAmxru6Ql+Jkdi0aLNKzgPijvnxia2e9WFhfUoMhC1qb1rP7BsZGZkSbVbI8xOj0Vnsn9gDMjO9DcH/MOp5G925o1aydeFko0G0WSEXBOKOh8bH/57OpDuxbPwuBsKM0Omw195taWkxbWXF55YiFwXizsbGxibSWqYTFf2b6ByZ2uqsb+jmZ82irRK5YA2JDkOekEdykXuA2CzaMP5+YanUzujkZDLfVr6mJCDu1ufzubxOZzeq6AUxDGrtVz1FXo4lYgnRVq5cMhB3zLvH1dD4I2poS14gdOuMru3A6Ps3z1aGYsEaEv1MTEw8fDQzvRP6QdGG4bep1mbv52fRebYyFGUBcb/j4+OPpmbTuzFz4yzIfCHdHQ6cK/IzabOldKlsIO4ao++/tK7tQe3cE0OhOzcSh+N+9mxaNJYgl1VDBfzVtcsyvtnobtGG+euvWV3rjMfjY6JtIXlJGTI4nMH/iQPI1A8GXbaJN13Pz6j5gi3aFpIrBeK+01E1dhAL77d5gShd47DZB/mZdZ6tiKLSLjO6tUeCoes4qjlsVPI2uk/RCNumKMqwaBNlKzKU85nBr4JXkamvc4rcHW8t87NrvjPN6YrdrQTiMTTU1OtY+67lBaQk+9+Dn2Xn2QwKq4G4a21IVd5Apq4Y4jxuUuonNvv97Jl2nnHukSJ6K9Q0EpQvYwZ/S3SGmhrPMH27qqp/ijbTV6porFTGT90u/NxdgXnKtEtATTXZKD3scTb1JFKpcWOcqgLxQIC643F7fNi6PGcMjHYjZvUjrkZPb/Jh8kHOVnUgHiiRTHQjUy5kyrx1obSBSuSI1+Xqm0ylsjP6sgBxKGTqHn6D1yNTpq0LslSPXxNH3c6mAXTfqJUTI4+76IXT3AvY5L1f4MFUhrBdy5ahHAAy1e91uzD46Es53dydYv7qWnYgHhxQgx6XexZQ2+dgZojGDuCf2p0nAsQhEqnkzz63awpz0hacve+LjqjZA7H/AWSbJ/TPf3CuAAAAAElFTkSuQmCC";
NSData *data = [[NSData alloc] initWithBase64EncodedString:base64Data options:NSDataBase64DecodingIgnoreUnknownCharacters];
keyboardLeftImage = [UIImage imageWithData:data scale:3];
//Support for RTL languages like Arabic, Persia etc... (Bug ID: #448)
keyboardLeftImage = [keyboardLeftImage imageFlippedForRightToLeftLayoutDirection];
}
return keyboardLeftImage;
}
+(UIImage*)keyboardRightImage
{
static UIImage *keyboardRightImage = nil;
if (keyboardRightImage == nil)
{
NSString *base64Data = @"iVBORw0KGgoAAAANSUhEUgAAACQAAAA/CAYAAACIEWrAAAAAAXNSR0IArs4c6QAABu5JREFUaAXFmXtsFEUcx2f3rj0Kvd29k9LHtXfXqyjGV2J8EF/hIQgp4VnahPgIxviH0ZgYNSbGmBg1McaYGGOM+o8k+EINMQjIo6UoBAVEEBGQXnvbS1ttw91epUDbu/E7lb3bm22Pu97uOQnszO+3ne/nvjM7sw9CMsXRFAi83jhnTnUmVPqacEXSGfIHPhMEoYUSejpJyKJIJNJfehxCRIiWwZktDIYBCESY56BCZ319ve9/AQr5/c8CY7VRXBDIXJfo6Kyrq2swxktRZ0NWFgoEPocza3lBDF9P6rKwsGegp4fP2dVmQzYWjkTaCCVf8iKADIou0un3+0N8zq42A2JlvEvt2QBHPv2vmfkfFvrLiNAZqq+fm4naV9OBmEISTj0MpzaZ5AShXhAd+xrr6q435SwO6Je9sVsRc+ojDNdjxiCrw8GBcUoXq6p6is9Z1TY6pPeZglOPQ/1DPaAfAVnjFMQODN/Neszqo2OqDmNa/DuPJM/G+nSn8RxYOgux9Upl5a748PBfxpwV9SmBWOexhLbdIyserEvzs8QEYSYRxFZJUfZommbpip4TaAJKi+/0SnIlEYS7jVBwqQJutXkkqT2WSPQZc8XUrwo0AZXQdntkaQYg7jWKYU4hJrZJlXKnNqxFjbnp1vMCmoDStL2KJDsBdT8n5hJFoRXAP8Q0TeVyBTfzBmI9xxNah1eRU9j7FnJKLrTbZLf7QDyRiHC5gpoFAbGe4cJ+TPRRTPTFRiU4V45/rV5FOYRzuo25QuoFA7HOsST8qCjyBcyhpUYxAJVRSloVSToMp7qMuXzr0wJincc17SCc0uDUMqMYg8JEb/W65aNYNs4Zc/nUpw3EOodTh+DUEFb15QDBKpAuTiJi8ZSl4wA/m47mUSkKiPUPwcNeWR6ghDRzUA60W+DUSTh1Og+WiVOKBmK9YBIfVRQlCqdW8FC4J16nyPJpgOe1IVsCxKAgeAxOReDUyiwoTCik13olz9lYIn6SnZurWAbERODUcY+idMGpVYBK30mwOm5d1sCpMMBPlAzoCtRvsiSdEdmDAweF/Go4pcKpX6eCstQhXQRr0O9w6hTWqTWIpTXYUMKpVXCqD079op9vPKZPNgatqGP4/pAl9wlRENnTTFqHQaG9wiN5/oZTR3it9Il8woo2nDrjUeRjcGod+nPqfTIoYDVjnToPp37W4+xoKxATgFN/ym7lCKZ4C6xJQ7EcqJZjsx7BOQdZmxXbgZhIPBE/h9uTn1BdD4gyFssUYQmgkoDaz2IlAWJCEAxLlcpBDFULoMpZLFOERdgXBWxF+4z7TyZvYy1YH1wginQvoNLrlC6XIvT5rDHVEzYeRYdINhrXJ10LK7yapPSbUgI58AC6CQAbdAj9SCntpmOjC9X+/kipgJxN/uBmALTqEOkjpecujY8t6uvv72WxUgBNvO6B1iSve8jxkdHLSwYGBgZ1QLuByuHMFoit1AUzR3psNJl8ADDnMzF7HXLhveXXuB9qNgqyOubMkXFCl0aj0Rifs8WhIAnOcPjJVsA8yAsC5xAZTixTYzHNnLPBIbwsrcA68y0u7Qd4QThzIDFyYflQLDbM5/S2pQ5VV1fPcjkc27BLLdAF9CMej/YPXxxpHhoa+kePTXa0DKiqqqpylqtiO0TuMwvRDlzaKwYHB0fMueyIJUBer1eSKmbuwJzJekPCpODM7tFUclVfX9/FbOnJW0UDhTwembil79H9XWYJujOlCmuiJHrJnJs8UhQQXhd7MF92YYe+ne8eE3hbWI20IH6Zz+Vqm3bcXCcbcz6f7xo8M7Nd2wSDgdoKGHaXWBAM639aDtXU1FS5nGV78Pe3sE6MBc58BRi2gY4Z4/nWCwZin6/EctdeCNxoEqHkC8A8hPi4KZdnoCCgQCBQi/nSjnkzj+8fzmwGzKOIJ/lcIe285xD7XOUgwj48QZhgUpR8AphHioVh4HkBsc9U7HMV3LnO9Gsp/bhb7dmIOF71FV+uOmSNtbUBwVnWgb2pkZejNPVBWFWfRBx3oNaUnEDssxTuxdvhTMAkl6LvhXvVp03xIgNTDhnmzLXss9RkMHg+f6erN2I5DPstkzrEPkOJoqMdw1TH/+AUpW91q5EX+LhVbRNQoDZwA54t2aVdYxahbwDmJXPcukgWUFNDw01UxHZAyBxeArv2q7i0X+HjVrfTQI0+3634wrMHMLPNIvRlwLxmjlsfmQDCCnwb3iTtxpzx8hK4tF/Epf0mH7er7Qw1NNyBzndh11Z4kVSKPtfdq77Nx+1sO7GiVeCNpBN3e9mFpp4BzLvZQftbExhNfv89mD87IOfGJollhjwV7o28b798DoWgLzgfD3bnAfdEjtNsT/0LGvgrBSkuN9gAAAAASUVORK5CYII=";
NSData *data = [[NSData alloc] initWithBase64EncodedString:base64Data options:NSDataBase64DecodingIgnoreUnknownCharacters];
keyboardRightImage = [UIImage imageWithData:data scale:3];
//Support for RTL languages like Arabic, Persia etc... (Bug ID: #448)
keyboardRightImage = [keyboardRightImage imageFlippedForRightToLeftLayoutDirection];
}
return keyboardRightImage;
}
+(UIImage*)keyboardUpImage
{
static UIImage *keyboardUpImage = nil;
if (keyboardUpImage == nil)
{
NSString *base64Data = @"iVBORw0KGgoAAAANSUhEUgAAAD8AAAAkCAYAAAA+TuKHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpMwidZAAAGmklEQVRoBd1ZWWwbRRie2bVz27s2adPGxzqxqAQCIRA3CDVJGxpKaEtRoSAVISQQggdeQIIHeIAHkOCBFyQeKlARhaYHvUJa0ksVoIgKUKFqKWqdeG2nR1Lsdeo0h73D54iku7NO6ySOk3alyPN//+zM/81/7MyEkDl66j2eJXWK8vocTT82rTgXk/t8vqBNEI9QSp9zOeVkPJnomgs7ik5eUZQ6OxGOEEq9WcKUksdlWbqU0LRfi70ARSXv8Xi8dkE8CsJ+I1FK6BNYgCgW4A8jPtvtopFHqNeWCLbDIF6fkxQjK91O1z9IgRM59bMAFoV8YEFgka1EyBJfMhkH5L9ACFstS9IpRMDJyfoVEp918sGamoVCme0QyN3GG87wAKcTOBYA4hrJKf+VSCb+nsBnqYHVnr2ntra2mpWWH0BVu52fhRH2XSZDmsA/xensokC21Pv9T3J4wcWrq17gob1er7tEhMcJuYsfGoS3hdTweuBpxaM0iCJph8fLuX7DJMPWnI2GOzi8YOKseD4gB+RSQezMRRx5vRPEn88Sz7IIx8KHgT3FCBniWJUyke6o8/uXc3jBxIKTd7vdTsFJfkSo38NbCY/vPRsOPwt81KgLqeoBXc+sBjZsxLF4ZfgM7goqSqMRL1S7oOSrq6sdLodjH0rYfbyByPEOePwZ4CO8Liv3RCL70Wctr8+mA2NkT53P91iu92aCFYx8TU1NpbOi8gfs2R7iDYLxnXqYPg3c5Fm+Xygcbs/omXXATZGBBagQqNAe9Psf4d+ZiVwQ8qjqFVVl5dmi9ShvDEL90IieXtVDevic5ruOyYiAXYiA9YSxsZow0YnSKkKFjoAn8OAENsPGjKs9qnp5iSDuBXFLXsLjR4fSIy29vb2DU7UThW4d8n0zxjXtRVAYNaJnlocikWNTHZPvP1PPl2LLujM3cfbzwJXUyukQzxrZraptRCcbEDm60Wh4S0IE7McByVJQjf3yac+EfEm9ouxAcWu2TsS6koOplr6+vstWXf5IKBrejBR4ybIAlLpE1JE6j8eyh8h/dEKmS95e7w9sy57G+MkQ6sdYMrmiv79/gNdNR0YEbGKUvIIFQMRffRBtbkG0HQj6fHdcRafWmg55Gzy+BR5vtUzF2O96kjSH4nHNopsB0B0Ob6SEvcYvAPYS1UwQDyqLFcu5IZ/pTMUkjxfEoD/wLVY9+z02PXDL8RE9s0y9qMZNigIJcU37TZblfj7aUAMqURLXuqqq9sQHBi5NZbqpkBfh8a9BPLtDMz3wyImh9GhTLBab0uSmQfIQcNQ95pJkDVG3wtgdC1KFA+HaSodjdzKZ/Neou1Y7X/JC0K98BeIvWAdjp+jwUKN6/nyfVVd4JK4lunDrkwJhc6Gl1GGjwhqnLO3UNC2Rz8z5kKfw+EYQf5EfEKF+Wh+kDd0XYxd43WzKiIBfEAEjiIAm0zyUSFiU1XJF+feJy5evW3euR57C41+A+MumSbICY2dGmd6gnlPPWXRFABABP7llCXsA2mCcDjVAJoK4qryycsfAwEDSqOPb1yQPj38O4q/yL4F4aCiTXhqNRmMWXREBFMGjslOywUbToQeyyy4IrVVO53bUgEk/uZOSr/MHPsOd0hs8F4R6mI2ONKi9vRFeNxdyIqkddknOMhA2nyuy+wAqtEol8rbEYCLnZisneXj8UxB/00KGkUiGsqU90WiPRTeHACLgoNsp4eBDHzaagRS4RbCzle6ysq3xVIq/LiMW8ti5fYRVfMs4yFibsdgI05eqqhqy6OYBEE9qnSiCLhRB7tRHFzDR1oIasBU1wHTAMpHHjcmHIP4OzwXf8XMkk24IR6NneN18klEE97mc0gJwuN9oF+SFNlF8vNJR1YYacGVcN0Eet6XvY6Pw3rhi/Bc5fiEzShp7eiOnx7H5/IsI6EAELEIE3Gu0EymwyCbQZocktWEfMHa3MEa+zqe8KwjCB8bO/7f70kxvVGPqyRy6eQshAtpdsuTDN/9us5F0MQ4zTS5BaIsPDQ3jO+5/G+fjj82dIDF2CZeKjd3R6J8W3Y0BYFca+JJQssFqLuvSUqlmESHSiZywGzsgx+OZNFnWE4scN+I3WJshAnYjAm5FBNxptp16y+y2hICLEtOVMXJcI0xvDveGi/ofU7NxBZN0XIpuIIy0mUZkZNNZVf1kDAt6lZagEhjGnxbweh8wdbw5hOwdxHbwY/j9BpTM9xi4MGzFvZhpk3Bz8J5gkb19ym7cJr5w/wEmUjzJqoNVhwAAAABJRU5ErkJggg==";
NSData *data = [[NSData alloc] initWithBase64EncodedString:base64Data options:NSDataBase64DecodingIgnoreUnknownCharacters];
keyboardUpImage = [UIImage imageWithData:data scale:3];
//Support for RTL languages like Arabic, Persia etc... (Bug ID: #448)
keyboardUpImage = [keyboardUpImage imageFlippedForRightToLeftLayoutDirection];
}
return keyboardUpImage;
}
+(UIImage*)keyboardDownImage
{
static UIImage *keyboardDownImage = nil;
if (keyboardDownImage == nil)
{
NSString *base64Data = @"iVBORw0KGgoAAAANSUhEUgAAAD8AAAAkCAYAAAA+TuKHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpMwidZAAAGp0lEQVRoBd1ZCWhcRRiemff25WrydmOtuXbfZlMo4lEpKkppm6TpZUovC4UqKlQoUhURqQcUBcWDIkhVUCuI9SpJa+2h0VZjUawUEUUUirLNXqmxSnc32WaT7O4bv0nd5R1bc+2maR8s7z9m5v+/+f/5Z94sIf89jW73Yp/bfUuWvwLfDp/H8zhwObLYmCCaPJ6FjLJPCWNHNU1bkFVeQW/Zp2l7KWUvNmlaB3DJAhvz1ntvI5R1EUpnUUKdEifHGuvr519BwKUmj/cDYNtwARNd5/NoH4GWKIhzlFKXCSzn/xCut/jD4V9N8suPYYj4ewC+2e46f55Rwp/geExKSmdzJn2l1WrXmuSXF8MQ8XfyAeeEn9KTyV3MHwq9RTh50IqLEjJHUkh3Y13dPKvuMuApIr6bUHKP1VeE+Y8MIa09Z8/+JQlltD/+Q7VaFcW6X2VsjFmbRRnbUFFZeai/v/+cUTeDaYqIv4GlfL/NR879I3qmORwOnxG6UfCCiMbjJ51VagKdlgs+91BaKVO6oVJVD8bj8WhOPkMJn1t7jTL6gNU9pHpgKJ1q7u3tjWR1OfBCEOuPf+9Sq4YwAW3ZBqNvSqsYpeuc5WUHYolE3KSbQYzP430FwB+yuoSCFtKHaXP4z3DIqDOBFwpkwHfVThXLgrYaG6IGOAmT1pZVVHw8MDDQb9TNBLrJre0E8EdtvnAeSRPeHOwN9lh1NvCiASbgG5fqRLDJEmMHsSU6GFuDGrAfNWDAqLuUNE5uL6A2bbf5wPkZrmdaAuGw36aDIC940TAajx1HBijIgEWmjpRWS4ytrnKq+1EDEibdJWAa3dqzjLGnrKaxxvt4OtXS09v7u1WX5S8KXjRABnQ7VbUCEV+Y7SDeWAJX4dfuLCnZFzt//rxRN500jqo74NvTVptY42fTnLcGI5FTVp2R/1/womEsHj/mwgxg27vd2BH8bCrLq0rKyjoTicSgUTcdNIrbkwD+nM2WOJ3qmaVI9d9sOotgTPCiPTLgi+oqdTbOAbea+lM6xyHLK8pnVXSiCCZNuiIyjZr2GArSS1YTOKie45n0UqT6L1ZdPn5c4EVHHIS6sA3WYLZvNg6E9L9GZmwZzgEdqAFDRl0xaET8EQB/2To21ngsQ0kbIv6zVXcxftzgxQDIgM+qVbUeGbDAPCCtxbfxUhdjHdGhoWGzrnAcIr4NwHflGbGf6PqyQCj0Yx7dRUUTAi9GwQQccapOL7bBm4yjIiPqSElpC5VYRzKZLPgE4M5hK0rt67CDZDM9A+k0XxmIhE6apONgJgxejBmLxw65VHUu/LjRaANeNZQpyhJZUToGBwdHjLqp0Ij4FgB/0wocaxw7DV8F4CcmM/6kwMMQRwYcrFad87DvXW8yTKlbkZVFSmlJB3bBlEk3CQYRvxfA3wbw0Vun7BAAPqjrmfaecPjbrGyib2sKTbS/LG5F4NhGe0d+fDiTuSMSiUx6F8Bn6V343N6TB3gSyb/aHwx22+2OX2KazfF3y7VMnw4FcUvCP8lJcgRtVph0yEu8pTnRBAiv270JwN+1AscQw5zr66YKXLgyVfBijBQc2YQ0PCIY4wPH2yQPERNTYpSPRSPid0qUvY/+1mU5QjJ8PVL96FhjjEdfCPDCzggyAKnPP7cZpWQFlsZ+yPGdMPaDiK/F6fEjbKeypXVK5/pGfyTYZZFPmi0UeOHAcCZI1+Oa6JjVG0SwHbcrnZDn7sytbQSPiLdLTBJXy+Z2nKcR8U09odDhfP0mKyskeBIggaERPb0WGfC1zSFK1gDcXsitER1t6m3wrkTEbRmC5ZTRCd+MiB+wjTlFwVSrfV7zdXV15aWy0oWKvNjWgJMOfyiAIklwYXLhwfd4G/47OAxnTMVRAKec3u0PB8SkFfyxFpSCGMBHTkpWHPsU2bEEKe8xDUrJdfhKnItzgiiEXKvXWhijR9CuzNgOwHWc1+87HQ5+aJQXki4KeOGgOOFJDkdnqeJowSGlweg00vsGHJAa1UpnTJKIAF5u1AM4R8S3APgeo7zQdFHS3uikz+VSSWXVlwBo+hoUbUR0ITfVHQEcEd+K4rbbOE4xaJPhYhg4HY3GcYG4HFB/so5vBT6q53TbdAAXtooe+SzghoaGakWSu2FwflZmfWMffxjAX7XKi8VPG3gBoKam5uoKpeQEDjBz7YD4dpwUd9rlxZMUPe2Nrvf19f2dTKdasap7jHIsiR3TDdxsfxq5xtpazad5g02al+Na6plpND0zTHk8Hp+4iLyU3vwLp0orLWXqrZQAAAAASUVORK5CYII=";
NSData *data = [[NSData alloc] initWithBase64EncodedString:base64Data options:NSDataBase64DecodingIgnoreUnknownCharacters];
keyboardDownImage = [UIImage imageWithData:data scale:3];
//Support for RTL languages like Arabic, Persia etc... (Bug ID: #448)
keyboardDownImage = [keyboardDownImage imageFlippedForRightToLeftLayoutDirection];
}
return keyboardDownImage;
}
+(UIImage*)keyboardPreviousImage
{
if (@available(iOS 10.0, *))
{
return [UIImage keyboardUpImage];
}
else
{
return [UIImage keyboardLeftImage];
}
}
+(UIImage*)keyboardNextImage
{
if (@available(iOS 10.0, *))
{
return [UIImage keyboardDownImage];
}
else
{
return [UIImage keyboardRightImage];
}
}
@end
/*UIKeyboardToolbar Category implementation*/
@implementation UIView (IQToolbarAddition)
-(IQToolbar *)keyboardToolbar
{
IQToolbar *keyboardToolbar = nil;
if ([[self inputAccessoryView] isKindOfClass:[IQToolbar class]])
{
keyboardToolbar = [self inputAccessoryView];
}
else
{
keyboardToolbar = objc_getAssociatedObject(self, @selector(keyboardToolbar));
if (keyboardToolbar == nil)
{
CGRect frame = CGRectMake(0, 0, UIScreen.mainScreen.bounds.size.width, 44);
keyboardToolbar = [[IQToolbar alloc] initWithFrame:frame];
objc_setAssociatedObject(self, @selector(keyboardToolbar), keyboardToolbar, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}
return keyboardToolbar;
}
-(void)setShouldHideToolbarPlaceholder:(BOOL)shouldHideToolbarPlaceholder
{
objc_setAssociatedObject(self, @selector(shouldHideToolbarPlaceholder), @(shouldHideToolbarPlaceholder), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
self.keyboardToolbar.titleBarButton.title = self.drawingToolbarPlaceholder;
}
-(BOOL)shouldHideToolbarPlaceholder
{
NSNumber *shouldHideToolbarPlaceholder = objc_getAssociatedObject(self, @selector(shouldHideToolbarPlaceholder));
return [shouldHideToolbarPlaceholder boolValue];
}
-(void)setToolbarPlaceholder:(NSString *)toolbarPlaceholder
{
objc_setAssociatedObject(self, @selector(toolbarPlaceholder), toolbarPlaceholder, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
self.keyboardToolbar.titleBarButton.title = self.drawingToolbarPlaceholder;
}
-(NSString *)toolbarPlaceholder
{
NSString *toolbarPlaceholder = objc_getAssociatedObject(self, @selector(toolbarPlaceholder));
return toolbarPlaceholder;
}
-(NSString *)drawingToolbarPlaceholder
{
if (self.shouldHideToolbarPlaceholder)
{
return nil;
}
else if (self.toolbarPlaceholder.length != 0)
{
return self.toolbarPlaceholder;
}
else if ([self respondsToSelector:@selector(placeholder)])
{
return [(UITextField*)self placeholder];
}
else
{
return nil;
}
}
#pragma mark - Private helper
+(IQBarButtonItem*)flexibleBarButtonItem
{
static IQBarButtonItem *nilButton = nil;
if (nilButton == nil)
{
nilButton = [[IQBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
}
return nilButton;
}
#pragma mark - Common
- (void)addKeyboardToolbarWithTarget:(id)target titleText:(NSString*)titleText rightBarButtonConfiguration:(IQBarButtonItemConfiguration*)rightBarButtonConfiguration previousBarButtonConfiguration:(IQBarButtonItemConfiguration*)previousBarButtonConfiguration nextBarButtonConfiguration:(IQBarButtonItemConfiguration*)nextBarButtonConfiguration
{
//If can't set InputAccessoryView. Then return
if (![self respondsToSelector:@selector(setInputAccessoryView:)]) return;
// Creating a toolBar for phoneNumber keyboard
IQToolbar *toolbar = self.keyboardToolbar;
NSMutableArray<UIBarButtonItem*> *items = [[NSMutableArray alloc] init];
if(previousBarButtonConfiguration)
{
IQBarButtonItem *prev = toolbar.previousBarButton;
if (prev.isSystemItem == NO && (previousBarButtonConfiguration.image || previousBarButtonConfiguration.title))
{
prev.title = previousBarButtonConfiguration.title;
prev.accessibilityLabel = previousBarButtonConfiguration.accessibilityLabel;
prev.accessibilityIdentifier = prev.accessibilityLabel;
prev.image = previousBarButtonConfiguration.image;
prev.target = target;
prev.action = previousBarButtonConfiguration.action;
}
else if (previousBarButtonConfiguration.image)
{
prev = [[IQBarButtonItem alloc] initWithImage:previousBarButtonConfiguration.image style:UIBarButtonItemStylePlain target:target action:previousBarButtonConfiguration.action];
prev.invocation = toolbar.previousBarButton.invocation;
prev.accessibilityLabel = previousBarButtonConfiguration.accessibilityLabel;
prev.accessibilityIdentifier = prev.accessibilityLabel;
prev.enabled = toolbar.previousBarButton.enabled;
prev.tag = toolbar.previousBarButton.tag;
toolbar.previousBarButton = prev;
}
else if (previousBarButtonConfiguration.title)
{
prev = [[IQBarButtonItem alloc] initWithTitle:previousBarButtonConfiguration.title style:UIBarButtonItemStylePlain target:target action:previousBarButtonConfiguration.action];
prev.invocation = toolbar.previousBarButton.invocation;
prev.accessibilityLabel = previousBarButtonConfiguration.accessibilityLabel;
prev.accessibilityIdentifier = prev.accessibilityLabel;
prev.enabled = toolbar.previousBarButton.enabled;
prev.tag = toolbar.previousBarButton.tag;
toolbar.previousBarButton = prev;
}
else
{
prev = [[IQBarButtonItem alloc] initWithBarButtonSystemItem:previousBarButtonConfiguration.barButtonSystemItem target:target action:previousBarButtonConfiguration.action];
prev.invocation = toolbar.previousBarButton.invocation;
prev.accessibilityLabel = previousBarButtonConfiguration.accessibilityLabel;
prev.accessibilityIdentifier = prev.accessibilityLabel;
prev.enabled = toolbar.previousBarButton.enabled;
prev.tag = toolbar.previousBarButton.tag;
toolbar.previousBarButton = prev;
}
[items addObject:prev];
}
if (previousBarButtonConfiguration != nil && nextBarButtonConfiguration != nil)
{
[items addObject:toolbar.fixedSpaceBarButton];
}
if(nextBarButtonConfiguration)
{
IQBarButtonItem *next = toolbar.nextBarButton;
if (next.isSystemItem == NO && (nextBarButtonConfiguration.image || nextBarButtonConfiguration.title))
{
next.title = nextBarButtonConfiguration.title;
next.accessibilityLabel = nextBarButtonConfiguration.accessibilityLabel;
next.accessibilityIdentifier = next.accessibilityLabel;
next.image = nextBarButtonConfiguration.image;
next.target = target;
next.action = nextBarButtonConfiguration.action;
}
else if (nextBarButtonConfiguration.image)
{
next = [[IQBarButtonItem alloc] initWithImage:nextBarButtonConfiguration.image style:UIBarButtonItemStylePlain target:target action:nextBarButtonConfiguration.action];
next.invocation = toolbar.nextBarButton.invocation;
next.accessibilityLabel = nextBarButtonConfiguration.accessibilityLabel;
next.accessibilityIdentifier = next.accessibilityLabel;
next.enabled = toolbar.nextBarButton.enabled;
next.tag = toolbar.nextBarButton.tag;
toolbar.nextBarButton = next;
}
else if (nextBarButtonConfiguration.title)
{
next = [[IQBarButtonItem alloc] initWithTitle:nextBarButtonConfiguration.title style:UIBarButtonItemStylePlain target:target action:nextBarButtonConfiguration.action];
next.invocation = toolbar.nextBarButton.invocation;
next.accessibilityLabel = nextBarButtonConfiguration.accessibilityLabel;
next.accessibilityIdentifier = next.accessibilityLabel;
next.enabled = toolbar.nextBarButton.enabled;
next.tag = toolbar.nextBarButton.tag;
toolbar.nextBarButton = next;
}
else
{
next = [[IQBarButtonItem alloc] initWithBarButtonSystemItem:nextBarButtonConfiguration.barButtonSystemItem target:target action:nextBarButtonConfiguration.action];
next.invocation = toolbar.nextBarButton.invocation;
next.accessibilityLabel = nextBarButtonConfiguration.accessibilityLabel;
next.accessibilityIdentifier = next.accessibilityLabel;
next.enabled = toolbar.nextBarButton.enabled;
next.tag = toolbar.nextBarButton.tag;
toolbar.nextBarButton = next;
}
[items addObject:next];
}
//Title
{
//Flexible space
[items addObject:[[self class] flexibleBarButtonItem]];
//Title button
toolbar.titleBarButton.title = titleText;
if (@available(iOS 11.0, *)) {}
else
{
toolbar.titleBarButton.customView.frame = CGRectZero;
}
[items addObject:toolbar.titleBarButton];
//Flexible space
[items addObject:[[self class] flexibleBarButtonItem]];
}
if(rightBarButtonConfiguration)
{
IQBarButtonItem *done = toolbar.doneBarButton;
if (done.isSystemItem == NO && (rightBarButtonConfiguration.image || rightBarButtonConfiguration.title))
{
done.title = rightBarButtonConfiguration.title;
done.accessibilityLabel = rightBarButtonConfiguration.accessibilityLabel;
done.accessibilityIdentifier = done.accessibilityLabel;
done.image = rightBarButtonConfiguration.image;
done.target = target;
done.action = rightBarButtonConfiguration.action;
}
else if (rightBarButtonConfiguration.image)
{
done = [[IQBarButtonItem alloc] initWithImage:rightBarButtonConfiguration.image style:UIBarButtonItemStylePlain target:target action:rightBarButtonConfiguration.action];
done.invocation = toolbar.doneBarButton.invocation;
done.accessibilityLabel = rightBarButtonConfiguration.accessibilityLabel;
done.accessibilityIdentifier = done.accessibilityLabel;
done.enabled = toolbar.doneBarButton.enabled;
done.tag = toolbar.doneBarButton.tag;
toolbar.doneBarButton = done;
}
else if (rightBarButtonConfiguration.title)
{
done = [[IQBarButtonItem alloc] initWithTitle:rightBarButtonConfiguration.title style:UIBarButtonItemStylePlain target:target action:rightBarButtonConfiguration.action];
done.invocation = toolbar.doneBarButton.invocation;
done.accessibilityLabel = rightBarButtonConfiguration.accessibilityLabel;
done.accessibilityIdentifier = done.accessibilityLabel;
done.enabled = toolbar.doneBarButton.enabled;
done.tag = toolbar.doneBarButton.tag;
toolbar.doneBarButton = done;
}
else
{
done = [[IQBarButtonItem alloc] initWithBarButtonSystemItem:rightBarButtonConfiguration.barButtonSystemItem target:target action:rightBarButtonConfiguration.action];
done.invocation = toolbar.doneBarButton.invocation;
done.accessibilityLabel = rightBarButtonConfiguration.accessibilityLabel;
done.accessibilityIdentifier = done.accessibilityLabel;
done.enabled = toolbar.doneBarButton.enabled;
done.tag = toolbar.doneBarButton.tag;
toolbar.doneBarButton = done;
}
[items addObject:done];
}
// Adding button to toolBar.
[toolbar setItems:items];
// Setting toolbar to keyboard.
[(UITextField*)self setInputAccessoryView:toolbar];
if ([self respondsToSelector:@selector(keyboardAppearance)])
{
switch ([(UITextField*)self keyboardAppearance])
{
case UIKeyboardAppearanceDark: toolbar.barStyle = UIBarStyleBlack; break;
default: toolbar.barStyle = UIBarStyleDefault; break;
}
}
}
#pragma mark - Right
- (void)addRightButtonOnKeyboardWithText:(NSString*)text target:(id)target action:(SEL)action
{
[self addRightButtonOnKeyboardWithText:text target:target action:action titleText:nil];
}
- (void)addRightButtonOnKeyboardWithText:(NSString*)text target:(id)target action:(SEL)action shouldShowPlaceholder:(BOOL)shouldShowPlaceholder
{
[self addRightButtonOnKeyboardWithText:text target:target action:action titleText:(shouldShowPlaceholder?[self drawingToolbarPlaceholder]:nil)];
}
- (void)addRightButtonOnKeyboardWithText:(NSString*)text target:(id)target action:(SEL)action titleText:(NSString*)titleText
{
IQBarButtonItemConfiguration *rightConfiguration = [[IQBarButtonItemConfiguration alloc] initWithTitle:text action:action];
[self addKeyboardToolbarWithTarget:target titleText:titleText rightBarButtonConfiguration:rightConfiguration previousBarButtonConfiguration:nil nextBarButtonConfiguration:nil];
}
- (void)addRightButtonOnKeyboardWithImage:(UIImage*)image target:(id)target action:(SEL)action
{
[self addRightButtonOnKeyboardWithImage:image target:target action:action titleText:nil];
}
- (void)addRightButtonOnKeyboardWithImage:(UIImage*)image target:(id)target action:(SEL)action shouldShowPlaceholder:(BOOL)shouldShowPlaceholder
{
[self addRightButtonOnKeyboardWithImage:image target:target action:action titleText:(shouldShowPlaceholder?[self drawingToolbarPlaceholder]:nil)];
}
- (void)addRightButtonOnKeyboardWithImage:(UIImage*)image target:(id)target action:(SEL)action titleText:(NSString*)titleText
{
IQBarButtonItemConfiguration *rightConfiguration = [[IQBarButtonItemConfiguration alloc] initWithImage:image action:action];
[self addKeyboardToolbarWithTarget:target titleText:titleText rightBarButtonConfiguration:rightConfiguration previousBarButtonConfiguration:nil nextBarButtonConfiguration:nil];
}
-(void)addDoneOnKeyboardWithTarget:(id)target action:(SEL)action
{
[self addDoneOnKeyboardWithTarget:target action:action titleText:nil];
}
-(void)addDoneOnKeyboardWithTarget:(id)target action:(SEL)action shouldShowPlaceholder:(BOOL)shouldShowPlaceholder
{
[self addDoneOnKeyboardWithTarget:target action:action titleText:(shouldShowPlaceholder?[self drawingToolbarPlaceholder]:nil)];
}
- (void)addDoneOnKeyboardWithTarget:(id)target action:(SEL)action titleText:(NSString*)titleText
{
IQBarButtonItemConfiguration *rightConfiguration = [[IQBarButtonItemConfiguration alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone action:action];
[self addKeyboardToolbarWithTarget:target titleText:titleText rightBarButtonConfiguration:rightConfiguration previousBarButtonConfiguration:nil nextBarButtonConfiguration:nil];
}
- (void)addLeftRightOnKeyboardWithTarget:(id)target leftButtonTitle:(NSString*)leftTitle rightButtonTitle:(NSString*)rightTitle leftButtonAction:(SEL)leftAction rightButtonAction:(SEL)rightAction
{
[self addLeftRightOnKeyboardWithTarget:target leftButtonTitle:leftTitle rightButtonTitle:rightTitle leftButtonAction:leftAction rightButtonAction:rightAction titleText:nil];
}
- (void)addLeftRightOnKeyboardWithTarget:(id)target leftButtonTitle:(NSString*)leftTitle rightButtonTitle:(NSString*)rightTitle leftButtonAction:(SEL)leftAction rightButtonAction:(SEL)rightAction shouldShowPlaceholder:(BOOL)shouldShowPlaceholder
{
[self addLeftRightOnKeyboardWithTarget:target leftButtonTitle:leftTitle rightButtonTitle:rightTitle leftButtonAction:leftAction rightButtonAction:rightAction titleText:(shouldShowPlaceholder?[self drawingToolbarPlaceholder]:nil)];
}
- (void)addLeftRightOnKeyboardWithTarget:(id)target leftButtonTitle:(NSString*)leftTitle rightButtonTitle:(NSString*)rightTitle leftButtonAction:(SEL)leftAction rightButtonAction:(SEL)rightAction titleText:(NSString*)titleText
{
IQBarButtonItemConfiguration *leftConfiguration = [[IQBarButtonItemConfiguration alloc] initWithTitle:leftTitle action:leftAction];
IQBarButtonItemConfiguration *rightConfiguration = [[IQBarButtonItemConfiguration alloc] initWithTitle:rightTitle action:rightAction];
[self addKeyboardToolbarWithTarget:target titleText:titleText rightBarButtonConfiguration:rightConfiguration previousBarButtonConfiguration:leftConfiguration nextBarButtonConfiguration:nil];
}
-(void)addCancelDoneOnKeyboardWithTarget:(id)target cancelAction:(SEL)cancelAction doneAction:(SEL)doneAction
{
[self addCancelDoneOnKeyboardWithTarget:target cancelAction:cancelAction doneAction:doneAction titleText:nil];
}
-(void)addCancelDoneOnKeyboardWithTarget:(id)target cancelAction:(SEL)cancelAction doneAction:(SEL)doneAction shouldShowPlaceholder:(BOOL)shouldShowPlaceholder
{
[self addCancelDoneOnKeyboardWithTarget:target cancelAction:cancelAction doneAction:doneAction titleText:(shouldShowPlaceholder?[self drawingToolbarPlaceholder]:nil)];
}
- (void)addCancelDoneOnKeyboardWithTarget:(id)target cancelAction:(SEL)cancelAction doneAction:(SEL)doneAction titleText:(NSString*)titleText
{
IQBarButtonItemConfiguration *leftConfiguration = [[IQBarButtonItemConfiguration alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel action:cancelAction];
IQBarButtonItemConfiguration *rightConfiguration = [[IQBarButtonItemConfiguration alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone action:doneAction];
[self addKeyboardToolbarWithTarget:target titleText:titleText rightBarButtonConfiguration:rightConfiguration previousBarButtonConfiguration:leftConfiguration nextBarButtonConfiguration:nil];
}
-(void)addPreviousNextDoneOnKeyboardWithTarget:(id)target previousAction:(SEL)previousAction nextAction:(SEL)nextAction doneAction:(SEL)doneAction
{
[self addPreviousNextDoneOnKeyboardWithTarget:target previousAction:previousAction nextAction:nextAction doneAction:doneAction titleText:nil];
}
-(void)addPreviousNextDoneOnKeyboardWithTarget:(id)target previousAction:(SEL)previousAction nextAction:(SEL)nextAction doneAction:(SEL)doneAction shouldShowPlaceholder:(BOOL)shouldShowPlaceholder
{
[self addPreviousNextDoneOnKeyboardWithTarget:target previousAction:previousAction nextAction:nextAction doneAction:doneAction titleText:(shouldShowPlaceholder?[self drawingToolbarPlaceholder]:nil)];
}
- (void)addPreviousNextDoneOnKeyboardWithTarget:(id)target previousAction:(SEL)previousAction nextAction:(SEL)nextAction doneAction:(SEL)doneAction titleText:(NSString*)titleText
{
IQBarButtonItemConfiguration *previousConfiguration = [[IQBarButtonItemConfiguration alloc] initWithImage:[UIImage keyboardPreviousImage] action:previousAction];
IQBarButtonItemConfiguration *nextConfiguration = [[IQBarButtonItemConfiguration alloc] initWithImage:[UIImage keyboardNextImage] action:nextAction];
IQBarButtonItemConfiguration *rightConfiguration = [[IQBarButtonItemConfiguration alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone action:doneAction];
[self addKeyboardToolbarWithTarget:target titleText:titleText rightBarButtonConfiguration:rightConfiguration previousBarButtonConfiguration:previousConfiguration nextBarButtonConfiguration:nextConfiguration];
}
- (void)addPreviousNextRightOnKeyboardWithTarget:(nullable id)target rightButtonImage:(nullable UIImage*)rightButtonImage previousAction:(nullable SEL)previousAction nextAction:(nullable SEL)nextAction rightButtonAction:(nullable SEL)rightButtonAction
{
[self addPreviousNextRightOnKeyboardWithTarget:target rightButtonImage:rightButtonImage previousAction:previousAction nextAction:nextAction rightButtonAction:rightButtonAction titleText:nil];
}
- (void)addPreviousNextRightOnKeyboardWithTarget:(nullable id)target rightButtonImage:(nullable UIImage*)rightButtonImage previousAction:(nullable SEL)previousAction nextAction:(nullable SEL)nextAction rightButtonAction:(nullable SEL)rightButtonAction shouldShowPlaceholder:(BOOL)shouldShowPlaceholder
{
[self addPreviousNextRightOnKeyboardWithTarget:target rightButtonImage:rightButtonImage previousAction:previousAction nextAction:nextAction rightButtonAction:rightButtonAction titleText:(shouldShowPlaceholder?[self drawingToolbarPlaceholder]:nil)];
}
- (void)addPreviousNextRightOnKeyboardWithTarget:(id)target rightButtonImage:(UIImage*)rightButtonImage previousAction:(SEL)previousAction nextAction:(SEL)nextAction rightButtonAction:(SEL)rightButtonAction titleText:(NSString*)titleText
{
IQBarButtonItemConfiguration *previousConfiguration = [[IQBarButtonItemConfiguration alloc] initWithImage:[UIImage keyboardPreviousImage] action:previousAction];
IQBarButtonItemConfiguration *nextConfiguration = [[IQBarButtonItemConfiguration alloc] initWithImage:[UIImage keyboardNextImage] action:nextAction];
IQBarButtonItemConfiguration *rightConfiguration = [[IQBarButtonItemConfiguration alloc] initWithImage:rightButtonImage action:rightButtonAction];
[self addKeyboardToolbarWithTarget:target titleText:titleText rightBarButtonConfiguration:rightConfiguration previousBarButtonConfiguration:previousConfiguration nextBarButtonConfiguration:nextConfiguration];
}
- (void)addPreviousNextRightOnKeyboardWithTarget:(id)target rightButtonTitle:(NSString*)rightButtonTitle previousAction:(SEL)previousAction nextAction:(SEL)nextAction rightButtonAction:(SEL)rightButtonAction
{
[self addPreviousNextRightOnKeyboardWithTarget:target rightButtonTitle:rightButtonTitle previousAction:previousAction nextAction:nextAction rightButtonAction:rightButtonAction titleText:nil];
}
- (void)addPreviousNextRightOnKeyboardWithTarget:(id)target rightButtonTitle:(NSString*)rightButtonTitle previousAction:(SEL)previousAction nextAction:(SEL)nextAction rightButtonAction:(SEL)rightButtonAction shouldShowPlaceholder:(BOOL)shouldShowPlaceholder
{
[self addPreviousNextRightOnKeyboardWithTarget:target rightButtonTitle:rightButtonTitle previousAction:previousAction nextAction:nextAction rightButtonAction:rightButtonAction titleText:(shouldShowPlaceholder?[self drawingToolbarPlaceholder]:nil)];
}
- (void)addPreviousNextRightOnKeyboardWithTarget:(id)target rightButtonTitle:(NSString*)rightButtonTitle previousAction:(SEL)previousAction nextAction:(SEL)nextAction rightButtonAction:(SEL)rightButtonAction titleText:(NSString*)titleText
{
IQBarButtonItemConfiguration *previousConfiguration = [[IQBarButtonItemConfiguration alloc] initWithImage:[UIImage keyboardPreviousImage] action:previousAction];
IQBarButtonItemConfiguration *nextConfiguration = [[IQBarButtonItemConfiguration alloc] initWithImage:[UIImage keyboardNextImage] action:nextAction];
IQBarButtonItemConfiguration *rightConfiguration = [[IQBarButtonItemConfiguration alloc] initWithTitle:rightButtonTitle action:rightButtonAction];
[self addKeyboardToolbarWithTarget:target titleText:titleText rightBarButtonConfiguration:rightConfiguration previousBarButtonConfiguration:previousConfiguration nextBarButtonConfiguration:nextConfiguration];
}
@end
Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
MIT License
Copyright (c) 2013-2017 Iftekhar Qurashi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
......@@ -7,14 +9,13 @@ 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 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.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
<p align="center">
<img src="https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/master/Demo/Resources/icon.png" alt="Icon"/>
</p>
<H1 align="center">IQKeyboardManager</H1>
<p align="center">
<img src="https://img.shields.io/github/license/hackiftekhar/IQKeyboardManager.svg"
alt="GitHub license"/>
[![Build Status](https://travis-ci.org/hackiftekhar/IQKeyboardManager.svg)](https://travis-ci.org/hackiftekhar/IQKeyboardManager)
While developing iOS apps, we often run into issues where the iPhone keyboard slides up and covers the `UITextField/UITextView`. `IQKeyboardManager` allows you to prevent this issue of keyboard sliding up and covering `UITextField/UITextView` without needing you to write any code or make any additional setup. To use `IQKeyboardManager` you simply need to add source files to your project.
#### Key Features
1) `**CODELESS**, Zero Lines of Code`
2) `Works Automatically`
3) `No More UIScrollView`
4) `No More Subclasses`
5) `No More Manual Work`
6) `No More #imports`
`IQKeyboardManager` works on all orientations, and with the toolbar. It also has nice optional features allowing you to customize the distance from the text field, behaviour of previous, next and done buttons in the keyboard toolbar, play sound when the user navigates through the form and more.
## Screenshot
[![IQKeyboardManager](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/v3.3.0/Screenshot/IQKeyboardManagerScreenshot.png)](http://youtu.be/6nhLw6hju2A)
[![Settings](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/v3.3.0/Screenshot/IQKeyboardManagerSettings.png)](http://youtu.be/6nhLw6hju2A)
## GIF animation
[![IQKeyboardManager](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/v3.3.0/Screenshot/IQKeyboardManager.gif)](http://youtu.be/6nhLw6hju2A)
## Video
<a href="http://youtu.be/WAYc2Qj-OQg" target="_blank"><img src="http://img.youtube.com/vi/WAYc2Qj-OQg/0.jpg"
alt="IQKeyboardManager Demo Video" width="480" height="360" border="10" /></a>
## Tutorial video by @rebeloper ([#1135](https://github.com/hackiftekhar/IQKeyboardManager/issues/1135))
@rebeloper demonstrated two videos on how to implement **IQKeyboardManager** at it's core:
<a href="https://www.youtube.com/playlist?list=PL_csAAO9PQ8aTL87XnueOXi3RpWE2m_8v" target="_blank"><img src="https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/master/Screenshot/ThirdPartyYoutubeTutorial.jpg"
alt="Youtube Tutorial Playlist"/></a>
https://www.youtube.com/playlist?list=PL_csAAO9PQ8aTL87XnueOXi3RpWE2m_8v
## Warning
- **If you're planning to build SDK/library/framework and want to handle UITextField/UITextView with IQKeyboardManager then you're totally going the wrong way.** I would never suggest to add **IQKeyboardManager** as **dependency/adding/shipping** with any third-party library. Instead of adding **IQKeyboardManager** you should implement your own solution to achieve same kind of results. **IQKeyboardManager** is totally designed for projects to help developers for their convenience, it's not designed for **adding/dependency/shipping** with any **third-party library**, because **doing this could block adoption by other developers for their projects as well (who are not using IQKeyboardManager and have implemented their custom solution to handle UITextField/UITextView in the project).**
- If **IQKeyboardManager** conflicts with other **third-party library**, then it's **developer responsibility** to **enable/disable IQKeyboardManager** when **presenting/dismissing** third-party library UI. Third-party libraries are not responsible to handle IQKeyboardManager.
## Requirements
[![Platform iOS](https://img.shields.io/badge/Platform-iOS-blue.svg?style=fla)]()
| | Language | Minimum iOS Target | Minimum Xcode Version |
|------------------------|----------|--------------------|-----------------------|
| IQKeyboardManager | Obj-C | iOS 8.0 | Xcode 9 |
| IQKeyboardManagerSwift | Swift | iOS 8.0 | Xcode 9 |
| Demo Project | | | Xcode 11 |
#### Swift versions support
| Swift | Xcode | IQKeyboardManagerSwift |
|-------------------|-------|------------------------|
| 5.1, 5.0, 4.2, 4.0, 3.2, 3.0| 11 | >= 6.5.0 |
| 5.0,4.2, 4.0, 3.2, 3.0| 10.2 | >= 6.2.1 |
| 4.2, 4.0, 3.2, 3.0| 10.0 | >= 6.0.4 |
| 4.0, 3.2, 3.0 | 9.0 | 5.0.0 |
Installation
==========================
#### Installation with CocoaPods
[![CocoaPods](https://img.shields.io/cocoapods/v/IQKeyboardManager.svg)](http://cocoadocs.org/docsets/IQKeyboardManager)
***IQKeyboardManager (Objective-C):*** IQKeyboardManager is available through [CocoaPods](http://cocoapods.org). To install
it, simply add the following line to your Podfile: ([#9](https://github.com/hackiftekhar/IQKeyboardManager/issues/9))
```ruby
pod 'IQKeyboardManager' #iOS8 and later
```
***IQKeyboardManager (Swift):*** IQKeyboardManagerSwift is available through [CocoaPods](http://cocoapods.org). To install
it, simply add the following line to your Podfile: ([#236](https://github.com/hackiftekhar/IQKeyboardManager/issues/236))
*Swift 5.1, 5.0, 4.2, 4.0, 3.2, 3.0 (Xcode 11)*
```ruby
pod 'IQKeyboardManagerSwift'
```
*Or you can choose the version you need based on Swift support table from [Requirements](README.md#requirements)*
```ruby
pod 'IQKeyboardManagerSwift', '6.3.0'
```
In AppDelegate.swift, just import IQKeyboardManagerSwift framework and enable IQKeyboardManager.
```swift
import IQKeyboardManagerSwift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
IQKeyboardManager.shared.enable = true
return true
}
}
```
#### Installation with Carthage
[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.
You can install Carthage with [Homebrew](http://brew.sh/) using the following command:
```bash
$ brew update
$ brew install carthage
```
To integrate `IQKeyboardManger` or `IQKeyboardManagerSwift` into your Xcode project using Carthage, add the following line to your `Cartfile`:
```ogdl
github "hackiftekhar/IQKeyboardManager"
```
Run `carthage` to build the frameworks and drag the appropriate framework (`IQKeyboardManager.framework` or `IQKeyboardManagerSwift.framework`) into your Xcode project based on your need. Make sure to add only one framework and not both.
#### Installation with Source Code
[![Github tag](https://img.shields.io/github/tag/hackiftekhar/iqkeyboardmanager.svg)]()
***IQKeyboardManager (Objective-C):*** Just ***drag and drop*** `IQKeyboardManager` directory from demo project to your project. That's it.
***IQKeyboardManager (Swift):*** ***Drag and drop*** `IQKeyboardManagerSwift` directory from demo project to your project
In AppDelegate.swift, just enable IQKeyboardManager.
```swift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
IQKeyboardManager.shared.enable = true
return true
}
}
```
#### Installation with Swift Package Manager
[Swift Package Manager(SPM)](https://swift.org/package-manager/) is Apple's dependency manager tool. It is now supported in Xcode 11. So it can be used in all appleOS types of projects. It can be used alongside other tools like CocoaPods and Carthage as well.
To install IQKeyboardManager package into your packages, add a reference to IQKeyboardManager and a targeting release version in the dependencies section in `Package.swift` file:
```swift
import PackageDescription
let package = Package(
name: "YOUR_PROJECT_NAME",
products: [],
dependencies: [
.package(url: "https://github.com/hackiftekhar/IQKeyboardManager.git", from: "6.5.0")
]
)
```
To install IQKeyboardManager package via Xcode
* Go to File -> Swift Packages -> Add Package Dependency...
* Then search for https://github.com/hackiftekhar/IQKeyboardManager.git
* And choose the version you want
Migration Guide
==========================
- [IQKeyboardManager 6.0.0 Migration Guide](https://github.com/hackiftekhar/IQKeyboardManager/wiki/IQKeyboardManager-6.0.0-Migration-Guide)
Other Links
==========================
- [Known Issues](https://github.com/hackiftekhar/IQKeyboardManager/wiki/Known-Issues)
- [Manual Management Tweaks](https://github.com/hackiftekhar/IQKeyboardManager/wiki/Manual-Management)
- [Properties and functions usage](https://github.com/hackiftekhar/IQKeyboardManager/wiki/Properties-&-Functions)
## Flow Diagram
[![IQKeyboardManager CFD](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/master/Screenshot/IQKeyboardManagerFlowDiagram.jpg)](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/master/Screenshot/IQKeyboardManagerFlowDiagram.jpg)
If you would like to see detailed Flow diagram then check [Detailed Flow Diagram](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardManager/v3.3.0/Screenshot/IQKeyboardManagerCFD.jpg).
LICENSE
---
Distributed under the MIT License.
Contributions
---
Any contribution is more than welcome! You can contribute through pull requests and issues on GitHub.
Author
---
If you wish to contact me, email at: hack.iftekhar@gmail.com
PODS:
- AFNetworking (4.0.1):
- AFNetworking/NSURLSession (= 4.0.1)
- AFNetworking/Reachability (= 4.0.1)
- AFNetworking/Security (= 4.0.1)
- AFNetworking/Serialization (= 4.0.1)
- AFNetworking/UIKit (= 4.0.1)
- AFNetworking/NSURLSession (4.0.1):
- AFNetworking/Reachability
- AFNetworking/Security
......@@ -6,6 +12,8 @@ PODS:
- AFNetworking/Reachability (4.0.1)
- AFNetworking/Security (4.0.1)
- AFNetworking/Serialization (4.0.1)
- AFNetworking/UIKit (4.0.1):
- AFNetworking/NSURLSession
- DKNightVersion (2.4.3):
- DKNightVersion/Core (= 2.4.3)
- DKNightVersion/CoreAnimation (= 2.4.3)
......@@ -22,13 +30,12 @@ PODS:
- DOUAudioStreamer (0.2.16)
- FreeStreamer (4.0.0):
- Reachability (~> 3.0)
- IQKeyboardManager (6.5.10)
- lottie-ios (2.5.3)
- Masonry (1.1.0)
- MBProgressHUD (1.2.0)
- MJRefresh (3.7.5)
- Reachability (3.2)
- YTKNetwork (3.0.6):
- AFNetworking/NSURLSession (~> 4.0)
- YYCache (1.0.4)
- YYImage (1.0.4):
- YYImage/Core (= 1.0.4)
......@@ -41,14 +48,15 @@ PODS:
- YYImage
DEPENDENCIES:
- AFNetworking (~> 4.0.1)
- DKNightVersion (~> 2.4.3)
- DOUAudioStreamer (~> 0.2.16)
- FreeStreamer (~> 4.0.0)
- IQKeyboardManager (~> 6.5.10)
- lottie-ios (~> 2.5.3)
- Masonry (~> 1.1.0)
- MBProgressHUD (~> 1.2.0)
- MJRefresh (~> 3.7.5)
- YTKNetwork (~> 3.0.6)
- YYImage/WebP
- YYModel (~> 1.0.4)
- YYWebImage (~> 1.0.5)
......@@ -59,12 +67,12 @@ SPEC REPOS:
- DKNightVersion
- DOUAudioStreamer
- FreeStreamer
- IQKeyboardManager
- lottie-ios
- Masonry
- MBProgressHUD
- MJRefresh
- Reachability
- YTKNetwork
- YYCache
- YYImage
- YYModel
......@@ -75,17 +83,17 @@ SPEC CHECKSUMS:
DKNightVersion: eaa80cc4014b4bae7d4b535fd87ecc6a3c2767b3
DOUAudioStreamer: c503ba2ecb9a54ff7bda0eff66963ad224f3c7dc
FreeStreamer: 7e9c976045701ac2f7e9c14c17245203c37bf2ea
IQKeyboardManager: 45a1fa55c1a5b02c61ac0fd7fd5b62bb4ad20d97
lottie-ios: a50d5c0160425cd4b01b852bb9578963e6d92d31
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
MBProgressHUD: 3ee5efcc380f6a79a7cc9b363dd669c5e1ae7406
MJRefresh: fdf5e979eb406a0341468932d1dfc8b7f9fce961
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
YTKNetwork: c16be90b06be003de9e9cd0d3b187cc8eaf35c04
YYCache: 8105b6638f5e849296c71f331ff83891a4942952
YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54
YYModel: 2a7fdd96aaa4b86a824e26d0c517de8928c04b30
YYWebImage: 5f7f36aee2ae293f016d418c7d6ba05c4863e928
PODFILE CHECKSUM: d78d9f7fd55a2a7be3fae24d212bdd5eab78666c
PODFILE CHECKSUM: 597c449d3caf07f7d1329c26f74f21892c777293
COCOAPODS: 1.11.3
......@@ -10,6 +10,7 @@
#endif
#endif
#import "AFNetworking.h"
#import "AFHTTPSessionManager.h"
#import "AFURLSessionManager.h"
#import "AFCompatibilityMacros.h"
......@@ -17,6 +18,16 @@
#import "AFSecurityPolicy.h"
#import "AFURLRequestSerialization.h"
#import "AFURLResponseSerialization.h"
#import "AFAutoPurgingImageCache.h"
#import "AFImageDownloader.h"
#import "AFNetworkActivityIndicatorManager.h"
#import "UIActivityIndicatorView+AFNetworking.h"
#import "UIButton+AFNetworking.h"
#import "UIImageView+AFNetworking.h"
#import "UIKit+AFNetworking.h"
#import "UIProgressView+AFNetworking.h"
#import "UIRefreshControl+AFNetworking.h"
#import "WKWebView+AFNetworking.h"
FOUNDATION_EXPORT double AFNetworkingVersionNumber;
FOUNDATION_EXPORT const unsigned char AFNetworkingVersionString[];
......
......@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>3.0.6</string>
<string>6.5.10</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
......
#import <Foundation/Foundation.h>
@interface PodsDummy_YTKNetwork : NSObject
@interface PodsDummy_IQKeyboardManager : NSObject
@end
@implementation PodsDummy_YTKNetwork
@implementation PodsDummy_IQKeyboardManager
@end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
#import "IQKeyboardManager.h"
#import "IQKeyboardReturnKeyHandler.h"
#import "IQUIScrollView+Additions.h"
#import "IQUITextFieldView+Additions.h"
#import "IQUIView+Hierarchy.h"
#import "IQUIViewController+Additions.h"
#import "IQKeyboardManagerConstants.h"
#import "IQTextView.h"
#import "IQBarButtonItem.h"
#import "IQPreviousNextView.h"
#import "IQTitleBarButtonItem.h"
#import "IQToolbar.h"
#import "IQUIView+IQKeyboardToolbar.h"
FOUNDATION_EXPORT double IQKeyboardManagerVersionNumber;
FOUNDATION_EXPORT const unsigned char IQKeyboardManagerVersionString[];
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking"
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -framework "CFNetwork"
OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "Foundation" -framework "QuartzCore" -framework "UIKit"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/YTKNetwork
PODS_TARGET_SRCROOT = ${PODS_ROOT}/IQKeyboardManager
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
......
framework module YTKNetwork {
umbrella header "YTKNetwork-umbrella.h"
framework module IQKeyboardManager {
umbrella header "IQKeyboardManager-umbrella.h"
export *
module * { export * }
......
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking"
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
OTHER_LDFLAGS = $(inherited) -framework "AFNetworking" -framework "CFNetwork"
OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "Foundation" -framework "QuartzCore" -framework "UIKit"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/YTKNetwork
PODS_TARGET_SRCROOT = ${PODS_ROOT}/IQKeyboardManager
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
......
......@@ -141,6 +141,31 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
## IQKeyboardManager
MIT License
Copyright (c) 2013-2017 Iftekhar Qurashi
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.
## MBProgressHUD
Copyright © 2009-2020 Matej Bukovinski
......@@ -222,30 +247,6 @@ Redistribution and use in source and binary forms, with or without modification,
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## YTKNetwork
Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
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.
## YYCache
The MIT License (MIT)
......
......@@ -178,6 +178,37 @@ POSSIBILITY OF SUCH DAMAGE.
</dict>
<dict>
<key>FooterText</key>
<string>MIT License
Copyright (c) 2013-2017 Iftekhar Qurashi
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>IQKeyboardManager</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright © 2009-2020 Matej Bukovinski
Permission is hereby granted, free of charge, to any person obtaining a copy
......@@ -283,36 +314,6 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
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>YTKNetwork</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>The MIT License (MIT)
Copyright (c) 2015 ibireme &lt;ibireme@gmail.com&gt;
......
......@@ -3,11 +3,11 @@ ${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework
${BUILT_PRODUCTS_DIR}/DKNightVersion/DKNightVersion.framework
${BUILT_PRODUCTS_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework
${BUILT_PRODUCTS_DIR}/FreeStreamer/FreeStreamer.framework
${BUILT_PRODUCTS_DIR}/IQKeyboardManager/IQKeyboardManager.framework
${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework
${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework
${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework
${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework
${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework
${BUILT_PRODUCTS_DIR}/YYCache/YYCache.framework
${BUILT_PRODUCTS_DIR}/YYImage/YYImage.framework
${BUILT_PRODUCTS_DIR}/YYModel/YYModel.framework
......
......@@ -2,11 +2,11 @@ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AFNetworking.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKNightVersion.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DOUAudioStreamer.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FreeStreamer.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManager.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YTKNetwork.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYCache.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYImage.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYModel.framework
......
......@@ -3,11 +3,11 @@ ${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework
${BUILT_PRODUCTS_DIR}/DKNightVersion/DKNightVersion.framework
${BUILT_PRODUCTS_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework
${BUILT_PRODUCTS_DIR}/FreeStreamer/FreeStreamer.framework
${BUILT_PRODUCTS_DIR}/IQKeyboardManager/IQKeyboardManager.framework
${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework
${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework
${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework
${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework
${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework
${BUILT_PRODUCTS_DIR}/YYCache/YYCache.framework
${BUILT_PRODUCTS_DIR}/YYImage/YYImage.framework
${BUILT_PRODUCTS_DIR}/YYModel/YYModel.framework
......
......@@ -2,11 +2,11 @@ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AFNetworking.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKNightVersion.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DOUAudioStreamer.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FreeStreamer.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManager.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YTKNetwork.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYCache.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYImage.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYModel.framework
......
......@@ -3,11 +3,11 @@ ${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework
${BUILT_PRODUCTS_DIR}/DKNightVersion/DKNightVersion.framework
${BUILT_PRODUCTS_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework
${BUILT_PRODUCTS_DIR}/FreeStreamer/FreeStreamer.framework
${BUILT_PRODUCTS_DIR}/IQKeyboardManager/IQKeyboardManager.framework
${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework
${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework
${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework
${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework
${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework
${BUILT_PRODUCTS_DIR}/YYCache/YYCache.framework
${BUILT_PRODUCTS_DIR}/YYImage/YYImage.framework
${BUILT_PRODUCTS_DIR}/YYModel/YYModel.framework
......
......@@ -2,11 +2,11 @@ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AFNetworking.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKNightVersion.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DOUAudioStreamer.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FreeStreamer.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManager.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Masonry.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YTKNetwork.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYCache.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYImage.framework
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYModel.framework
......
......@@ -180,11 +180,11 @@ if [[ "$CONFIGURATION" == "Beta" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/DKNightVersion/DKNightVersion.framework"
install_framework "${BUILT_PRODUCTS_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework"
install_framework "${BUILT_PRODUCTS_DIR}/FreeStreamer/FreeStreamer.framework"
install_framework "${BUILT_PRODUCTS_DIR}/IQKeyboardManager/IQKeyboardManager.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YYCache/YYCache.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YYImage/YYImage.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YYModel/YYModel.framework"
......@@ -196,11 +196,11 @@ if [[ "$CONFIGURATION" == "Debug" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/DKNightVersion/DKNightVersion.framework"
install_framework "${BUILT_PRODUCTS_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework"
install_framework "${BUILT_PRODUCTS_DIR}/FreeStreamer/FreeStreamer.framework"
install_framework "${BUILT_PRODUCTS_DIR}/IQKeyboardManager/IQKeyboardManager.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YYCache/YYCache.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YYImage/YYImage.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YYModel/YYModel.framework"
......@@ -212,11 +212,11 @@ if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework "${BUILT_PRODUCTS_DIR}/DKNightVersion/DKNightVersion.framework"
install_framework "${BUILT_PRODUCTS_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework"
install_framework "${BUILT_PRODUCTS_DIR}/FreeStreamer/FreeStreamer.framework"
install_framework "${BUILT_PRODUCTS_DIR}/IQKeyboardManager/IQKeyboardManager.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework"
install_framework "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Masonry/Masonry.framework"
install_framework "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YTKNetwork/YTKNetwork.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YYCache/YYCache.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YYImage/YYImage.framework"
install_framework "${BUILT_PRODUCTS_DIR}/YYModel/YYModel.framework"
......
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios" "${PODS_ROOT}/YYImage/Vendor"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios" "${PODS_ROOT}/YYImage/Vendor"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion/DKNightVersion.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer/FreeStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork/YTKNetwork.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache/YYCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage/YYImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage/YYWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers" $(SDKROOT)/usr/include/libxml2
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion/DKNightVersion.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer/FreeStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager/IQKeyboardManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache/YYCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage/YYImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage/YYWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers" $(SDKROOT)/usr/include/libxml2
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"sqlite3" -l"xml2" -l"z" -framework "AFNetworking" -framework "AVFoundation" -framework "Accelerate" -framework "AssetsLibrary" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreAudio" -framework "CoreFoundation" -framework "CoreGraphics" -framework "DKNightVersion" -framework "DOUAudioStreamer" -framework "Foundation" -framework "FreeStreamer" -framework "ImageIO" -framework "Lottie" -framework "MBProgressHUD" -framework "MJRefresh" -framework "Masonry" -framework "MediaPlayer" -framework "MobileCoreServices" -framework "OpenGLES" -framework "QuartzCore" -framework "Reachability" -framework "SystemConfiguration" -framework "UIKit" -framework "YTKNetwork" -framework "YYCache" -framework "YYImage" -framework "YYModel" -framework "YYWebImage"
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"sqlite3" -l"xml2" -l"z" -framework "AFNetworking" -framework "AVFoundation" -framework "Accelerate" -framework "AssetsLibrary" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreAudio" -framework "CoreFoundation" -framework "CoreGraphics" -framework "DKNightVersion" -framework "DOUAudioStreamer" -framework "Foundation" -framework "FreeStreamer" -framework "IQKeyboardManager" -framework "ImageIO" -framework "Lottie" -framework "MBProgressHUD" -framework "MJRefresh" -framework "Masonry" -framework "MediaPlayer" -framework "MobileCoreServices" -framework "OpenGLES" -framework "QuartzCore" -framework "Reachability" -framework "SystemConfiguration" -framework "UIKit" -framework "YYCache" -framework "YYImage" -framework "YYModel" -framework "YYWebImage"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
......
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios" "${PODS_ROOT}/YYImage/Vendor"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios" "${PODS_ROOT}/YYImage/Vendor"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion/DKNightVersion.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer/FreeStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork/YTKNetwork.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache/YYCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage/YYImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage/YYWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers" $(SDKROOT)/usr/include/libxml2
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion/DKNightVersion.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer/FreeStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager/IQKeyboardManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache/YYCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage/YYImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage/YYWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers" $(SDKROOT)/usr/include/libxml2
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"sqlite3" -l"xml2" -l"z" -framework "AFNetworking" -framework "AVFoundation" -framework "Accelerate" -framework "AssetsLibrary" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreAudio" -framework "CoreFoundation" -framework "CoreGraphics" -framework "DKNightVersion" -framework "DOUAudioStreamer" -framework "Foundation" -framework "FreeStreamer" -framework "ImageIO" -framework "Lottie" -framework "MBProgressHUD" -framework "MJRefresh" -framework "Masonry" -framework "MediaPlayer" -framework "MobileCoreServices" -framework "OpenGLES" -framework "QuartzCore" -framework "Reachability" -framework "SystemConfiguration" -framework "UIKit" -framework "YTKNetwork" -framework "YYCache" -framework "YYImage" -framework "YYModel" -framework "YYWebImage"
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"sqlite3" -l"xml2" -l"z" -framework "AFNetworking" -framework "AVFoundation" -framework "Accelerate" -framework "AssetsLibrary" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreAudio" -framework "CoreFoundation" -framework "CoreGraphics" -framework "DKNightVersion" -framework "DOUAudioStreamer" -framework "Foundation" -framework "FreeStreamer" -framework "IQKeyboardManager" -framework "ImageIO" -framework "Lottie" -framework "MBProgressHUD" -framework "MJRefresh" -framework "Masonry" -framework "MediaPlayer" -framework "MobileCoreServices" -framework "OpenGLES" -framework "QuartzCore" -framework "Reachability" -framework "SystemConfiguration" -framework "UIKit" -framework "YYCache" -framework "YYImage" -framework "YYModel" -framework "YYWebImage"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
......
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios" "${PODS_ROOT}/YYImage/Vendor"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios" "${PODS_ROOT}/YYImage/Vendor"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion/DKNightVersion.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer/FreeStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YTKNetwork/YTKNetwork.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache/YYCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage/YYImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage/YYWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers" $(SDKROOT)/usr/include/libxml2
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DKNightVersion/DKNightVersion.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DOUAudioStreamer/DOUAudioStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/FreeStreamer/FreeStreamer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager/IQKeyboardManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache/YYCache.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage/YYImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage/YYWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers" $(SDKROOT)/usr/include/libxml2
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"sqlite3" -l"xml2" -l"z" -framework "AFNetworking" -framework "AVFoundation" -framework "Accelerate" -framework "AssetsLibrary" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreAudio" -framework "CoreFoundation" -framework "CoreGraphics" -framework "DKNightVersion" -framework "DOUAudioStreamer" -framework "Foundation" -framework "FreeStreamer" -framework "ImageIO" -framework "Lottie" -framework "MBProgressHUD" -framework "MJRefresh" -framework "Masonry" -framework "MediaPlayer" -framework "MobileCoreServices" -framework "OpenGLES" -framework "QuartzCore" -framework "Reachability" -framework "SystemConfiguration" -framework "UIKit" -framework "YTKNetwork" -framework "YYCache" -framework "YYImage" -framework "YYModel" -framework "YYWebImage"
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"sqlite3" -l"xml2" -l"z" -framework "AFNetworking" -framework "AVFoundation" -framework "Accelerate" -framework "AssetsLibrary" -framework "AudioToolbox" -framework "CFNetwork" -framework "CoreAudio" -framework "CoreFoundation" -framework "CoreGraphics" -framework "DKNightVersion" -framework "DOUAudioStreamer" -framework "Foundation" -framework "FreeStreamer" -framework "IQKeyboardManager" -framework "ImageIO" -framework "Lottie" -framework "MBProgressHUD" -framework "MJRefresh" -framework "Masonry" -framework "MediaPlayer" -framework "MobileCoreServices" -framework "OpenGLES" -framework "QuartzCore" -framework "Reachability" -framework "SystemConfiguration" -framework "UIKit" -framework "YYCache" -framework "YYImage" -framework "YYModel" -framework "YYWebImage"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
......
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
#import "YTKBaseRequest.h"
#import "YTKBatchRequest.h"
#import "YTKBatchRequestAgent.h"
#import "YTKChainRequest.h"
#import "YTKChainRequestAgent.h"
#import "YTKNetwork.h"
#import "YTKNetworkAgent.h"
#import "YTKNetworkConfig.h"
#import "YTKRequest.h"
#import "YTKRequestEventAccessory.h"
FOUNDATION_EXPORT double YTKNetworkVersionNumber;
FOUNDATION_EXPORT const unsigned char YTKNetworkVersionString[];
YTKNetwork
==========
![License MIT](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)
![Pod version](https://img.shields.io/cocoapods/v/YTKNetwork.svg?style=flat)
[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![Platform info](https://img.shields.io/cocoapods/p/YTKNetwork.svg?style=flat)](http://cocoadocs.org/docsets/YTKNetwork)
[![Build Status](https://api.travis-ci.org/yuantiku/YTKNetwork.svg?branch=master)](https://travis-ci.org/yuantiku/YTKNetwork)
## What
YTKNetwork is a high level request util based on [AFNetworking][AFNetworking]. It's developed by the iOS Team of YuanTiKu. It provides a High Level API for network request.
YTKNetwork is used in all products of YuanTiKu, including: [YuanTiKu][YuanTiKu], [YuanSoTi][YuanSoTi], [YuanFuDao][YuanFuDao], [FenBiZhiBoKe][FenBiZhiBoKe].
[**中文说明**](Docs/README_cn.md)
## Features
* Response can be cached by expiration time
* Response can be cached by version number
* Set common base URL and CDN URL
* Validate JSON response
* Resume download
* `block` and `delegate` callback
* Batch requests (see `YTKBatchRequest`)
* Chain requests (see `YTKChainRequest`)
* URL filter, replace part of URL, or append common parameter 
* Plugin mechanism, handle request start and finish. A plugin for show "Loading" HUD is provided
## Who
YTKNetowrk is suitable for a slightly more complex project, not for a simple personal project.
YTKNetwork is helpful if you want to cache requests, manage the dependences of requests, or validate the JSON response. And if you want to cache requests based on request version, this is one of the greatest advantages of YTKNetwork.
## Why 
YTKNetwork provides YTKRequest to handle every network request. You should inherit it and override some methods to define custom requests in your project.
The main idea is use the Command Pattern. The benefits are:
* Your code is decoupled to detail network request framework, it's easy to replace it. Actually, YTKNetwork is originally based on ASIHttpRequest, we just spent two days to switch to AFNetworking.
* Handle common logic in base class.
* Easier Persistence
But YTKNetwork is not suitable if your project is very simple. You can use AFNetworking directly in controller.
## Installation
To use YTKNetwork add the following to your Podfile
pod 'YTKNetwork'
Or add this in your Cartfile:
github "yuantiku/YTKNetwork" ~> 3.0
## Requirements
| YTKNetwork Version | AFNetworking Version | Minimum iOS Target | Note |
|:------------------:|:--------------------:|:-------------------:|:-----|
| 3.x | 4.x | iOS 9 | Xcode 11+ is required. |
| 2.x | 3.x | iOS 7 | Xcode 7+ is required. |
| 1.x | 2.x | iOS 6 | n/a |
YTKNetwork is based on AFNetworking. You can find more detail about version compatibility at [AFNetworking README](https://github.com/AFNetworking/AFNetworking).
## Guide & Demo
* [Basic Usage Guide](Docs/BasicGuide_en.md)
* [YTKNetwork 2.0 Migration Guide(Simplified Chinese)](Docs/2.0_MigrationGuide_cn.md)
## Contributors
* [lancy][lancyGithub]
* [maojj][maojjGithub]
* [veecci][veecciGithub]
* [tangqiaoboy][tangqiaoboyGithub]
* [skyline75489][skyline75489Github]
* [joeshang][joeshangGithub]
## Acknowledgements
* [AFNetworking]
* [AFDownloadRequestOperation]
Thanks for their great work.
## License
YTKNetwork is available under the MIT license. See the LICENSE file for more info.
<!-- external links -->
[AFNetworking]:https://github.com/AFNetworking/AFNetworking
[AFDownloadRequestOperation]:https://github.com/steipete/AFDownloadRequestOperation
[YuanTiKu]:http://www.yuantiku.com
[YuanSoTi]:http://www.yuansouti.com/
[YuanFuDao]:http://www.yuanfudao.com
[FenBiZhiBoKe]:http://ke.fenbi.com/
[tangqiaoboyGithub]:https://github.com/tangqiaoboy
[lancyGithub]:https://github.com/lancy
[maojjGithub]:https://github.com/maojj
[veecciGithub]:https://github.com/veecci
[skyline75489Github]:https://github.com/skyline75489
[joeshangGithub]:https://github.com/joeshang
//
// YTKBaseRequest.h
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
FOUNDATION_EXPORT NSString *const YTKRequestValidationErrorDomain;
NS_ENUM(NSInteger) {
YTKRequestValidationErrorInvalidStatusCode = -8,
YTKRequestValidationErrorInvalidJSONFormat = -9,
};
/// HTTP Request method.
typedef NS_ENUM(NSInteger, YTKRequestMethod) {
YTKRequestMethodGET = 0,
YTKRequestMethodPOST,
YTKRequestMethodHEAD,
YTKRequestMethodPUT,
YTKRequestMethodDELETE,
YTKRequestMethodPATCH,
};
/// Request serializer type.
typedef NS_ENUM(NSInteger, YTKRequestSerializerType) {
YTKRequestSerializerTypeHTTP = 0,
YTKRequestSerializerTypeJSON,
};
/// Response serializer type, which determines response serialization process and
/// the type of `responseObject`.
typedef NS_ENUM(NSInteger, YTKResponseSerializerType) {
/// NSData type
YTKResponseSerializerTypeHTTP,
/// JSON object type
YTKResponseSerializerTypeJSON,
/// NSXMLParser type
YTKResponseSerializerTypeXMLParser,
};
/// Request priority
typedef NS_ENUM(NSInteger, YTKRequestPriority) {
YTKRequestPriorityLow = -4L,
YTKRequestPriorityDefault = 0,
YTKRequestPriorityHigh = 4,
};
@protocol AFMultipartFormData;
typedef void (^AFConstructingBlock)(id<AFMultipartFormData> formData);
typedef void (^AFURLSessionTaskProgressBlock)(NSProgress *);
@class YTKBaseRequest;
typedef void(^YTKRequestCompletionBlock)(__kindof YTKBaseRequest *request);
/// The YTKRequestDelegate protocol defines several optional methods you can use
/// to receive network-related messages. All the delegate methods will be called
/// on the main queue.
@protocol YTKRequestDelegate <NSObject>
@optional
/// Tell the delegate that the request has finished successfully.
///
/// @param request The corresponding request.
- (void)requestFinished:(__kindof YTKBaseRequest *)request;
/// Tell the delegate that the request has failed.
///
/// @param request The corresponding request.
- (void)requestFailed:(__kindof YTKBaseRequest *)request;
@end
/// The YTKRequestAccessory protocol defines several optional methods that can be
/// used to track the status of a request. Objects that conforms this protocol
/// ("accessories") can perform additional configurations accordingly. All the
/// accessory methods will be called on the main queue.
@protocol YTKRequestAccessory <NSObject>
@optional
/// Inform the accessory that the request is about to start.
///
/// @param request The corresponding request.
- (void)requestWillStart:(id)request;
/// Inform the accessory that the request is about to stop. This method is called
/// before executing `requestFinished` and `successCompletionBlock`.
///
/// @param request The corresponding request.
- (void)requestWillStop:(id)request;
/// Inform the accessory that the request has already stoped. This method is called
/// after executing `requestFinished` and `successCompletionBlock`.
///
/// @param request The corresponding request.
- (void)requestDidStop:(id)request;
@end
/// YTKBaseRequest is the abstract class of network request. It provides many options
/// for constructing request. It's the base class of `YTKRequest`.
@interface YTKBaseRequest : NSObject
#pragma mark - Request and Response Information
///=============================================================================
/// @name Request and Response Information
///=============================================================================
/// The underlying NSURLSessionTask.
///
/// @warning This value is actually nil and should not be accessed before the request starts.
@property (nonatomic, strong, readonly) NSURLSessionTask *requestTask;
/// Shortcut for `requestTask.currentRequest`.
@property (nonatomic, strong, readonly) NSURLRequest *currentRequest;
/// Shortcut for `requestTask.originalRequest`.
@property (nonatomic, strong, readonly) NSURLRequest *originalRequest;
/// Shortcut for `requestTask.response`.
@property (nonatomic, strong, readonly) NSHTTPURLResponse *response;
/// The response status code.
@property (nonatomic, readonly) NSInteger responseStatusCode;
/// The response header fields.
@property (nonatomic, strong, readonly, nullable) NSDictionary *responseHeaders;
/// The raw data representation of response. Note this value can be nil if request failed.
@property (nonatomic, strong, readonly, nullable) NSData *responseData;
/// The string representation of response. Note this value can be nil if request failed.
@property (nonatomic, strong, readonly, nullable) NSString *responseString;
/// This serialized response object. The actual type of this object is determined by
/// `YTKResponseSerializerType`. Note this value can be nil if request failed.
///
/// @discussion If `resumableDownloadPath` and DownloadTask is using, this value will
/// be the path to which file is successfully saved (NSURL), or nil if request failed.
@property (nonatomic, strong, readonly, nullable) id responseObject;
/// If you use `YTKResponseSerializerTypeJSON`, this is a convenience (and sematic) getter
/// for the response object. Otherwise this value is nil.
@property (nonatomic, strong, readonly, nullable) id responseJSONObject;
/// This error can be either serialization error or network error. If nothing wrong happens
/// this value will be nil.
@property (nonatomic, strong, readonly, nullable) NSError *error;
/// Return cancelled state of request task.
@property (nonatomic, readonly, getter=isCancelled) BOOL cancelled;
/// Executing state of request task.
@property (nonatomic, readonly, getter=isExecuting) BOOL executing;
#pragma mark - Request Configuration
///=============================================================================
/// @name Request Configuration
///=============================================================================
/// Tag can be used to identify request. Default value is 0.
@property (nonatomic) NSInteger tag;
/// The userInfo can be used to store additional info about the request. Default is nil.
@property (nonatomic, strong, nullable) NSDictionary *userInfo;
/// The delegate object of the request. If you choose block style callback you can ignore this.
/// Default is nil.
@property (nonatomic, weak, nullable) id<YTKRequestDelegate> delegate;
/// The success callback. Note if this value is not nil and `requestFinished` delegate method is
/// also implemented, both will be executed but delegate method is first called. This block
/// will be called on the main queue.
@property (nonatomic, copy, nullable) YTKRequestCompletionBlock successCompletionBlock;
/// The failure callback. Note if this value is not nil and `requestFailed` delegate method is
/// also implemented, both will be executed but delegate method is first called. This block
/// will be called on the main queue.
@property (nonatomic, copy, nullable) YTKRequestCompletionBlock failureCompletionBlock;
/// This can be used to add several accessories object. Note if you use `addAccessory` to add accessory
/// this array will be automatically created. Default is nil.
@property (nonatomic, strong, nullable) NSMutableArray<id<YTKRequestAccessory>> *requestAccessories;
/// This can be use to construct HTTP body when needed in POST request. Default is nil.
@property (nonatomic, copy, nullable) AFConstructingBlock constructingBodyBlock;
/// This value is used to perform resumable download request. Default is nil.
///
/// @discussion NSURLSessionDownloadTask is used when this value is not nil.
/// The exist file at the path will be removed before the request starts. If request succeed, file will
/// be saved to this path automatically, otherwise the response will be saved to `responseData`
/// and `responseString`. For this to work, server must support `Range` and response with
/// proper `Last-Modified` and/or `Etag`. See `NSURLSessionDownloadTask` for more detail.
@property (nonatomic, strong, nullable) NSString *resumableDownloadPath;
/// You can use this block to track the download progress. See also `resumableDownloadPath`.
@property (nonatomic, copy, nullable) AFURLSessionTaskProgressBlock resumableDownloadProgressBlock;
/// You can use this block to track the upload progress.
@property (nonatomic, copy, nullable) AFURLSessionTaskProgressBlock uploadProgressBlock;
/// The priority of the request. Default is `YTKRequestPriorityDefault`.
@property (nonatomic) YTKRequestPriority requestPriority;
/// Set completion callbacks
- (void)setCompletionBlockWithSuccess:(nullable YTKRequestCompletionBlock)success
failure:(nullable YTKRequestCompletionBlock)failure;
/// Nil out both success and failure callback blocks.
- (void)clearCompletionBlock;
/// Convenience method to add request accessory. See also `requestAccessories`.
- (void)addAccessory:(id<YTKRequestAccessory>)accessory;
#pragma mark - Request Action
///=============================================================================
/// @name Request Action
///=============================================================================
/// Append self to request queue and start the request.
- (void)start;
/// Remove self from request queue and cancel the request.
- (void)stop;
/// Convenience method to start the request with block callbacks.
- (void)startWithCompletionBlockWithSuccess:(nullable YTKRequestCompletionBlock)success
failure:(nullable YTKRequestCompletionBlock)failure;
#pragma mark - Subclass Override
///=============================================================================
/// @name Subclass Override
///=============================================================================
/// Called on background thread after request succeeded but before switching to main thread. Note if
/// cache is loaded, this method WILL be called on the main thread, just like `requestCompleteFilter`.
- (void)requestCompletePreprocessor;
/// Called on the main thread after request succeeded.
- (void)requestCompleteFilter;
/// Called on background thread after request failed but before switching to main thread. See also
/// `requestCompletePreprocessor`.
- (void)requestFailedPreprocessor;
/// Called on the main thread when request failed.
- (void)requestFailedFilter;
/// The baseURL of request. This should only contain the host part of URL, e.g., http://www.example.com.
/// See also `requestUrl`
- (NSString *)baseUrl;
/// The URL path of request. This should only contain the path part of URL, e.g., /v1/user. See alse `baseUrl`.
///
/// @discussion This will be concated with `baseUrl` using [NSURL URLWithString:relativeToURL].
/// Because of this, it is recommended that the usage should stick to rules stated above.
/// Otherwise the result URL may not be correctly formed. See also `URLString:relativeToURL`
/// for more information.
///
/// Additionally, if `requestUrl` itself is a valid URL, it will be used as the result URL and
/// `baseUrl` will be ignored.
- (NSString *)requestUrl;
/// Optional CDN URL for request.
- (NSString *)cdnUrl;
/// Request timeout interval. Default is 60s.
///
/// @discussion When using `resumableDownloadPath`(NSURLSessionDownloadTask), the session seems to completely ignore
/// `timeoutInterval` property of `NSURLRequest`. One effective way to set timeout would be using
/// `timeoutIntervalForResource` of `NSURLSessionConfiguration`.
- (NSTimeInterval)requestTimeoutInterval;
/// Additional request argument.
- (nullable id)requestArgument;
/// Override this method to filter requests with certain arguments when caching.
- (id)cacheFileNameFilterForRequestArgument:(id)argument;
/// HTTP request method.
- (YTKRequestMethod)requestMethod;
/// Request serializer type.
- (YTKRequestSerializerType)requestSerializerType;
/// Response serializer type. See also `responseObject`.
- (YTKResponseSerializerType)responseSerializerType;
/// Username and password used for HTTP authorization. Should be formed as @[@"Username", @"Password"].
- (nullable NSArray<NSString *> *)requestAuthorizationHeaderFieldArray;
/// Additional HTTP request header field.
- (nullable NSDictionary<NSString *, NSString *> *)requestHeaderFieldValueDictionary;
/// Use this to build custom request. If this method return non-nil value, `requestUrl`, `requestTimeoutInterval`,
/// `requestArgument`, `allowsCellularAccess`, `requestMethod` and `requestSerializerType` will all be ignored.
- (nullable NSURLRequest *)buildCustomUrlRequest;
/// Should use CDN when sending request.
- (BOOL)useCDN;
/// Whether the request is allowed to use the cellular radio (if present). Default is YES.
- (BOOL)allowsCellularAccess;
/// The validator will be used to test if `responseJSONObject` is correctly formed.
- (nullable id)jsonValidator;
/// This validator will be used to test if `responseStatusCode` is valid.
- (BOOL)statusCodeValidator;
@end
NS_ASSUME_NONNULL_END
//
// YTKBaseRequest.m
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 "YTKBaseRequest.h"
#import "YTKNetworkAgent.h"
#import "YTKNetworkPrivate.h"
NSString *const YTKRequestValidationErrorDomain = @"com.yuantiku.request.validation";
@interface YTKBaseRequest ()
@property (nonatomic, strong, readwrite) NSURLSessionTask *requestTask;
@property (nonatomic, strong, readwrite) NSData *responseData;
@property (nonatomic, strong, readwrite) id responseJSONObject;
@property (nonatomic, strong, readwrite) id responseObject;
@property (nonatomic, strong, readwrite) NSString *responseString;
@property (nonatomic, strong, readwrite) NSError *error;
@end
@implementation YTKBaseRequest
#pragma mark - Request and Response Information
- (NSHTTPURLResponse *)response {
return (NSHTTPURLResponse *)self.requestTask.response;
}
- (NSInteger)responseStatusCode {
return self.response.statusCode;
}
- (NSDictionary *)responseHeaders {
return self.response.allHeaderFields;
}
- (NSURLRequest *)currentRequest {
return self.requestTask.currentRequest;
}
- (NSURLRequest *)originalRequest {
return self.requestTask.originalRequest;
}
- (BOOL)isCancelled {
if (!self.requestTask) {
return NO;
}
return self.requestTask.state == NSURLSessionTaskStateCanceling;
}
- (BOOL)isExecuting {
if (!self.requestTask) {
return NO;
}
return self.requestTask.state == NSURLSessionTaskStateRunning;
}
#pragma mark - Request Configuration
- (void)setCompletionBlockWithSuccess:(YTKRequestCompletionBlock)success
failure:(YTKRequestCompletionBlock)failure {
self.successCompletionBlock = success;
self.failureCompletionBlock = failure;
}
- (void)clearCompletionBlock {
// nil out to break the retain cycle.
self.successCompletionBlock = nil;
self.failureCompletionBlock = nil;
self.uploadProgressBlock = nil;
}
- (void)addAccessory:(id<YTKRequestAccessory>)accessory {
if (!self.requestAccessories) {
self.requestAccessories = [NSMutableArray array];
}
[self.requestAccessories addObject:accessory];
}
#pragma mark - Request Action
- (void)start {
[self toggleAccessoriesWillStartCallBack];
[[YTKNetworkAgent sharedAgent] addRequest:self];
}
- (void)stop {
[self toggleAccessoriesWillStopCallBack];
self.delegate = nil;
[[YTKNetworkAgent sharedAgent] cancelRequest:self];
[self toggleAccessoriesDidStopCallBack];
}
- (void)startWithCompletionBlockWithSuccess:(YTKRequestCompletionBlock)success
failure:(YTKRequestCompletionBlock)failure {
[self setCompletionBlockWithSuccess:success failure:failure];
[self start];
}
#pragma mark - Subclass Override
- (void)requestCompletePreprocessor {
}
- (void)requestCompleteFilter {
}
- (void)requestFailedPreprocessor {
}
- (void)requestFailedFilter {
}
- (NSString *)requestUrl {
return @"";
}
- (NSString *)cdnUrl {
return @"";
}
- (NSString *)baseUrl {
return @"";
}
- (NSTimeInterval)requestTimeoutInterval {
return 60;
}
- (id)requestArgument {
return nil;
}
- (id)cacheFileNameFilterForRequestArgument:(id)argument {
return argument;
}
- (YTKRequestMethod)requestMethod {
return YTKRequestMethodGET;
}
- (YTKRequestSerializerType)requestSerializerType {
return YTKRequestSerializerTypeHTTP;
}
- (YTKResponseSerializerType)responseSerializerType {
return YTKResponseSerializerTypeJSON;
}
- (NSArray *)requestAuthorizationHeaderFieldArray {
return nil;
}
- (NSDictionary *)requestHeaderFieldValueDictionary {
return nil;
}
- (NSURLRequest *)buildCustomUrlRequest {
return nil;
}
- (BOOL)useCDN {
return NO;
}
- (BOOL)allowsCellularAccess {
return YES;
}
- (id)jsonValidator {
return nil;
}
- (BOOL)statusCodeValidator {
NSInteger statusCode = [self responseStatusCode];
return (statusCode >= 200 && statusCode <= 299);
}
#pragma mark - NSObject
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p>{ URL: %@ } { method: %@ } { arguments: %@ }", NSStringFromClass([self class]), self, self.currentRequest.URL, self.currentRequest.HTTPMethod, self.requestArgument];
}
@end
//
// YTKBatchRequest.h
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class YTKRequest;
@class YTKBatchRequest;
@protocol YTKRequestAccessory;
/// The YTKBatchRequestDelegate protocol defines several optional methods you can use
/// to receive network-related messages. All the delegate methods will be called
/// on the main queue. Note the delegate methods will be called when all the requests
/// of batch request finishes.
@protocol YTKBatchRequestDelegate <NSObject>
@optional
/// Tell the delegate that the batch request has finished successfully/
///
/// @param batchRequest The corresponding batch request.
- (void)batchRequestFinished:(YTKBatchRequest *)batchRequest;
/// Tell the delegate that the batch request has failed.
///
/// @param batchRequest The corresponding batch request.
- (void)batchRequestFailed:(YTKBatchRequest *)batchRequest;
@end
/// YTKBatchRequest can be used to batch several YTKRequest. Note that when used inside YTKBatchRequest, a single
/// YTKRequest will have its own callback and delegate cleared, in favor of the batch request callback.
@interface YTKBatchRequest : NSObject
/// All the requests are stored in this array.
@property (nonatomic, strong, readonly) NSArray<YTKRequest *> *requestArray;
/// The delegate object of the batch request. Default is nil.
@property (nonatomic, weak, nullable) id<YTKBatchRequestDelegate> delegate;
/// The success callback. Note this will be called only if all the requests are finished.
/// This block will be called on the main queue.
@property (nonatomic, copy, nullable) void (^successCompletionBlock)(YTKBatchRequest *);
/// The failure callback. Note this will be called if one of the requests fails.
/// This block will be called on the main queue.
@property (nonatomic, copy, nullable) void (^failureCompletionBlock)(YTKBatchRequest *);
/// Tag can be used to identify batch request. Default value is 0.
@property (nonatomic) NSInteger tag;
/// This can be used to add several accessories object. Note if you use `addAccessory` to add acceesory
/// this array will be automatically created. Default is nil.
@property (nonatomic, strong, nullable) NSMutableArray<id<YTKRequestAccessory>> *requestAccessories;
/// The first request that failed (and causing the batch request to fail).
@property (nonatomic, strong, readonly, nullable) YTKRequest *failedRequest;
/// Creates a `YTKBatchRequest` with a bunch of requests.
///
/// @param requestArray requests useds to create batch request.
///
- (instancetype)initWithRequestArray:(NSArray<YTKRequest *> *)requestArray;
/// Set completion callbacks
- (void)setCompletionBlockWithSuccess:(nullable void (^)(YTKBatchRequest *batchRequest))success
failure:(nullable void (^)(YTKBatchRequest *batchRequest))failure;
/// Nil out both success and failure callback blocks.
- (void)clearCompletionBlock;
/// Convenience method to add request accessory. See also `requestAccessories`.
- (void)addAccessory:(id<YTKRequestAccessory>)accessory;
/// Append all the requests to queue.
- (void)start;
/// Stop all the requests of the batch request.
- (void)stop;
/// Convenience method to start the batch request with block callbacks.
- (void)startWithCompletionBlockWithSuccess:(nullable void (^)(YTKBatchRequest *batchRequest))success
failure:(nullable void (^)(YTKBatchRequest *batchRequest))failure;
/// Whether all response data is from local cache.
- (BOOL)isDataFromCache;
@end
NS_ASSUME_NONNULL_END
//
// YTKBatchRequest.m
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 "YTKBatchRequest.h"
#import "YTKNetworkPrivate.h"
#import "YTKBatchRequestAgent.h"
#import "YTKRequest.h"
@interface YTKBatchRequest() <YTKRequestDelegate>
@property (nonatomic) NSInteger finishedCount;
@end
@implementation YTKBatchRequest
- (instancetype)initWithRequestArray:(NSArray<YTKRequest *> *)requestArray {
self = [super init];
if (self) {
_requestArray = [requestArray copy];
_finishedCount = 0;
for (YTKRequest * req in _requestArray) {
if (![req isKindOfClass:[YTKRequest class]]) {
YTKLog(@"Error, request item must be YTKRequest instance.");
return nil;
}
}
}
return self;
}
- (void)start {
if (_finishedCount > 0) {
YTKLog(@"Error! Batch request has already started.");
return;
}
_failedRequest = nil;
[[YTKBatchRequestAgent sharedAgent] addBatchRequest:self];
[self toggleAccessoriesWillStartCallBack];
for (YTKRequest * req in _requestArray) {
req.delegate = self;
[req clearCompletionBlock];
[req start];
}
}
- (void)stop {
[self toggleAccessoriesWillStopCallBack];
_delegate = nil;
[self clearRequest];
[self toggleAccessoriesDidStopCallBack];
[[YTKBatchRequestAgent sharedAgent] removeBatchRequest:self];
}
- (void)startWithCompletionBlockWithSuccess:(void (^)(YTKBatchRequest *batchRequest))success
failure:(void (^)(YTKBatchRequest *batchRequest))failure {
[self setCompletionBlockWithSuccess:success failure:failure];
[self start];
}
- (void)setCompletionBlockWithSuccess:(void (^)(YTKBatchRequest *batchRequest))success
failure:(void (^)(YTKBatchRequest *batchRequest))failure {
self.successCompletionBlock = success;
self.failureCompletionBlock = failure;
}
- (void)clearCompletionBlock {
// nil out to break the retain cycle.
self.successCompletionBlock = nil;
self.failureCompletionBlock = nil;
}
- (BOOL)isDataFromCache {
BOOL result = YES;
for (YTKRequest *request in _requestArray) {
if (!request.isDataFromCache) {
result = NO;
}
}
return result;
}
- (void)dealloc {
[self clearRequest];
}
#pragma mark - Network Request Delegate
- (void)requestFinished:(YTKRequest *)request {
_finishedCount++;
if (_finishedCount == _requestArray.count) {
[self toggleAccessoriesWillStopCallBack];
if ([_delegate respondsToSelector:@selector(batchRequestFinished:)]) {
[_delegate batchRequestFinished:self];
}
if (_successCompletionBlock) {
_successCompletionBlock(self);
}
[self clearCompletionBlock];
[self toggleAccessoriesDidStopCallBack];
[[YTKBatchRequestAgent sharedAgent] removeBatchRequest:self];
}
}
- (void)requestFailed:(YTKRequest *)request {
_failedRequest = request;
[self toggleAccessoriesWillStopCallBack];
// Stop
for (YTKRequest *req in _requestArray) {
[req stop];
}
// Callback
if ([_delegate respondsToSelector:@selector(batchRequestFailed:)]) {
[_delegate batchRequestFailed:self];
}
if (_failureCompletionBlock) {
_failureCompletionBlock(self);
}
// Clear
[self clearCompletionBlock];
[self toggleAccessoriesDidStopCallBack];
[[YTKBatchRequestAgent sharedAgent] removeBatchRequest:self];
}
- (void)clearRequest {
for (YTKRequest * req in _requestArray) {
[req stop];
}
[self clearCompletionBlock];
}
#pragma mark - Request Accessoies
- (void)addAccessory:(id<YTKRequestAccessory>)accessory {
if (!self.requestAccessories) {
self.requestAccessories = [NSMutableArray array];
}
[self.requestAccessories addObject:accessory];
}
@end
//
// YTKBatchRequestAgent.h
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class YTKBatchRequest;
/// YTKBatchRequestAgent handles batch request management. It keeps track of all
/// the batch requests.
@interface YTKBatchRequestAgent : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/// Get the shared batch request agent.
+ (YTKBatchRequestAgent *)sharedAgent;
/// Add a batch request.
- (void)addBatchRequest:(YTKBatchRequest *)request;
/// Remove a previously added batch request.
- (void)removeBatchRequest:(YTKBatchRequest *)request;
@end
NS_ASSUME_NONNULL_END
//
// YTKBatchRequestAgent.m
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 "YTKBatchRequestAgent.h"
#import "YTKBatchRequest.h"
@interface YTKBatchRequestAgent()
@property (strong, nonatomic) NSMutableArray<YTKBatchRequest *> *requestArray;
@end
@implementation YTKBatchRequestAgent
+ (YTKBatchRequestAgent *)sharedAgent {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_requestArray = [NSMutableArray array];
}
return self;
}
- (void)addBatchRequest:(YTKBatchRequest *)request {
@synchronized(self) {
[_requestArray addObject:request];
}
}
- (void)removeBatchRequest:(YTKBatchRequest *)request {
@synchronized(self) {
[_requestArray removeObject:request];
}
}
@end
//
// YTKChainRequest.h
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class YTKChainRequest;
@class YTKBaseRequest;
@protocol YTKRequestAccessory;
/// The YTKChainRequestDelegate protocol defines several optional methods you can use
/// to receive network-related messages. All the delegate methods will be called
/// on the main queue. Note the delegate methods will be called when all the requests
/// of chain request finishes.
@protocol YTKChainRequestDelegate <NSObject>
@optional
/// Tell the delegate that the chain request has finished successfully.
///
/// @param chainRequest The corresponding chain request.
- (void)chainRequestFinished:(YTKChainRequest *)chainRequest;
/// Tell the delegate that the chain request has failed.
///
/// @param chainRequest The corresponding chain request.
/// @param request First failed request that causes the whole request to fail.
- (void)chainRequestFailed:(YTKChainRequest *)chainRequest failedBaseRequest:(YTKBaseRequest*)request;
@end
typedef void (^YTKChainCallback)(YTKChainRequest *chainRequest, YTKBaseRequest *baseRequest);
/// YTKBatchRequest can be used to chain several YTKRequest so that one will only starts after another finishes.
/// Note that when used inside YTKChainRequest, a single YTKRequest will have its own callback and delegate
/// cleared, in favor of the batch request callback.
@interface YTKChainRequest : NSObject
/// All the requests are stored in this array.
- (NSArray<YTKBaseRequest *> *)requestArray;
/// The delegate object of the chain request. Default is nil.
@property (nonatomic, weak, nullable) id<YTKChainRequestDelegate> delegate;
/// This can be used to add several accessories object. Note if you use `addAccessory` to add acceesory
/// this array will be automatically created. Default is nil.
@property (nonatomic, strong, nullable) NSMutableArray<id<YTKRequestAccessory>> *requestAccessories;
/// Convenience method to add request accessory. See also `requestAccessories`.
- (void)addAccessory:(id<YTKRequestAccessory>)accessory;
/// Start the chain request, adding first request in the chain to request queue.
- (void)start;
/// Stop the chain request. Remaining request in chain will be cancelled.
- (void)stop;
/// Add request to request chain.
///
/// @param request The request to be chained.
/// @param callback The finish callback
- (void)addRequest:(YTKBaseRequest *)request callback:(nullable YTKChainCallback)callback;
@end
NS_ASSUME_NONNULL_END
//
// YTKChainRequest.m
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 "YTKChainRequest.h"
#import "YTKChainRequestAgent.h"
#import "YTKNetworkPrivate.h"
#import "YTKBaseRequest.h"
@interface YTKChainRequest()<YTKRequestDelegate>
@property (strong, nonatomic) NSMutableArray<YTKBaseRequest *> *requestArray;
@property (strong, nonatomic) NSMutableArray<YTKChainCallback> *requestCallbackArray;
@property (assign, nonatomic) NSUInteger nextRequestIndex;
@property (strong, nonatomic) YTKChainCallback emptyCallback;
@end
@implementation YTKChainRequest
- (instancetype)init {
self = [super init];
if (self) {
_nextRequestIndex = 0;
_requestArray = [NSMutableArray array];
_requestCallbackArray = [NSMutableArray array];
_emptyCallback = ^(YTKChainRequest *chainRequest, YTKBaseRequest *baseRequest) {
// do nothing
};
}
return self;
}
- (void)start {
if (_nextRequestIndex > 0) {
YTKLog(@"Error! Chain request has already started.");
return;
}
if ([_requestArray count] > 0) {
[self toggleAccessoriesWillStartCallBack];
[self startNextRequest];
[[YTKChainRequestAgent sharedAgent] addChainRequest:self];
} else {
YTKLog(@"Error! Chain request array is empty.");
}
}
- (void)stop {
[self toggleAccessoriesWillStopCallBack];
[self clearRequest];
[[YTKChainRequestAgent sharedAgent] removeChainRequest:self];
[self toggleAccessoriesDidStopCallBack];
}
- (void)addRequest:(YTKBaseRequest *)request callback:(YTKChainCallback)callback {
[_requestArray addObject:request];
if (callback != nil) {
[_requestCallbackArray addObject:callback];
} else {
[_requestCallbackArray addObject:_emptyCallback];
}
}
- (NSArray<YTKBaseRequest *> *)requestArray {
return _requestArray;
}
- (BOOL)startNextRequest {
if (_nextRequestIndex < [_requestArray count]) {
YTKBaseRequest *request = _requestArray[_nextRequestIndex];
_nextRequestIndex++;
request.delegate = self;
[request clearCompletionBlock];
[request start];
return YES;
} else {
return NO;
}
}
#pragma mark - Network Request Delegate
- (void)requestFinished:(YTKBaseRequest *)request {
NSUInteger currentRequestIndex = _nextRequestIndex - 1;
YTKChainCallback callback = _requestCallbackArray[currentRequestIndex];
callback(self, request);
if (![self startNextRequest]) {
[self toggleAccessoriesWillStopCallBack];
if ([_delegate respondsToSelector:@selector(chainRequestFinished:)]) {
[_delegate chainRequestFinished:self];
[[YTKChainRequestAgent sharedAgent] removeChainRequest:self];
}
[self toggleAccessoriesDidStopCallBack];
}
}
- (void)requestFailed:(YTKBaseRequest *)request {
[self toggleAccessoriesWillStopCallBack];
if ([_delegate respondsToSelector:@selector(chainRequestFailed:failedBaseRequest:)]) {
[_delegate chainRequestFailed:self failedBaseRequest:request];
[[YTKChainRequestAgent sharedAgent] removeChainRequest:self];
}
[self toggleAccessoriesDidStopCallBack];
}
- (void)clearRequest {
NSUInteger currentRequestIndex = _nextRequestIndex - 1;
if (currentRequestIndex < [_requestArray count]) {
YTKBaseRequest *request = _requestArray[currentRequestIndex];
[request stop];
}
[_requestArray removeAllObjects];
[_requestCallbackArray removeAllObjects];
}
#pragma mark - Request Accessoies
- (void)addAccessory:(id<YTKRequestAccessory>)accessory {
if (!self.requestAccessories) {
self.requestAccessories = [NSMutableArray array];
}
[self.requestAccessories addObject:accessory];
}
@end
//
// YTKChainRequestAgent.h
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class YTKChainRequest;
/// YTKChainRequestAgent handles chain request management. It keeps track of all
/// the chain requests.
@interface YTKChainRequestAgent : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/// Get the shared chain request agent.
+ (YTKChainRequestAgent *)sharedAgent;
/// Add a chain request.
- (void)addChainRequest:(YTKChainRequest *)request;
/// Remove a previously added chain request.
- (void)removeChainRequest:(YTKChainRequest *)request;
@end
NS_ASSUME_NONNULL_END
//
// YTKChainRequestAgent.m
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 "YTKChainRequestAgent.h"
#import "YTKChainRequest.h"
@interface YTKChainRequestAgent()
@property (strong, nonatomic) NSMutableArray<YTKChainRequest *> *requestArray;
@end
@implementation YTKChainRequestAgent
+ (YTKChainRequestAgent *)sharedAgent {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_requestArray = [NSMutableArray array];
}
return self;
}
- (void)addChainRequest:(YTKChainRequest *)request {
@synchronized(self) {
[_requestArray addObject:request];
}
}
- (void)removeChainRequest:(YTKChainRequest *)request {
@synchronized(self) {
[_requestArray removeObject:request];
}
}
@end
//
// YTKNetwork.h
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 <Foundation/Foundation.h>
#ifndef _YTKNETWORK_
#define _YTKNETWORK_
#if __has_include(<YTKNetwork/YTKNetwork.h>)
FOUNDATION_EXPORT double YTKNetworkVersionNumber;
FOUNDATION_EXPORT const unsigned char YTKNetworkVersionString[];
#import <YTKNetwork/YTKRequest.h>
#import <YTKNetwork/YTKBaseRequest.h>
#import <YTKNetwork/YTKNetworkAgent.h>
#import <YTKNetwork/YTKBatchRequest.h>
#import <YTKNetwork/YTKBatchRequestAgent.h>
#import <YTKNetwork/YTKChainRequest.h>
#import <YTKNetwork/YTKChainRequestAgent.h>
#import <YTKNetwork/YTKNetworkConfig.h>
#import <YTKNetwork/YTKRequestEventAccessory.h>
#else
#import "YTKRequest.h"
#import "YTKBaseRequest.h"
#import "YTKNetworkAgent.h"
#import "YTKBatchRequest.h"
#import "YTKBatchRequestAgent.h"
#import "YTKChainRequest.h"
#import "YTKChainRequestAgent.h"
#import "YTKNetworkConfig.h"
#import "YTKRequestEventAccessory.h"
#endif /* __has_include */
#endif /* _YTKNETWORK_ */
//
// YTKNetworkAgent.h
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class YTKBaseRequest;
/// YTKNetworkAgent is the underlying class that handles actual request generation,
/// serialization and response handling.
@interface YTKNetworkAgent : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/// Get the shared agent.
+ (YTKNetworkAgent *)sharedAgent;
/// Add request to session and start it.
- (void)addRequest:(YTKBaseRequest *)request;
/// Cancel a request that was previously added.
- (void)cancelRequest:(YTKBaseRequest *)request;
/// Cancel all requests that were previously added.
- (void)cancelAllRequests;
/// Return the constructed URL of request.
///
/// @param request The request to parse. Should not be nil.
///
/// @return The result URL.
- (NSString *)buildRequestUrl:(YTKBaseRequest *)request;
@end
NS_ASSUME_NONNULL_END
//
// YTKNetworkAgent.m
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 "YTKNetworkAgent.h"
#import "YTKNetworkConfig.h"
#import "YTKNetworkPrivate.h"
#import <pthread/pthread.h>
#if __has_include(<AFNetworking/AFHTTPSessionManager.h>)
#import <AFNetworking/AFHTTPSessionManager.h>
#else
#import <AFNetworking/AFHTTPSessionManager.h>
#endif
#define Lock() pthread_mutex_lock(&_lock)
#define Unlock() pthread_mutex_unlock(&_lock)
#define kYTKNetworkIncompleteDownloadFolderName @"Incomplete"
@implementation YTKNetworkAgent {
AFHTTPSessionManager *_manager;
YTKNetworkConfig *_config;
AFJSONResponseSerializer *_jsonResponseSerializer;
AFXMLParserResponseSerializer *_xmlParserResponseSerialzier;
NSMutableDictionary<NSNumber *, YTKBaseRequest *> *_requestsRecord;
dispatch_queue_t _processingQueue;
pthread_mutex_t _lock;
NSIndexSet *_allStatusCodes;
}
+ (YTKNetworkAgent *)sharedAgent {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_config = [YTKNetworkConfig sharedConfig];
_manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:_config.sessionConfiguration];
_requestsRecord = [NSMutableDictionary dictionary];
_processingQueue = dispatch_queue_create("com.yuantiku.networkagent.processing", DISPATCH_QUEUE_CONCURRENT);
_allStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(100, 500)];
pthread_mutex_init(&_lock, NULL);
_manager.securityPolicy = _config.securityPolicy;
_manager.responseSerializer = [AFHTTPResponseSerializer serializer];
// Take over the status code validation
_manager.responseSerializer.acceptableStatusCodes = _allStatusCodes;
_manager.completionQueue = _processingQueue;
[_manager setTaskDidFinishCollectingMetricsBlock:_config.collectingMetricsBlock];
}
return self;
}
- (AFJSONResponseSerializer *)jsonResponseSerializer {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_jsonResponseSerializer = [AFJSONResponseSerializer serializer];
_jsonResponseSerializer.acceptableStatusCodes = _allStatusCodes;
});
return _jsonResponseSerializer;
}
- (AFXMLParserResponseSerializer *)xmlParserResponseSerialzier {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_xmlParserResponseSerialzier = [AFXMLParserResponseSerializer serializer];
_xmlParserResponseSerialzier.acceptableStatusCodes = _allStatusCodes;
});
return _xmlParserResponseSerialzier;
}
#pragma mark -
- (NSString *)buildRequestUrl:(YTKBaseRequest *)request {
NSParameterAssert(request != nil);
NSString *detailUrl = [request requestUrl];
NSURL *temp = [NSURL URLWithString:detailUrl];
// If detailUrl is valid URL
if (temp && temp.host && temp.scheme) {
return detailUrl;
}
// Filter URL if needed
NSArray *filters = [_config urlFilters];
for (id<YTKUrlFilterProtocol> f in filters) {
detailUrl = [f filterUrl:detailUrl withRequest:request];
}
NSString *baseUrl;
if ([request useCDN]) {
if ([request cdnUrl].length > 0) {
baseUrl = [request cdnUrl];
} else {
baseUrl = [_config cdnUrl];
}
} else {
if ([request baseUrl].length > 0) {
baseUrl = [request baseUrl];
} else {
baseUrl = [_config baseUrl];
}
}
// URL slash compatibility
NSURL *url = [NSURL URLWithString:baseUrl];
if (baseUrl.length > 0 && ![baseUrl hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
}
return [NSURL URLWithString:detailUrl relativeToURL:url].absoluteString;
}
- (AFHTTPRequestSerializer *)requestSerializerForRequest:(YTKBaseRequest *)request {
AFHTTPRequestSerializer *requestSerializer = nil;
if (request.requestSerializerType == YTKRequestSerializerTypeHTTP) {
requestSerializer = [AFHTTPRequestSerializer serializer];
} else if (request.requestSerializerType == YTKRequestSerializerTypeJSON) {
requestSerializer = [AFJSONRequestSerializer serializer];
}
requestSerializer.timeoutInterval = [request requestTimeoutInterval];
requestSerializer.allowsCellularAccess = [request allowsCellularAccess];
// If api needs server username and password
NSArray<NSString *> *authorizationHeaderFieldArray = [request requestAuthorizationHeaderFieldArray];
if (authorizationHeaderFieldArray != nil) {
[requestSerializer setAuthorizationHeaderFieldWithUsername:authorizationHeaderFieldArray.firstObject
password:authorizationHeaderFieldArray.lastObject];
}
// If api needs to add custom value to HTTPHeaderField
NSDictionary<NSString *, NSString *> *headerFieldValueDictionary = [request requestHeaderFieldValueDictionary];
if (headerFieldValueDictionary != nil) {
for (NSString *httpHeaderField in headerFieldValueDictionary.allKeys) {
NSString *value = headerFieldValueDictionary[httpHeaderField];
[requestSerializer setValue:value forHTTPHeaderField:httpHeaderField];
}
}
return requestSerializer;
}
- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
YTKRequestMethod method = [request requestMethod];
NSString *url = [self buildRequestUrl:request];
id param = request.requestArgument;
AFConstructingBlock constructingBlock = [request constructingBodyBlock];
AFURLSessionTaskProgressBlock uploadProgressBlock = [request uploadProgressBlock];
AFHTTPRequestSerializer *requestSerializer = [self requestSerializerForRequest:request];
switch (method) {
case YTKRequestMethodGET:
if (request.resumableDownloadPath) {
return [self downloadTaskWithDownloadPath:request.resumableDownloadPath
requestSerializer:requestSerializer
URLString:url
parameters:param
progress:request.resumableDownloadProgressBlock
error:error];
} else {
return [self dataTaskWithHTTPMethod:@"GET"
requestSerializer:requestSerializer
URLString:url
parameters:param
error:error];
}
case YTKRequestMethodPOST:
return [self dataTaskWithHTTPMethod:@"POST"
requestSerializer:requestSerializer
URLString:url
parameters:param
uploadProgress:uploadProgressBlock
constructingBodyWithBlock:constructingBlock
error:error];
case YTKRequestMethodHEAD:
return [self dataTaskWithHTTPMethod:@"HEAD"
requestSerializer:requestSerializer
URLString:url
parameters:param
error:error];
case YTKRequestMethodPUT:
return [self dataTaskWithHTTPMethod:@"PUT"
requestSerializer:requestSerializer
URLString:url
parameters:param
uploadProgress:uploadProgressBlock
constructingBodyWithBlock:constructingBlock
error:error];
case YTKRequestMethodDELETE:
return [self dataTaskWithHTTPMethod:@"DELETE"
requestSerializer:requestSerializer
URLString:url
parameters:param
error:error];
case YTKRequestMethodPATCH:
return [self dataTaskWithHTTPMethod:@"PATCH"
requestSerializer:requestSerializer
URLString:url
parameters:param
error:error];
}
}
- (void)addRequest:(YTKBaseRequest *)request {
NSParameterAssert(request != nil);
NSError * __autoreleasing requestSerializationError = nil;
NSURLRequest *customUrlRequest= [request buildCustomUrlRequest];
if (customUrlRequest) {
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [_manager dataTaskWithRequest:customUrlRequest
uploadProgress:nil
downloadProgress:nil
completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
[self handleRequestResult:dataTask responseObject:responseObject error:error];
}];
request.requestTask = dataTask;
} else {
request.requestTask = [self sessionTaskForRequest:request error:&requestSerializationError];
}
if (requestSerializationError) {
[self requestDidFailWithRequest:request error:requestSerializationError];
return;
}
NSAssert(request.requestTask != nil, @"requestTask should not be nil");
// Set request task priority
if ([request.requestTask respondsToSelector:@selector(priority)]) {
switch (request.requestPriority) {
case YTKRequestPriorityHigh:
request.requestTask.priority = NSURLSessionTaskPriorityHigh;
break;
case YTKRequestPriorityLow:
request.requestTask.priority = NSURLSessionTaskPriorityLow;
break;
case YTKRequestPriorityDefault:
/*!!fall through*/
default:
request.requestTask.priority = NSURLSessionTaskPriorityDefault;
break;
}
}
// Retain request
YTKLog(@"Add request: %@", NSStringFromClass([request class]));
[self addRequestToRecord:request];
[request.requestTask resume];
}
- (void)cancelRequest:(YTKBaseRequest *)request {
NSParameterAssert(request != nil);
if (request.resumableDownloadPath && [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath] != nil) {
NSURLSessionDownloadTask *requestTask = (NSURLSessionDownloadTask *)request.requestTask;
[requestTask cancelByProducingResumeData:^(NSData *resumeData) {
NSURL *localUrl = [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath];
[resumeData writeToURL:localUrl atomically:YES];
}];
} else {
[request.requestTask cancel];
}
[self removeRequestFromRecord:request];
[request clearCompletionBlock];
}
- (void)cancelAllRequests {
Lock();
NSArray *allKeys = [_requestsRecord allKeys];
Unlock();
if (allKeys && allKeys.count > 0) {
NSArray *copiedKeys = [allKeys copy];
for (NSNumber *key in copiedKeys) {
Lock();
YTKBaseRequest *request = _requestsRecord[key];
Unlock();
// We are using non-recursive lock.
// Do not lock `stop`, otherwise deadlock may occur.
[request stop];
}
}
}
- (BOOL)validateResult:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
BOOL result = [request statusCodeValidator];
if (!result) {
if (error) {
NSString *desc = [NSString stringWithFormat:@"Invalid status code (%ld)", (long)[request responseStatusCode]];
*error = [NSError errorWithDomain:YTKRequestValidationErrorDomain code:YTKRequestValidationErrorInvalidStatusCode userInfo:@{NSLocalizedDescriptionKey:desc}];
}
return result;
}
id json = [request responseJSONObject];
id validator = [request jsonValidator];
if (json && validator) {
result = [YTKNetworkUtils validateJSON:json withValidator:validator];
if (!result) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestValidationErrorDomain code:YTKRequestValidationErrorInvalidJSONFormat userInfo:@{NSLocalizedDescriptionKey:@"Invalid JSON format"}];
}
return result;
}
}
return YES;
}
- (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error {
Lock();
YTKBaseRequest *request = _requestsRecord[@(task.taskIdentifier)];
Unlock();
// When the request is cancelled and removed from records, the underlying
// AFNetworking failure callback will still kicks in, resulting in a nil `request`.
//
// Here we choose to completely ignore cancelled tasks. Neither success or failure
// callback will be called.
if (!request) {
return;
}
YTKLog(@"Finished Request: %@", NSStringFromClass([request class]));
NSError * __autoreleasing serializationError = nil;
NSError * __autoreleasing validationError = nil;
NSError *requestError = nil;
BOOL succeed = NO;
request.responseObject = responseObject;
if ([request.responseObject isKindOfClass:[NSData class]]) {
request.responseData = responseObject;
request.responseString = [[NSString alloc] initWithData:responseObject encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];
switch (request.responseSerializerType) {
case YTKResponseSerializerTypeHTTP:
// Default serializer. Do nothing.
break;
case YTKResponseSerializerTypeJSON:
request.responseObject = [self.jsonResponseSerializer responseObjectForResponse:task.response data:request.responseData error:&serializationError];
request.responseJSONObject = request.responseObject;
break;
case YTKResponseSerializerTypeXMLParser:
request.responseObject = [self.xmlParserResponseSerialzier responseObjectForResponse:task.response data:request.responseData error:&serializationError];
break;
}
}
if (error) {
succeed = NO;
requestError = error;
} else if (serializationError) {
succeed = NO;
requestError = serializationError;
} else {
succeed = [self validateResult:request error:&validationError];
requestError = validationError;
}
if (succeed) {
[self requestDidSucceedWithRequest:request];
} else {
[self requestDidFailWithRequest:request error:requestError];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self removeRequestFromRecord:request];
[request clearCompletionBlock];
});
}
- (void)requestDidSucceedWithRequest:(YTKBaseRequest *)request {
@autoreleasepool {
[request requestCompletePreprocessor];
}
dispatch_async(dispatch_get_main_queue(), ^{
[request toggleAccessoriesWillStopCallBack];
[request requestCompleteFilter];
if (request.delegate != nil) {
[request.delegate requestFinished:request];
}
if (request.successCompletionBlock) {
request.successCompletionBlock(request);
}
[request toggleAccessoriesDidStopCallBack];
});
}
- (void)requestDidFailWithRequest:(YTKBaseRequest *)request error:(NSError *)error {
request.error = error;
YTKLog(@"Request %@ failed, status code = %ld, error = %@",
NSStringFromClass([request class]), (long)request.responseStatusCode, error.localizedDescription);
// Save incomplete download data.
NSData *incompleteDownloadData = error.userInfo[NSURLSessionDownloadTaskResumeData];
NSURL *localUrl = nil;
if (request.resumableDownloadPath) {
localUrl = [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath];
}
if (incompleteDownloadData && localUrl != nil) {
[incompleteDownloadData writeToURL:localUrl atomically:YES];
}
// Load response from file and clean up if download task failed.
if ([request.responseObject isKindOfClass:[NSURL class]]) {
NSURL *url = request.responseObject;
if (url.isFileURL && [[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
request.responseData = [NSData dataWithContentsOfURL:url];
request.responseString = [[NSString alloc] initWithData:request.responseData encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];
[[NSFileManager defaultManager] removeItemAtURL:url error:nil];
}
request.responseObject = nil;
}
@autoreleasepool {
[request requestFailedPreprocessor];
}
dispatch_async(dispatch_get_main_queue(), ^{
[request toggleAccessoriesWillStopCallBack];
[request requestFailedFilter];
if (request.delegate != nil) {
[request.delegate requestFailed:request];
}
if (request.failureCompletionBlock) {
request.failureCompletionBlock(request);
}
[request toggleAccessoriesDidStopCallBack];
});
}
- (void)addRequestToRecord:(YTKBaseRequest *)request {
Lock();
_requestsRecord[@(request.requestTask.taskIdentifier)] = request;
Unlock();
}
- (void)removeRequestFromRecord:(YTKBaseRequest *)request {
Lock();
[_requestsRecord removeObjectForKey:@(request.requestTask.taskIdentifier)];
YTKLog(@"Request queue size = %zd", [_requestsRecord count]);
Unlock();
}
#pragma mark -
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError * _Nullable __autoreleasing *)error {
return [self dataTaskWithHTTPMethod:method
requestSerializer:requestSerializer
URLString:URLString
parameters:parameters
uploadProgress:nil
constructingBodyWithBlock:nil
error:error];
}
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(AFURLSessionTaskProgressBlock)uploadProgress
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
error:(NSError * _Nullable __autoreleasing *)error {
NSMutableURLRequest *request = nil;
if (block) {
request = [requestSerializer multipartFormRequestWithMethod:method URLString:URLString parameters:parameters constructingBodyWithBlock:block error:error];
} else {
request = [requestSerializer requestWithMethod:method URLString:URLString parameters:parameters error:error];
}
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [_manager dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:nil
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *_error) {
[self handleRequestResult:dataTask responseObject:responseObject error:_error];
}];
return dataTask;
}
- (NSURLSessionDownloadTask *)downloadTaskWithDownloadPath:(NSString *)downloadPath
requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
URLString:(NSString *)URLString
parameters:(id)parameters
progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
error:(NSError * _Nullable __autoreleasing *)error {
// add parameters to URL;
NSMutableURLRequest *urlRequest = [requestSerializer requestWithMethod:@"GET" URLString:URLString parameters:parameters error:error];
NSString *downloadTargetPath;
BOOL isDirectory;
if (![[NSFileManager defaultManager] fileExistsAtPath:downloadPath isDirectory:&isDirectory]) {
isDirectory = NO;
}
// If targetPath is a directory, use the file name we got from the urlRequest.
// Make sure downloadTargetPath is always a file, not directory.
if (isDirectory) {
NSString *fileName = [urlRequest.URL lastPathComponent];
downloadTargetPath = [NSString pathWithComponents:@[downloadPath, fileName]];
} else {
downloadTargetPath = downloadPath;
}
// AFN use `moveItemAtURL` to move downloaded file to target path,
// this method aborts the move attempt if a file already exist at the path.
// So we remove the exist file before we start the download task.
// https://github.com/AFNetworking/AFNetworking/issues/3775
if ([[NSFileManager defaultManager] fileExistsAtPath:downloadTargetPath]) {
[[NSFileManager defaultManager] removeItemAtPath:downloadTargetPath error:nil];
}
BOOL resumeSucceeded = NO;
__block NSURLSessionDownloadTask *downloadTask = nil;
NSURL *localUrl = [self incompleteDownloadTempPathForDownloadPath:downloadPath];
if (localUrl != nil) {
BOOL resumeDataFileExists = [[NSFileManager defaultManager] fileExistsAtPath:localUrl.path];
NSData *data = [NSData dataWithContentsOfURL:localUrl];
BOOL resumeDataIsValid = [YTKNetworkUtils validateResumeData:data];
BOOL canBeResumed = resumeDataFileExists && resumeDataIsValid;
// Try to resume with resumeData.
// Even though we try to validate the resumeData, this may still fail and raise excecption.
if (canBeResumed) {
@try {
downloadTask = [_manager downloadTaskWithResumeData:data progress:downloadProgressBlock destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
return [NSURL fileURLWithPath:downloadTargetPath isDirectory:NO];
} completionHandler:
^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
[self handleRequestResult:downloadTask responseObject:filePath error:error];
}];
resumeSucceeded = YES;
} @catch (NSException *exception) {
YTKLog(@"Resume download failed, reason = %@", exception.reason);
resumeSucceeded = NO;
}
}
}
if (!resumeSucceeded) {
downloadTask = [_manager downloadTaskWithRequest:urlRequest progress:downloadProgressBlock destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
return [NSURL fileURLWithPath:downloadTargetPath isDirectory:NO];
} completionHandler:
^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
[self handleRequestResult:downloadTask responseObject:filePath error:error];
}];
}
return downloadTask;
}
#pragma mark - Resumable Download
- (NSString *)incompleteDownloadTempCacheFolder {
NSFileManager *fileManager = [NSFileManager new];
NSString *cacheFolder = [NSTemporaryDirectory() stringByAppendingPathComponent:kYTKNetworkIncompleteDownloadFolderName];
BOOL isDirectory = NO;
if ([fileManager fileExistsAtPath:cacheFolder isDirectory:&isDirectory] && isDirectory) {
return cacheFolder;
}
NSError *error = nil;
if ([fileManager createDirectoryAtPath:cacheFolder withIntermediateDirectories:YES attributes:nil error:&error] && error == nil) {
return cacheFolder;
}
YTKLog(@"Failed to create cache directory at %@ with error: %@", cacheFolder, error != nil ? error.localizedDescription : @"unkown");
return nil;
}
- (NSURL *)incompleteDownloadTempPathForDownloadPath:(NSString *)downloadPath {
if (downloadPath == nil || downloadPath.length == 0) {
return nil;
}
NSString *tempPath = nil;
NSString *md5URLString = [YTKNetworkUtils md5StringFromString:downloadPath];
tempPath = [[self incompleteDownloadTempCacheFolder] stringByAppendingPathComponent:md5URLString];
return tempPath == nil ? nil : [NSURL fileURLWithPath:tempPath];
}
#pragma mark - Testing
- (AFHTTPSessionManager *)manager {
return _manager;
}
- (void)resetURLSessionManager {
_manager = [AFHTTPSessionManager manager];
}
- (void)resetURLSessionManagerWithConfiguration:(NSURLSessionConfiguration *)configuration {
_manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
}
@end
//
// YTKNetworkConfig.h
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class YTKBaseRequest;
@class AFSecurityPolicy;
typedef void (^AFURLSessionTaskDidFinishCollectingMetricsBlock)(NSURLSession *session, NSURLSessionTask *task, NSURLSessionTaskMetrics * metrics) API_AVAILABLE(ios(10), macosx(10.12), watchos(3), tvos(10));
/// YTKUrlFilterProtocol can be used to append common parameters to requests before sending them.
@protocol YTKUrlFilterProtocol <NSObject>
/// Preprocess request URL before actually sending them.
///
/// @param originUrl request's origin URL, which is returned by `requestUrl`
/// @param request request itself
///
/// @return A new url which will be used as a new `requestUrl`
- (NSString *)filterUrl:(NSString *)originUrl withRequest:(YTKBaseRequest *)request;
@end
/// YTKCacheDirPathFilterProtocol can be used to append common path components when caching response results
@protocol YTKCacheDirPathFilterProtocol <NSObject>
/// Preprocess cache path before actually saving them.
///
/// @param originPath original base cache path, which is generated in `YTKRequest` class.
/// @param request request itself
///
/// @return A new path which will be used as base path when caching.
- (NSString *)filterCacheDirPath:(NSString *)originPath withRequest:(YTKBaseRequest *)request;
@end
/// YTKNetworkConfig stored global network-related configurations, which will be used in `YTKNetworkAgent`
/// to form and filter requests, as well as caching response.
@interface YTKNetworkConfig : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/// Return a shared config object.
+ (YTKNetworkConfig *)sharedConfig;
/// Request base URL, such as "http://www.yuantiku.com". Default is empty string.
@property (nonatomic, strong) NSString *baseUrl;
/// Request CDN URL. Default is empty string.
@property (nonatomic, strong) NSString *cdnUrl;
/// URL filters. See also `YTKUrlFilterProtocol`.
@property (nonatomic, strong, readonly) NSArray<id<YTKUrlFilterProtocol>> *urlFilters;
/// Cache path filters. See also `YTKCacheDirPathFilterProtocol`.
@property (nonatomic, strong, readonly) NSArray<id<YTKCacheDirPathFilterProtocol>> *cacheDirPathFilters;
/// Security policy will be used by AFNetworking. See also `AFSecurityPolicy`.
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
/// Whether to log debug info. Default is NO;
@property (nonatomic) BOOL debugLogEnabled;
/// SessionConfiguration will be used to initialize AFHTTPSessionManager. Default is nil.
@property (nonatomic, strong, nullable) NSURLSessionConfiguration* sessionConfiguration;
/// NSURLSessionTaskMetrics
@property (nonatomic, strong) AFURLSessionTaskDidFinishCollectingMetricsBlock collectingMetricsBlock API_AVAILABLE(ios(10), macosx(10.12), watchos(3), tvos(10));
/// Add a new URL filter.
- (void)addUrlFilter:(id<YTKUrlFilterProtocol>)filter;
/// Remove all URL filters.
- (void)clearUrlFilter;
/// Add a new cache path filter
- (void)addCacheDirPathFilter:(id<YTKCacheDirPathFilterProtocol>)filter;
/// Clear all cache path filters.
- (void)clearCacheDirPathFilter;
@end
NS_ASSUME_NONNULL_END
//
// YTKNetworkConfig.m
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 "YTKNetworkConfig.h"
#import "YTKBaseRequest.h"
#if __has_include(<AFNetworking/AFSecurityPolicy.h>)
#import <AFNetworking/AFSecurityPolicy.h>
#else
#import <AFNetworking/AFSecurityPolicy.h>
#endif
@implementation YTKNetworkConfig {
NSMutableArray<id<YTKUrlFilterProtocol>> *_urlFilters;
NSMutableArray<id<YTKCacheDirPathFilterProtocol>> *_cacheDirPathFilters;
}
+ (YTKNetworkConfig *)sharedConfig {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_baseUrl = @"";
_cdnUrl = @"";
_urlFilters = [NSMutableArray array];
_cacheDirPathFilters = [NSMutableArray array];
_securityPolicy = [AFSecurityPolicy defaultPolicy];
_debugLogEnabled = NO;
}
return self;
}
- (void)addUrlFilter:(id<YTKUrlFilterProtocol>)filter {
[_urlFilters addObject:filter];
}
- (void)clearUrlFilter {
[_urlFilters removeAllObjects];
}
- (void)addCacheDirPathFilter:(id<YTKCacheDirPathFilterProtocol>)filter {
[_cacheDirPathFilters addObject:filter];
}
- (void)clearCacheDirPathFilter {
[_cacheDirPathFilters removeAllObjects];
}
- (NSArray<id<YTKUrlFilterProtocol>> *)urlFilters {
return [_urlFilters copy];
}
- (NSArray<id<YTKCacheDirPathFilterProtocol>> *)cacheDirPathFilters {
return [_cacheDirPathFilters copy];
}
#pragma mark - NSObject
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p>{ baseURL: %@ } { cdnURL: %@ }", NSStringFromClass([self class]), self, self.baseUrl, self.cdnUrl];
}
@end
//
// YTKNetworkPrivate.h
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 <Foundation/Foundation.h>
#import "YTKRequest.h"
#import "YTKBaseRequest.h"
#import "YTKBatchRequest.h"
#import "YTKChainRequest.h"
#import "YTKNetworkAgent.h"
#import "YTKNetworkConfig.h"
NS_ASSUME_NONNULL_BEGIN
FOUNDATION_EXPORT void YTKLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
@class AFHTTPSessionManager;
@interface YTKNetworkUtils : NSObject
+ (BOOL)validateJSON:(id)json withValidator:(id)jsonValidator;
+ (void)addDoNotBackupAttribute:(NSString *)path;
+ (NSString *)md5StringFromString:(NSString *)string;
+ (NSString *)appVersionString;
+ (NSStringEncoding)stringEncodingWithRequest:(YTKBaseRequest *)request;
+ (BOOL)validateResumeData:(NSData *)data;
@end
@interface YTKRequest (Getter)
- (NSString *)cacheBasePath;
@end
@interface YTKBaseRequest (Setter)
@property (nonatomic, strong, readwrite) NSURLSessionTask *requestTask;
@property (nonatomic, strong, readwrite, nullable) NSData *responseData;
@property (nonatomic, strong, readwrite, nullable) id responseJSONObject;
@property (nonatomic, strong, readwrite, nullable) id responseObject;
@property (nonatomic, strong, readwrite, nullable) NSString *responseString;
@property (nonatomic, strong, readwrite, nullable) NSError *error;
@end
@interface YTKBaseRequest (RequestAccessory)
- (void)toggleAccessoriesWillStartCallBack;
- (void)toggleAccessoriesWillStopCallBack;
- (void)toggleAccessoriesDidStopCallBack;
@end
@interface YTKBatchRequest (RequestAccessory)
- (void)toggleAccessoriesWillStartCallBack;
- (void)toggleAccessoriesWillStopCallBack;
- (void)toggleAccessoriesDidStopCallBack;
@end
@interface YTKChainRequest (RequestAccessory)
- (void)toggleAccessoriesWillStartCallBack;
- (void)toggleAccessoriesWillStopCallBack;
- (void)toggleAccessoriesDidStopCallBack;
@end
@interface YTKNetworkAgent (Private)
- (AFHTTPSessionManager *)manager;
- (void)resetURLSessionManager;
- (void)resetURLSessionManagerWithConfiguration:(NSURLSessionConfiguration *)configuration;
- (NSString *)incompleteDownloadTempCacheFolder;
@end
NS_ASSUME_NONNULL_END
//
// YTKNetworkPrivate.m
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 <CommonCrypto/CommonDigest.h>
#import "YTKNetworkPrivate.h"
#if __has_include(<AFNetworking/AFURLRequestSerialization.h>)
#import <AFNetworking/AFURLRequestSerialization.h>
#else
#import <AFNetworking/AFURLRequestSerialization.h>
#endif
void YTKLog(NSString *format, ...) {
#ifdef DEBUG
if (![YTKNetworkConfig sharedConfig].debugLogEnabled) {
return;
}
va_list argptr;
va_start(argptr, format);
NSLogv(format, argptr);
va_end(argptr);
#endif
}
@implementation YTKNetworkUtils
+ (BOOL)validateJSON:(id)json withValidator:(id)jsonValidator {
if ([json isKindOfClass:[NSDictionary class]] &&
[jsonValidator isKindOfClass:[NSDictionary class]]) {
NSDictionary * dict = json;
NSDictionary * validator = jsonValidator;
BOOL result = YES;
NSEnumerator * enumerator = [validator keyEnumerator];
NSString * key;
while ((key = [enumerator nextObject]) != nil) {
id value = dict[key];
id format = validator[key];
if ([value isKindOfClass:[NSDictionary class]]
|| [value isKindOfClass:[NSArray class]]) {
result = [self validateJSON:value withValidator:format];
if (!result) {
break;
}
} else {
if ([value isKindOfClass:format] == NO &&
[value isKindOfClass:[NSNull class]] == NO) {
result = NO;
break;
}
}
}
return result;
} else if ([json isKindOfClass:[NSArray class]] &&
[jsonValidator isKindOfClass:[NSArray class]]) {
NSArray * validatorArray = (NSArray *)jsonValidator;
if (validatorArray.count > 0) {
NSArray * array = json;
NSDictionary * validator = jsonValidator[0];
for (id item in array) {
BOOL result = [self validateJSON:item withValidator:validator];
if (!result) {
return NO;
}
}
}
return YES;
} else if ([json isKindOfClass:jsonValidator]) {
return YES;
} else {
return NO;
}
}
+ (void)addDoNotBackupAttribute:(NSString *)path {
NSURL *url = [NSURL fileURLWithPath:path];
NSError *error = nil;
[url setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:&error];
if (error) {
YTKLog(@"error to set do not backup attribute, error = %@", error);
}
}
+ (NSString *)md5StringFromString:(NSString *)string {
NSParameterAssert(string != nil && [string length] > 0);
const char *value = [string UTF8String];
unsigned char outputBuffer[CC_MD5_DIGEST_LENGTH];
CC_MD5(value, (CC_LONG)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;
}
+ (NSString *)appVersionString {
return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
}
+ (NSStringEncoding)stringEncodingWithRequest:(YTKBaseRequest *)request {
// From AFNetworking 2.6.3
NSStringEncoding stringEncoding = NSUTF8StringEncoding;
NSString *encodingName = [request.response.textEncodingName copy];
if (encodingName) {
CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)encodingName);
if (encoding != kCFStringEncodingInvalidId) {
stringEncoding = CFStringConvertEncodingToNSStringEncoding(encoding);
}
}
return stringEncoding;
}
+ (BOOL)validateResumeData:(NSData *)data {
// From http://stackoverflow.com/a/22137510/3562486
if (!data || [data length] < 1) return NO;
NSError *error;
NSDictionary *resumeDictionary = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:NULL error:&error];
if (!resumeDictionary || error) return NO;
// Before iOS 9 & Mac OS X 10.11
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < 90000)\
|| (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED < 101100)
NSString *localFilePath = [resumeDictionary objectForKey:@"NSURLSessionResumeInfoLocalPath"];
if ([localFilePath length] < 1) return NO;
return [[NSFileManager defaultManager] fileExistsAtPath:localFilePath];
#endif
// After iOS 9 we can not actually detects if the cache file exists. This plist file has a somehow
// complicated structure. Besides, the plist structure is different between iOS 9 and iOS 10.
// We can only assume that the plist being successfully parsed means the resume data is valid.
return YES;
}
@end
@implementation YTKBaseRequest (RequestAccessory)
- (void)toggleAccessoriesWillStartCallBack {
for (id<YTKRequestAccessory> accessory in self.requestAccessories) {
if ([accessory respondsToSelector:@selector(requestWillStart:)]) {
[accessory requestWillStart:self];
}
}
}
- (void)toggleAccessoriesWillStopCallBack {
for (id<YTKRequestAccessory> accessory in self.requestAccessories) {
if ([accessory respondsToSelector:@selector(requestWillStop:)]) {
[accessory requestWillStop:self];
}
}
}
- (void)toggleAccessoriesDidStopCallBack {
for (id<YTKRequestAccessory> accessory in self.requestAccessories) {
if ([accessory respondsToSelector:@selector(requestDidStop:)]) {
[accessory requestDidStop:self];
}
}
}
@end
@implementation YTKBatchRequest (RequestAccessory)
- (void)toggleAccessoriesWillStartCallBack {
for (id<YTKRequestAccessory> accessory in self.requestAccessories) {
if ([accessory respondsToSelector:@selector(requestWillStart:)]) {
[accessory requestWillStart:self];
}
}
}
- (void)toggleAccessoriesWillStopCallBack {
for (id<YTKRequestAccessory> accessory in self.requestAccessories) {
if ([accessory respondsToSelector:@selector(requestWillStop:)]) {
[accessory requestWillStop:self];
}
}
}
- (void)toggleAccessoriesDidStopCallBack {
for (id<YTKRequestAccessory> accessory in self.requestAccessories) {
if ([accessory respondsToSelector:@selector(requestDidStop:)]) {
[accessory requestDidStop:self];
}
}
}
@end
@implementation YTKChainRequest (RequestAccessory)
- (void)toggleAccessoriesWillStartCallBack {
for (id<YTKRequestAccessory> accessory in self.requestAccessories) {
if ([accessory respondsToSelector:@selector(requestWillStart:)]) {
[accessory requestWillStart:self];
}
}
}
- (void)toggleAccessoriesWillStopCallBack {
for (id<YTKRequestAccessory> accessory in self.requestAccessories) {
if ([accessory respondsToSelector:@selector(requestWillStop:)]) {
[accessory requestWillStop:self];
}
}
}
- (void)toggleAccessoriesDidStopCallBack {
for (id<YTKRequestAccessory> accessory in self.requestAccessories) {
if ([accessory respondsToSelector:@selector(requestDidStop:)]) {
[accessory requestDidStop:self];
}
}
}
@end
//
// YTKRequest.h
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 "YTKBaseRequest.h"
NS_ASSUME_NONNULL_BEGIN
FOUNDATION_EXPORT NSString *const YTKRequestCacheErrorDomain;
NS_ENUM(NSInteger) {
YTKRequestCacheErrorExpired = -1,
YTKRequestCacheErrorVersionMismatch = -2,
YTKRequestCacheErrorSensitiveDataMismatch = -3,
YTKRequestCacheErrorAppVersionMismatch = -4,
YTKRequestCacheErrorInvalidCacheTime = -5,
YTKRequestCacheErrorInvalidMetadata = -6,
YTKRequestCacheErrorInvalidCacheData = -7,
};
/// YTKRequest is the base class you should inherit to create your own request class.
/// Based on YTKBaseRequest, YTKRequest adds local caching feature. Note download
/// request will not be cached whatsoever, because download request may involve complicated
/// cache control policy controlled by `Cache-Control`, `Last-Modified`, etc.
@interface YTKRequest : YTKBaseRequest
/// Whether to use cache as response or not.
/// Default is NO, which means caching will take effect with specific arguments.
/// Note that `cacheTimeInSeconds` default is -1. As a result cache data is not actually
/// used as response unless you return a positive value in `cacheTimeInSeconds`.
///
/// Also note that this option does not affect storing the response, which means response will always be saved
/// even `ignoreCache` is YES.
@property (nonatomic) BOOL ignoreCache;
/// Whether data is from local cache.
- (BOOL)isDataFromCache;
/// Manually load cache from storage.
///
/// @param error If an error occurred causing cache loading failed, an error object will be passed, otherwise NULL.
///
/// @return Whether cache is successfully loaded.
- (BOOL)loadCacheWithError:(NSError * __autoreleasing *)error;
/// Start request without reading local cache even if it exists. Use this to update local cache.
- (void)startWithoutCache;
/// Save response data (probably from another request) to this request's cache location
- (void)saveResponseDataToCacheFile:(NSData *)data;
#pragma mark - Subclass Override
/// The max time duration that cache can stay in disk until it's considered expired.
/// Default is -1, which means response is not actually saved as cache.
- (NSInteger)cacheTimeInSeconds;
/// Version can be used to identify and invalidate local cache. Default is 0.
- (long long)cacheVersion;
/// This can be used as additional identifier that tells the cache needs updating.
///
/// @discussion The `description` string of this object will be used as an identifier to verify whether cache
/// is invalid. Using `NSArray` or `NSDictionary` as return value type is recommended. However,
/// If you intend to use your custom class type, make sure that `description` is correctly implemented.
- (nullable id)cacheSensitiveData;
/// Whether cache is asynchronously written to storage. Default is YES.
- (BOOL)writeCacheAsynchronously;
@end
NS_ASSUME_NONNULL_END
//
// YTKRequest.m
//
// Copyright (c) 2012-2016 YTKNetwork https://github.com/yuantiku
//
// 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 "YTKNetworkConfig.h"
#import "YTKRequest.h"
#import "YTKNetworkPrivate.h"
#ifndef NSFoundationVersionNumber_iOS_8_0
#define NSFoundationVersionNumber_With_QoS_Available 1140.11
#else
#define NSFoundationVersionNumber_With_QoS_Available NSFoundationVersionNumber_iOS_8_0
#endif
NSString *const YTKRequestCacheErrorDomain = @"com.yuantiku.request.caching";
static dispatch_queue_t ytkrequest_cache_writing_queue() {
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dispatch_queue_attr_t attr = DISPATCH_QUEUE_SERIAL;
if (NSFoundationVersionNumber >= NSFoundationVersionNumber_With_QoS_Available) {
attr = dispatch_queue_attr_make_with_qos_class(attr, QOS_CLASS_BACKGROUND, 0);
}
queue = dispatch_queue_create("com.yuantiku.ytkrequest.caching", attr);
});
return queue;
}
@interface YTKCacheMetadata : NSObject<NSSecureCoding>
@property (nonatomic, assign) long long version;
@property (nonatomic, strong) NSString *sensitiveDataString;
@property (nonatomic, assign) NSStringEncoding stringEncoding;
@property (nonatomic, strong) NSDate *creationDate;
@property (nonatomic, strong) NSString *appVersionString;
@end
@implementation YTKCacheMetadata
+ (BOOL)supportsSecureCoding {
return YES;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:@(self.version) forKey:NSStringFromSelector(@selector(version))];
[aCoder encodeObject:self.sensitiveDataString forKey:NSStringFromSelector(@selector(sensitiveDataString))];
[aCoder encodeObject:@(self.stringEncoding) forKey:NSStringFromSelector(@selector(stringEncoding))];
[aCoder encodeObject:self.creationDate forKey:NSStringFromSelector(@selector(creationDate))];
[aCoder encodeObject:self.appVersionString forKey:NSStringFromSelector(@selector(appVersionString))];
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [self init];
if (!self) {
return nil;
}
self.version = [[aDecoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(version))] integerValue];
self.sensitiveDataString = [aDecoder decodeObjectOfClass:[NSString class] forKey:NSStringFromSelector(@selector(sensitiveDataString))];
self.stringEncoding = [[aDecoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(stringEncoding))] integerValue];
self.creationDate = [aDecoder decodeObjectOfClass:[NSDate class] forKey:NSStringFromSelector(@selector(creationDate))];
self.appVersionString = [aDecoder decodeObjectOfClass:[NSString class] forKey:NSStringFromSelector(@selector(appVersionString))];
return self;
}
@end
@interface YTKRequest()
@property (nonatomic, strong) NSData *cacheData;
@property (nonatomic, strong) NSString *cacheString;
@property (nonatomic, strong) id cacheJSON;
@property (nonatomic, strong) NSXMLParser *cacheXML;
@property (nonatomic, strong) YTKCacheMetadata *cacheMetadata;
@property (nonatomic, assign) BOOL dataFromCache;
@end
@implementation YTKRequest
- (void)start {
if (self.ignoreCache) {
[self startWithoutCache];
return;
}
// Do not cache download request.
if (self.resumableDownloadPath) {
[self startWithoutCache];
return;
}
if (![self loadCacheWithError:nil]) {
[self startWithoutCache];
return;
}
_dataFromCache = YES;
dispatch_async(dispatch_get_main_queue(), ^{
[self requestCompletePreprocessor];
[self requestCompleteFilter];
YTKRequest *strongSelf = self;
[strongSelf.delegate requestFinished:strongSelf];
if (strongSelf.successCompletionBlock) {
strongSelf.successCompletionBlock(strongSelf);
}
[strongSelf clearCompletionBlock];
});
}
- (void)startWithoutCache {
[self clearCacheVariables];
[super start];
}
#pragma mark - Network Request Delegate
- (void)requestCompletePreprocessor {
[super requestCompletePreprocessor];
if (self.writeCacheAsynchronously) {
dispatch_async(ytkrequest_cache_writing_queue(), ^{
[self saveResponseDataToCacheFile:[super responseData]];
});
} else {
[self saveResponseDataToCacheFile:[super responseData]];
}
}
#pragma mark - Subclass Override
- (NSInteger)cacheTimeInSeconds {
return -1;
}
- (long long)cacheVersion {
return 0;
}
- (id)cacheSensitiveData {
return nil;
}
- (BOOL)writeCacheAsynchronously {
return YES;
}
#pragma mark -
- (BOOL)isDataFromCache {
return _dataFromCache;
}
- (NSData *)responseData {
if (_cacheData) {
return _cacheData;
}
return [super responseData];
}
- (NSString *)responseString {
if (_cacheString) {
return _cacheString;
}
return [super responseString];
}
- (id)responseJSONObject {
if (_cacheJSON) {
return _cacheJSON;
}
return [super responseJSONObject];
}
- (id)responseObject {
if (_cacheJSON) {
return _cacheJSON;
}
if (_cacheXML) {
return _cacheXML;
}
if (_cacheData) {
return _cacheData;
}
return [super responseObject];
}
#pragma mark -
- (BOOL)loadCacheWithError:(NSError * _Nullable __autoreleasing *)error {
// Make sure cache time in valid.
if ([self cacheTimeInSeconds] < 0) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidCacheTime userInfo:@{ NSLocalizedDescriptionKey:@"Invalid cache time"}];
}
return NO;
}
// Try load metadata.
if (![self loadCacheMetadata]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidMetadata userInfo:@{ NSLocalizedDescriptionKey:@"Invalid metadata. Cache may not exist"}];
}
return NO;
}
// Check if cache is still valid.
if (![self validateCacheWithError:error]) {
return NO;
}
// Try load cache.
if (![self loadCacheData]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidCacheData userInfo:@{ NSLocalizedDescriptionKey:@"Invalid cache data"}];
}
return NO;
}
return YES;
}
- (BOOL)validateCacheWithError:(NSError * _Nullable __autoreleasing *)error {
// Date
NSDate *creationDate = self.cacheMetadata.creationDate;
NSTimeInterval duration = -[creationDate timeIntervalSinceNow];
if (duration < 0 || duration > [self cacheTimeInSeconds]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorExpired userInfo:@{ NSLocalizedDescriptionKey:@"Cache expired"}];
}
return NO;
}
// Version
long long cacheVersionFileContent = self.cacheMetadata.version;
if (cacheVersionFileContent != [self cacheVersion]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorVersionMismatch userInfo:@{ NSLocalizedDescriptionKey:@"Cache version mismatch"}];
}
return NO;
}
// Sensitive data
NSString *sensitiveDataString = self.cacheMetadata.sensitiveDataString;
NSString *currentSensitiveDataString = ((NSObject *)[self cacheSensitiveData]).description;
if (sensitiveDataString || currentSensitiveDataString) {
// If one of the strings is nil, short-circuit evaluation will trigger
if (sensitiveDataString.length != currentSensitiveDataString.length || ![sensitiveDataString isEqualToString:currentSensitiveDataString]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorSensitiveDataMismatch userInfo:@{ NSLocalizedDescriptionKey:@"Cache sensitive data mismatch"}];
}
return NO;
}
}
// App version
NSString *appVersionString = self.cacheMetadata.appVersionString;
NSString *currentAppVersionString = [YTKNetworkUtils appVersionString];
if (appVersionString || currentAppVersionString) {
if (appVersionString.length != currentAppVersionString.length || ![appVersionString isEqualToString:currentAppVersionString]) {
if (error) {
*error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorAppVersionMismatch userInfo:@{ NSLocalizedDescriptionKey:@"App version mismatch"}];
}
return NO;
}
}
return YES;
}
- (BOOL)loadCacheMetadata {
NSString *path = [self cacheMetadataFilePath];
NSFileManager * fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:path isDirectory:nil]) {
@try {
_cacheMetadata = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
return YES;
} @catch (NSException *exception) {
YTKLog(@"Load cache metadata failed, reason = %@", exception.reason);
return NO;
}
}
return NO;
}
- (BOOL)loadCacheData {
NSString *path = [self cacheFilePath];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
if ([fileManager fileExistsAtPath:path isDirectory:nil]) {
NSData *data = [NSData dataWithContentsOfFile:path];
_cacheData = data;
_cacheString = [[NSString alloc] initWithData:_cacheData encoding:self.cacheMetadata.stringEncoding];
switch (self.responseSerializerType) {
case YTKResponseSerializerTypeHTTP:
// Do nothing.
return YES;
case YTKResponseSerializerTypeJSON:
_cacheJSON = [NSJSONSerialization JSONObjectWithData:_cacheData options:(NSJSONReadingOptions)0 error:&error];
return error == nil;
case YTKResponseSerializerTypeXMLParser:
_cacheXML = [[NSXMLParser alloc] initWithData:_cacheData];
return YES;
}
}
return NO;
}
- (void)saveResponseDataToCacheFile:(NSData *)data {
if ([self cacheTimeInSeconds] > 0 && ![self isDataFromCache]) {
if (data != nil) {
@try {
// New data will always overwrite old data.
[data writeToFile:[self cacheFilePath] atomically:YES];
YTKCacheMetadata *metadata = [[YTKCacheMetadata alloc] init];
metadata.version = [self cacheVersion];
metadata.sensitiveDataString = ((NSObject *)[self cacheSensitiveData]).description;
metadata.stringEncoding = [YTKNetworkUtils stringEncodingWithRequest:self];
metadata.creationDate = [NSDate date];
metadata.appVersionString = [YTKNetworkUtils appVersionString];
[NSKeyedArchiver archiveRootObject:metadata toFile:[self cacheMetadataFilePath]];
} @catch (NSException *exception) {
YTKLog(@"Save cache failed, reason = %@", exception.reason);
}
}
}
}
- (void)clearCacheVariables {
_cacheData = nil;
_cacheXML = nil;
_cacheJSON = nil;
_cacheString = nil;
_cacheMetadata = nil;
_dataFromCache = NO;
}
#pragma mark -
- (void)createDirectoryIfNeeded:(NSString *)path {
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isDir;
if (![fileManager fileExistsAtPath:path isDirectory:&isDir]) {
[self createBaseDirectoryAtPath:path];
} else {
if (!isDir) {
NSError *error = nil;
[fileManager removeItemAtPath:path error:&error];
[self createBaseDirectoryAtPath:path];
}
}
}
- (void)createBaseDirectoryAtPath:(NSString *)path {
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES
attributes:nil error:&error];
if (error) {
YTKLog(@"create cache directory failed, error = %@", error);
} else {
[YTKNetworkUtils addDoNotBackupAttribute:path];
}
}
- (NSString *)cacheBasePath {
NSString *pathOfLibrary = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *path = [pathOfLibrary stringByAppendingPathComponent:@"LazyRequestCache"];
// Filter cache base path
NSArray<id<YTKCacheDirPathFilterProtocol>> *filters = [[YTKNetworkConfig sharedConfig] cacheDirPathFilters];
if (filters.count > 0) {
for (id<YTKCacheDirPathFilterProtocol> f in filters) {
path = [f filterCacheDirPath:path withRequest:self];
}
}
[self createDirectoryIfNeeded:path];
return path;
}
- (NSString *)cacheFileName {
NSString *requestUrl = [self requestUrl];
NSString *baseUrl = [YTKNetworkConfig sharedConfig].baseUrl;
id argument = [self cacheFileNameFilterForRequestArgument:[self requestArgument]];
NSString *requestInfo = [NSString stringWithFormat:@"Method:%ld Host:%@ Url:%@ Argument:%@",
(long)[self requestMethod], baseUrl, requestUrl, argument];
NSString *cacheFileName = [YTKNetworkUtils md5StringFromString:requestInfo];
return cacheFileName;
}
- (NSString *)cacheFilePath {
NSString *cacheFileName = [self cacheFileName];
NSString *path = [self cacheBasePath];
path = [path stringByAppendingPathComponent:cacheFileName];
return path;
}
- (NSString *)cacheMetadataFilePath {
NSString *cacheMetadataFileName = [NSString stringWithFormat:@"%@.metadata", [self cacheFileName]];
NSString *path = [self cacheBasePath];
path = [path stringByAppendingPathComponent:cacheMetadataFileName];
return path;
}
@end
//
// YTKRequestEventAccessory.h
// YTKNetwork
//
// Created by Chuanren Shang on 2020/8/17.
//
#import "YTKBaseRequest.h"
#import "YTKBatchRequest.h"
NS_ASSUME_NONNULL_BEGIN
@interface YTKRequestEventAccessory : NSObject <YTKRequestAccessory>
@property (nonatomic, copy, nullable) void (^willStartBlock)(id);
@property (nonatomic, copy, nullable) void (^willStopBlock)(id);
@property (nonatomic, copy, nullable) void (^didStopBlock)(id);
@end
@interface YTKBaseRequest (YTKRequestEventAccessory)
- (void)startWithWillStart:(nullable YTKRequestCompletionBlock)willStart
willStop:(nullable YTKRequestCompletionBlock)willStop
success:(nullable YTKRequestCompletionBlock)success
failure:(nullable YTKRequestCompletionBlock)failure
didStop:(nullable YTKRequestCompletionBlock)didStop;
@end
@interface YTKBatchRequest (YTKRequestEventAccessory)
- (void)startWithWillStart:(nullable void (^)(YTKBatchRequest *batchRequest))willStart
willStop:(nullable void (^)(YTKBatchRequest *batchRequest))willStop
success:(nullable void (^)(YTKBatchRequest *batchRequest))success
failure:(nullable void (^)(YTKBatchRequest *batchRequest))failure
didStop:(nullable void (^)(YTKBatchRequest *batchRequest))didStop;
@end
NS_ASSUME_NONNULL_END
//
// YTKRequestEventAccessory.m
// YTKNetwork
//
// Created by Chuanren Shang on 2020/8/17.
//
#import "YTKRequestEventAccessory.h"
@implementation YTKRequestEventAccessory
- (void)requestWillStart:(id)request {
if (self.willStartBlock != nil) {
self.willStartBlock(request);
self.willStartBlock = nil;
}
}
- (void)requestWillStop:(id)request {
if (self.willStopBlock != nil) {
self.willStopBlock(request);
self.willStopBlock = nil;
}
}
- (void)requestDidStop:(id)request {
if (self.didStopBlock != nil) {
self.didStopBlock(request);
self.didStopBlock = nil;
}
}
@end
@implementation YTKBaseRequest (YTKRequestEventAccessory)
- (void)startWithWillStart:(nullable YTKRequestCompletionBlock)willStart
willStop:(nullable YTKRequestCompletionBlock)willStop
success:(nullable YTKRequestCompletionBlock)success
failure:(nullable YTKRequestCompletionBlock)failure
didStop:(nullable YTKRequestCompletionBlock)didStop {
YTKRequestEventAccessory *accessory = [YTKRequestEventAccessory new];
accessory.willStartBlock = willStart;
accessory.willStopBlock = willStop;
accessory.didStopBlock = didStop;
[self addAccessory:accessory];
[self startWithCompletionBlockWithSuccess:success
failure:failure];
}
@end
@implementation YTKBatchRequest (YTKRequestEventAccessory)
- (void)startWithWillStart:(nullable void (^)(YTKBatchRequest *batchRequest))willStart
willStop:(nullable void (^)(YTKBatchRequest *batchRequest))willStop
success:(nullable void (^)(YTKBatchRequest *batchRequest))success
failure:(nullable void (^)(YTKBatchRequest *batchRequest))failure
didStop:(nullable void (^)(YTKBatchRequest *batchRequest))didStop {
YTKRequestEventAccessory *accessory = [YTKRequestEventAccessory new];
accessory.willStartBlock = willStart;
accessory.willStopBlock = willStop;
accessory.didStopBlock = didStop;
[self addAccessory:accessory];
[self startWithCompletionBlockWithSuccess:success
failure:failure];
}
@end
支持 Markdown 格式
你添加了 0 到此讨论。请谨慎行事。
Finish editing this message first!