├── .gitmodules
├── MJAutoCompleteDemo
├── MJAutoCompleteDemo
│ ├── en.lproj
│ │ └── InfoPlist.strings
│ ├── placeholder.png
│ ├── MDAppDelegate.h
│ ├── MDViewController.h
│ ├── main.m
│ ├── Images.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── LaunchImage.launchimage
│ │ │ └── Contents.json
│ ├── MJAutoCompleteDemo-Prefix.pch
│ ├── MDCustomAutoCompleteCell.h
│ ├── MDCustomAutoCompleteCell.m
│ ├── MJAutoCompleteDemo-Info.plist
│ ├── MDAppDelegate.m
│ ├── Base.lproj
│ │ └── Main.storyboard
│ ├── MDCustomAutoCompleteCell.xib
│ └── MDViewController.m
├── MJAutoCompleteDemoTests
│ ├── en.lproj
│ │ └── InfoPlist.strings
│ ├── MJAutoCompleteDemoTests-Info.plist
│ └── MJAutoCompleteDemoTests.m
├── MJAutoCompleteDemo.xcodeproj
│ └── project.xcworkspace
│ │ └── contents.xcworkspacedata
└── Vendor
│ └── Haneke
│ ├── Haneke.h
│ ├── HNKSimpleFetcher.h
│ ├── HNKSimpleFetcher.m
│ ├── UIView+Haneke.h
│ ├── HNKDiskFetcher.h
│ ├── HNKNetworkFetcher.h
│ ├── HNKDiskFetcher.m
│ ├── UIView+Haneke.m
│ ├── HNKNetworkFetcher.m
│ ├── HNKDiskCache.h
│ ├── UIImageView+Haneke.m
│ ├── HNKCache.h
│ ├── UIImageView+Haneke.h
│ └── HNKDiskCache.m
├── resources
└── MJAutoComplete-demo.gif
├── .gitignore
├── MJAutoComplete
├── MJAutoCompleteCell.h
├── MJAutoCompleteCell.m
├── MJAutoCompleteTC.h
├── MJAutoCompleteItem.m
├── MJAutoCompleteItem.h
├── MJAutoCompleteTrigger.m
├── MJAutoCompleteTrigger.h
├── MJAutoCompleteManager.h
├── MJAutoCompleteTC.m
└── MJAutoCompleteManager.m
├── TODO.md
├── Vendor
└── Haneke
│ ├── Haneke.h
│ ├── HNKSimpleFetcher.h
│ ├── HNKSimpleFetcher.m
│ ├── UIView+Haneke.h
│ ├── HNKDiskFetcher.h
│ ├── HNKNetworkFetcher.h
│ ├── HNKDiskFetcher.m
│ ├── UIView+Haneke.m
│ ├── HNKNetworkFetcher.m
│ ├── HNKDiskCache.h
│ ├── UIImageView+Haneke.m
│ ├── HNKCache.h
│ ├── UIImageView+Haneke.h
│ └── HNKDiskCache.m
├── LICENSE
├── MJAutoComplete.podspec
└── README.md
/.gitmodules:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemo/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemoTests/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/resources/MJAutoComplete-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mazyod/MJAutoComplete/HEAD/resources/MJAutoComplete-demo.gif
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemo/placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mazyod/MJAutoComplete/HEAD/MJAutoCompleteDemo/MJAutoCompleteDemo/placeholder.png
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | .DS_Store
3 | */build/*
4 | *.pbxuser
5 | !default.pbxuser
6 | *.mode1v3
7 | !default.mode1v3
8 | *.mode2v3
9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | xcuserdata
13 | profile
14 | *.moved-aside
15 | DerivedData
16 | .idea/
17 | *.hmap
18 | *.xccheckout
19 |
20 | #CocoaPods
21 | Pods
22 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemo/MDAppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // MDAppDelegate.h
3 | // MJAutoCompleteDemo
4 | //
5 | // Created by Mazyad Alabduljaleel on 2/18/14.
6 | // Copyright (c) 2014 ArabianDevs. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface MDAppDelegate : UIResponder
12 |
13 | @property (strong, nonatomic) UIWindow *window;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemo/MDViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MDViewController.h
3 | // MJAutoCompleteDemo
4 | //
5 | // Created by Mazyad Alabduljaleel on 2/18/14.
6 | // Copyright (c) 2014 ArabianDevs. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "MJAutoCompleteManager.h"
11 |
12 | @interface MDViewController : UIViewController
13 |
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemo/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // MJAutoCompleteDemo
4 | //
5 | // Created by Mazyad Alabduljaleel on 2/18/14.
6 | // Copyright (c) 2014 ArabianDevs. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #import "MDAppDelegate.h"
12 |
13 | int main(int argc, char * argv[])
14 | {
15 | @autoreleasepool {
16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([MDAppDelegate class]));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/MJAutoComplete/MJAutoCompleteCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // MJAutoCompleteCell.h
3 | // MJAutoCompleteDemo
4 | //
5 | // Created by Mazyad Alabduljaleel on 11/11/13.
6 | // Copyright (c) 2013 ArabianDevs. All rights reserved.
7 | //
8 |
9 | #import "MJAutoCompleteItem.h"
10 |
11 | @interface MJAutoCompleteCell : UITableViewCell
12 |
13 | @property (strong, nonatomic) MJAutoCompleteItem *autoCompleteItem;
14 | /* You can customize the tableView height through subclassing */
15 | + (CGFloat)height;
16 |
17 | @end
18 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemo/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "40x40",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "60x60",
16 | "scale" : "2x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemo/MJAutoCompleteDemo-Prefix.pch:
--------------------------------------------------------------------------------
1 | //
2 | // Prefix header
3 | //
4 | // The contents of this file are implicitly included at the beginning of every source file.
5 | //
6 |
7 | #import
8 |
9 | #ifndef __IPHONE_5_0
10 | #warning "This project uses features only available in iOS SDK 5.0 and later."
11 | #endif
12 |
13 | #ifdef __OBJC__
14 | #import
15 | #import
16 | #endif
17 |
18 | #define DICT_GET(_dict_, _key_) _dict_[_key_] == [NSNull null] ? @"" : _dict_[_key_]
19 |
--------------------------------------------------------------------------------
/MJAutoComplete/MJAutoCompleteCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // MJAutoCompleteCell.m
3 | // MJAutoCompleteDemo
4 | //
5 | // Created by Mazyad Alabduljaleel on 11/11/13.
6 | // Copyright (c) 2013 ArabianDevs. All rights reserved.
7 | //
8 |
9 | #import "MJAutoCompleteCell.h"
10 |
11 | @implementation MJAutoCompleteCell
12 |
13 | + (CGFloat)height
14 | {
15 | return 44.f;
16 | }
17 |
18 | - (void)setAutoCompleteItem:(MJAutoCompleteItem *)autoCompleteItem
19 | {
20 | _autoCompleteItem = autoCompleteItem;
21 | self.textLabel.text = autoCompleteItem.displayedString;
22 | }
23 |
24 | @end
25 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemo/Images.xcassets/LaunchImage.launchimage/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "orientation" : "portrait",
5 | "idiom" : "iphone",
6 | "extent" : "full-screen",
7 | "minimum-system-version" : "7.0",
8 | "scale" : "2x"
9 | },
10 | {
11 | "orientation" : "portrait",
12 | "idiom" : "iphone",
13 | "subtype" : "retina4",
14 | "extent" : "full-screen",
15 | "minimum-system-version" : "7.0",
16 | "scale" : "2x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemo/MDCustomAutoCompleteCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // MDCustomAutoCompleteCell.h
3 | // MJAutoCompleteDemo
4 | //
5 | // Created by Mazyad Alabduljaleel on 2/19/14.
6 | // Copyright (c) 2014 ArabianDevs. All rights reserved.
7 | //
8 |
9 | #import "MJAutoCompleteCell.h"
10 |
11 | @interface MDCustomAutoCompleteCell : MJAutoCompleteCell
12 |
13 | @property (weak, nonatomic) IBOutlet UILabel *titleLabel;
14 | @property (weak, nonatomic) IBOutlet UILabel *subtitleLabel;
15 | @property (weak, nonatomic) IBOutlet UILabel *viewsLabel;
16 | @property (weak, nonatomic) IBOutlet UILabel *censoredLabel;
17 |
18 | @property (weak, nonatomic) IBOutlet UIImageView *avatarImageView;
19 |
20 | @end
21 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 |
2 | # TODO
3 |
4 | + Optionally auto hide and reveal the autoComplete container within the manager.
5 | + Smart autoComplete that not only filters, but knows which is a better match.
6 | + ~~Need to test assigning the trigger cell when the cell is not available from NIB.~~
7 | + Add UITableView and UITableViewCell customization ability for the user via UIAppearance proxy.
8 | + Should probably pass the tableView to the user the first time a trigger appears (and remove trigger.cellClass/cellNIB)
9 | + Raise exception or handle gracefully if the user sent an array of strings for the component.
10 | + explore linking the autoCompleteMgr to the user through KVO.
11 | + ~~Write a better README.~~
12 | + Write a demo walk through.
13 | + Mark init as unavailable in MJTrigger
14 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemoTests/MJAutoCompleteDemoTests-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | arabiandevs.${PRODUCT_NAME:rfc1034identifier}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundlePackageType
14 | BNDL
15 | CFBundleShortVersionString
16 | 1.0
17 | CFBundleSignature
18 | ????
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemoTests/MJAutoCompleteDemoTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // MJAutoCompleteDemoTests.m
3 | // MJAutoCompleteDemoTests
4 | //
5 | // Created by Mazyad Alabduljaleel on 2/18/14.
6 | // Copyright (c) 2014 ArabianDevs. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface MJAutoCompleteDemoTests : XCTestCase
12 |
13 | @end
14 |
15 | @implementation MJAutoCompleteDemoTests
16 |
17 | - (void)setUp
18 | {
19 | [super setUp];
20 | // Put setup code here. This method is called before the invocation of each test method in the class.
21 | }
22 |
23 | - (void)tearDown
24 | {
25 | // Put teardown code here. This method is called after the invocation of each test method in the class.
26 | [super tearDown];
27 | }
28 |
29 | - (void)testExample
30 | {
31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__);
32 | }
33 |
34 | @end
35 |
--------------------------------------------------------------------------------
/Vendor/Haneke/Haneke.h:
--------------------------------------------------------------------------------
1 | //
2 | // Haneke.h
3 | // Haneke
4 | //
5 | // Created by Hermés Piqué on 13/03/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import "HNKCache.h"
22 | #import "HNKDiskFetcher.h"
23 | #import "HNKNetworkFetcher.h"
24 | #import "UIImageView+Haneke.h"
25 | #import "UIButton+Haneke.h"
26 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/Vendor/Haneke/Haneke.h:
--------------------------------------------------------------------------------
1 | //
2 | // Haneke.h
3 | // Haneke
4 | //
5 | // Created by Hermés Piqué on 13/03/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import "HNKCache.h"
22 | #import "HNKDiskFetcher.h"
23 | #import "HNKNetworkFetcher.h"
24 | #import "UIImageView+Haneke.h"
25 | #import "UIButton+Haneke.h"
26 |
--------------------------------------------------------------------------------
/MJAutoComplete/MJAutoCompleteTC.h:
--------------------------------------------------------------------------------
1 | //
2 | // MJAutoCompleteTC.h
3 | // MJAutoCompleteDemo
4 | //
5 | // Created by Mazyad Alabduljaleel on 11/9/13.
6 | // Copyright (c) 2013 ArabianDevs. All rights reserved.
7 | //
8 |
9 | @import UIKit;
10 | #import "MJAutoCompleteItem.h"
11 |
12 | @class MJAutoCompleteTC, MJAutoCompleteCell, MJAutoCompleteTrigger;
13 |
14 | @protocol MJAutoCompleteTCDelegate
15 |
16 | @property (strong, nonatomic) MJAutoCompleteTrigger* currentTrigger;
17 |
18 | - (void)autoCompleteTableController:(MJAutoCompleteTC *)acTableController
19 | willPresentCell:(MJAutoCompleteCell *)cell;
20 |
21 | - (void)autoCompleteTableController:(MJAutoCompleteTC *)acTableController
22 | didSelectItem:(MJAutoCompleteItem *)selectedItem;
23 |
24 | @end
25 |
26 | @interface MJAutoCompleteTC : UITableViewController
27 |
28 | - (instancetype)initWithDelegate:(id)delegate;
29 |
30 | - (void)showAutoCompleteItems:(NSArray *)items reversed:(BOOL)reverse;
31 |
32 | @end
33 |
--------------------------------------------------------------------------------
/MJAutoComplete/MJAutoCompleteItem.m:
--------------------------------------------------------------------------------
1 | //
2 | // MJAutoCompleteItem.m
3 | // MJAutoCompleteDemo
4 | //
5 | // Created by Mazyad Alabduljaleel on 11/10/13.
6 | // Copyright (c) 2013 ArabianDevs. All rights reserved.
7 | //
8 |
9 | #import "MJAutoCompleteItem.h"
10 |
11 | @implementation MJAutoCompleteItem
12 | @synthesize displayedString = _displayedString;
13 |
14 | + (NSArray *)autoCompleteCellModelFromObjects:(NSArray *)objs
15 | {
16 | NSMutableArray* items = [NSMutableArray arrayWithCapacity:[objs count]];
17 | for (id obj in objs)
18 | {
19 | MJAutoCompleteItem* item = [[self alloc] init];
20 | item.autoCompleteString = [obj description];
21 |
22 | [items addObject:item];
23 | }
24 |
25 | return items;
26 | }
27 |
28 | - (NSString *)displayedString
29 | {
30 | if (!_displayedString)
31 | {
32 | return self.autoCompleteString;
33 | }
34 | return _displayedString;
35 | }
36 |
37 | - (NSString *)description
38 | {
39 | return self.autoCompleteString;
40 | }
41 |
42 | @end
43 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemo/MDCustomAutoCompleteCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // MDCustomAutoCompleteCell.m
3 | // MJAutoCompleteDemo
4 | //
5 | // Created by Mazyad Alabduljaleel on 2/19/14.
6 | // Copyright (c) 2014 ArabianDevs. All rights reserved.
7 | //
8 |
9 | #import "MDCustomAutoCompleteCell.h"
10 |
11 | @interface MDCustomAutoCompleteCell ()
12 |
13 | @end
14 |
15 | @implementation MDCustomAutoCompleteCell
16 |
17 | /* override the setter to assign the subtitle label */
18 | - (void)setAutoCompleteItem:(MJAutoCompleteItem *)autoCompleteItem
19 | {
20 | // I bet you didn't know this was possible :p
21 | super.autoCompleteItem = autoCompleteItem;
22 | /* Superclass will set the text label to displayString, we don't want that. */
23 | self.textLabel.hidden = YES;
24 |
25 | NSDictionary *context = autoCompleteItem.context;
26 | self.titleLabel.text = DICT_GET(context, @"title");
27 | self.subtitleLabel.text = DICT_GET(context, @"emotion");
28 | self.viewsLabel.text = [DICT_GET(context, @"views") description];
29 | self.censoredLabel.hidden = ![DICT_GET(context, @"is_censored") boolValue];
30 | }
31 |
32 | @end
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Mazyad Alabduljaleel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Vendor/Haneke/HNKSimpleFetcher.h:
--------------------------------------------------------------------------------
1 | //
2 | // HNKSimpleFetcher.h
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 8/19/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import
22 | #import "HNKCache.h"
23 |
24 | /**
25 | Simple fetcher that represents a key-image pair.
26 | @discussion Used as a convenience by the UIKit categories.
27 | */
28 | @interface HNKSimpleFetcher : NSObject
29 |
30 | /**
31 | Initializes a fetcher with the given key and image.
32 | @param key Image key.
33 | @param image Image that will be returned by the fetcher.
34 | */
35 | - (instancetype)initWithKey:(NSString*)key image:(UIImage*)image;
36 |
37 | @end
38 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/Vendor/Haneke/HNKSimpleFetcher.h:
--------------------------------------------------------------------------------
1 | //
2 | // HNKSimpleFetcher.h
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 8/19/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import
22 | #import "HNKCache.h"
23 |
24 | /**
25 | Simple fetcher that represents a key-image pair.
26 | @discussion Used as a convenience by the UIKit categories.
27 | */
28 | @interface HNKSimpleFetcher : NSObject
29 |
30 | /**
31 | Initializes a fetcher with the given key and image.
32 | @param key Image key.
33 | @param image Image that will be returned by the fetcher.
34 | */
35 | - (instancetype)initWithKey:(NSString*)key image:(UIImage*)image;
36 |
37 | @end
38 |
--------------------------------------------------------------------------------
/Vendor/Haneke/HNKSimpleFetcher.m:
--------------------------------------------------------------------------------
1 | //
2 | // HNKSimpleFetcher.m
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 8/19/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import "HNKSimpleFetcher.h"
22 |
23 | @implementation HNKSimpleFetcher {
24 | NSString *_key;
25 | UIImage *_image;
26 | }
27 |
28 | - (instancetype)initWithKey:(NSString*)key image:(UIImage*)image
29 | {
30 | if (self = [super init])
31 | {
32 | _key = [key copy];
33 | _image = image;
34 | }
35 | return self;
36 | }
37 |
38 | - (NSString*)key
39 | {
40 | return _key;
41 | }
42 |
43 | - (void)fetchImageWithSuccess:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
44 | {
45 | successBlock(_image);
46 | }
47 |
48 | @end
49 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/Vendor/Haneke/HNKSimpleFetcher.m:
--------------------------------------------------------------------------------
1 | //
2 | // HNKSimpleFetcher.m
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 8/19/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import "HNKSimpleFetcher.h"
22 |
23 | @implementation HNKSimpleFetcher {
24 | NSString *_key;
25 | UIImage *_image;
26 | }
27 |
28 | - (instancetype)initWithKey:(NSString*)key image:(UIImage*)image
29 | {
30 | if (self = [super init])
31 | {
32 | _key = [key copy];
33 | _image = image;
34 | }
35 | return self;
36 | }
37 |
38 | - (NSString*)key
39 | {
40 | return _key;
41 | }
42 |
43 | - (void)fetchImageWithSuccess:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
44 | {
45 | successBlock(_image);
46 | }
47 |
48 | @end
49 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemo/MJAutoCompleteDemo-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | ${PRODUCT_NAME}
9 | CFBundleExecutable
10 | ${EXECUTABLE_NAME}
11 | CFBundleIdentifier
12 | arabiandevs.${PRODUCT_NAME:rfc1034identifier}
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | ${PRODUCT_NAME}
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1.0
25 | LSRequiresIPhoneOS
26 |
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Vendor/Haneke/UIView+Haneke.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Haneke.h
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 8/20/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import
22 | #import "HNKCache.h"
23 |
24 | extern const CGFloat HNKViewFormatCompressionQuality;
25 | extern const unsigned long long HNKViewFormatDiskCapacity;
26 |
27 | /**
28 | Convenience category used in the other UIKit categories to avoid repeating code. Intended for internal use.
29 | */
30 | @interface UIView (Haneke)
31 |
32 | @property (nonatomic, readonly) HNKScaleMode hnk_scaleMode;
33 |
34 | @end
35 |
36 | @interface HNKCache(UIView)
37 |
38 | + (void)registerSharedFormat:(HNKCacheFormat*)format;
39 |
40 | + (HNKCacheFormat*)sharedFormatWithSize:(CGSize)size scaleMode:(HNKScaleMode)scaleMode;
41 |
42 | @end
43 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/Vendor/Haneke/UIView+Haneke.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Haneke.h
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 8/20/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import
22 | #import "HNKCache.h"
23 |
24 | extern const CGFloat HNKViewFormatCompressionQuality;
25 | extern const unsigned long long HNKViewFormatDiskCapacity;
26 |
27 | /**
28 | Convenience category used in the other UIKit categories to avoid repeating code. Intended for internal use.
29 | */
30 | @interface UIView (Haneke)
31 |
32 | @property (nonatomic, readonly) HNKScaleMode hnk_scaleMode;
33 |
34 | @end
35 |
36 | @interface HNKCache(UIView)
37 |
38 | + (void)registerSharedFormat:(HNKCacheFormat*)format;
39 |
40 | + (HNKCacheFormat*)sharedFormatWithSize:(CGSize)size scaleMode:(HNKScaleMode)scaleMode;
41 |
42 | @end
43 |
--------------------------------------------------------------------------------
/Vendor/Haneke/HNKDiskFetcher.h:
--------------------------------------------------------------------------------
1 | //
2 | // HNKDiskFetcher.h
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 7/23/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import
22 | #import "HNKCache.h"
23 |
24 | enum
25 | {
26 | HNKErrorDiskFetcherInvalidData = -500,
27 | };
28 |
29 | /**
30 | Fetcher that can provide a disk image. The key will be the given path.
31 | */
32 | @interface HNKDiskFetcher : NSObject
33 |
34 | /**
35 | Initializes a fetcher with the given path.
36 | @param path Image path.
37 | */
38 | - (instancetype)initWithPath:(NSString*)path;
39 |
40 | /**
41 | Cancels the current fetch. When a fetch is cancelled it should not call any of the provided blocks.
42 | @discussion This will be typically used by UI logic to cancel fetches during view reuse.
43 | */
44 | - (void)cancelFetch;
45 |
46 | @end
47 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/Vendor/Haneke/HNKDiskFetcher.h:
--------------------------------------------------------------------------------
1 | //
2 | // HNKDiskFetcher.h
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 7/23/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import
22 | #import "HNKCache.h"
23 |
24 | enum
25 | {
26 | HNKErrorDiskFetcherInvalidData = -500,
27 | };
28 |
29 | /**
30 | Fetcher that can provide a disk image. The key will be the given path.
31 | */
32 | @interface HNKDiskFetcher : NSObject
33 |
34 | /**
35 | Initializes a fetcher with the given path.
36 | @param path Image path.
37 | */
38 | - (instancetype)initWithPath:(NSString*)path;
39 |
40 | /**
41 | Cancels the current fetch. When a fetch is cancelled it should not call any of the provided blocks.
42 | @discussion This will be typically used by UI logic to cancel fetches during view reuse.
43 | */
44 | - (void)cancelFetch;
45 |
46 | @end
47 |
--------------------------------------------------------------------------------
/MJAutoComplete/MJAutoCompleteItem.h:
--------------------------------------------------------------------------------
1 | //
2 | // MJAutoCompleteItem.h
3 | // MJAutoCompleteDemo
4 | //
5 | // Created by Mazyad Alabduljaleel on 11/10/13.
6 | // Copyright (c) 2013 ArabianDevs. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | /*
12 | CLASS DESCRIPTION:
13 | ==================
14 |
15 | This class represents an item displayed in the autoComplete
16 | list. An item must have a string set, which will is the autoComplete
17 | option that is compared against the string when a trigger is fired.
18 | The other strings, displayedString, can optionally be set, and it
19 | will be the string displayed to the user rather than the autoComplete
20 | string.
21 |
22 | ex.
23 | autoCompleteString: mazyod (my twitter handle)
24 | displayedString: Mazyad Alabduljaleel (my name)
25 |
26 | So, by entering "mazyad", nothing shows up, but when "mazyod" is
27 | entered, "Mazyad Alabduljaleel" will be shown as an option.
28 |
29 | */
30 |
31 | @interface MJAutoCompleteItem : NSObject
32 | /* You can specify the string that autoCompletes different from the string that is shown in the UI */
33 | @property (strong, nonatomic) NSString *autoCompleteString;
34 | @property (strong, nonatomic) NSString *displayedString;
35 | /* Arbitrary data you might want to associate with the MJAutoCompleteItem */
36 | @property (strong, nonatomic) NSDictionary *context;
37 | // for convenience, -[NSObject description] will be called on all the items
38 | + (NSArray *)autoCompleteCellModelFromObjects:(NSArray *)objs;
39 |
40 | @end
41 |
--------------------------------------------------------------------------------
/Vendor/Haneke/HNKNetworkFetcher.h:
--------------------------------------------------------------------------------
1 | //
2 | // HNKNetworkFetcher.h
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 7/23/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import
22 | #import "HNKCache.h"
23 |
24 | enum
25 | {
26 | HNKErrorNetworkFetcherInvalidData = -400,
27 | HNKErrorNetworkFetcherMissingData = -401,
28 | HNKErrorNetworkFetcherInvalidStatusCode = -402
29 | };
30 |
31 | /**
32 | Fetcher that can provide a network image. The key will be the absolute string of the given URL.
33 | */
34 | @interface HNKNetworkFetcher : NSObject
35 |
36 | /**
37 | Initializes a fetcher with the given URL.
38 | @param URL Image URL.
39 | */
40 | - (instancetype)initWithURL:(NSURL*)URL;
41 |
42 | /**
43 | Image URL.
44 | */
45 | @property (nonatomic, readonly) NSURL *URL;
46 |
47 |
48 | /**
49 | Cancels the current fetch. When a fetch is cancelled it should not call any of the provided blocks.
50 | @discussion This will be typically used by UI logic to cancel fetches during view reuse.
51 | */
52 | - (void)cancelFetch;
53 |
54 | @end
55 |
56 | @interface HNKNetworkFetcher (Subclassing)
57 |
58 | /**
59 | Returns the URL sessions used to download the image. Override to use a custom session. Uses sharedSession by default.
60 | */
61 | @property (nonatomic, readonly) NSURLSession *URLSession;
62 |
63 | @end
64 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/Vendor/Haneke/HNKNetworkFetcher.h:
--------------------------------------------------------------------------------
1 | //
2 | // HNKNetworkFetcher.h
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 7/23/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import
22 | #import "HNKCache.h"
23 |
24 | enum
25 | {
26 | HNKErrorNetworkFetcherInvalidData = -400,
27 | HNKErrorNetworkFetcherMissingData = -401,
28 | HNKErrorNetworkFetcherInvalidStatusCode = -402
29 | };
30 |
31 | /**
32 | Fetcher that can provide a network image. The key will be the absolute string of the given URL.
33 | */
34 | @interface HNKNetworkFetcher : NSObject
35 |
36 | /**
37 | Initializes a fetcher with the given URL.
38 | @param URL Image URL.
39 | */
40 | - (instancetype)initWithURL:(NSURL*)URL;
41 |
42 | /**
43 | Image URL.
44 | */
45 | @property (nonatomic, readonly) NSURL *URL;
46 |
47 |
48 | /**
49 | Cancels the current fetch. When a fetch is cancelled it should not call any of the provided blocks.
50 | @discussion This will be typically used by UI logic to cancel fetches during view reuse.
51 | */
52 | - (void)cancelFetch;
53 |
54 | @end
55 |
56 | @interface HNKNetworkFetcher (Subclassing)
57 |
58 | /**
59 | Returns the URL sessions used to download the image. Override to use a custom session. Uses sharedSession by default.
60 | */
61 | @property (nonatomic, readonly) NSURLSession *URLSession;
62 |
63 | @end
64 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemo/MDAppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // MDAppDelegate.m
3 | // MJAutoCompleteDemo
4 | //
5 | // Created by Mazyad Alabduljaleel on 2/18/14.
6 | // Copyright (c) 2014 ArabianDevs. All rights reserved.
7 | //
8 |
9 | #import "MDAppDelegate.h"
10 |
11 | @implementation MDAppDelegate
12 |
13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
14 | {
15 | // Override point for customization after application launch.
16 | return YES;
17 | }
18 |
19 | - (void)applicationWillResignActive:(UIApplication *)application
20 | {
21 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
22 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
23 | }
24 |
25 | - (void)applicationDidEnterBackground:(UIApplication *)application
26 | {
27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
29 | }
30 |
31 | - (void)applicationWillEnterForeground:(UIApplication *)application
32 | {
33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | - (void)applicationDidBecomeActive:(UIApplication *)application
37 | {
38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
39 | }
40 |
41 | - (void)applicationWillTerminate:(UIApplication *)application
42 | {
43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
44 | }
45 |
46 | @end
47 |
--------------------------------------------------------------------------------
/MJAutoComplete/MJAutoCompleteTrigger.m:
--------------------------------------------------------------------------------
1 | //
2 | // MJAutoCompleteDelimiter.m
3 | // MJAutoCompleteDemo
4 | //
5 | // Created by Mazyad Alabduljaleel on 11/11/13.
6 | // Copyright (c) 2013 ArabianDevs. All rights reserved.
7 | //
8 |
9 | #import "MJAutoCompleteTrigger.h"
10 |
11 | @implementation MJAutoCompleteTrigger
12 |
13 | - (instancetype)initWithDelimiter:(NSString *)delimiter
14 | {
15 | return [self initWithDelimiter:delimiter autoCompleteItems:nil];
16 | }
17 |
18 | - (instancetype)initWithDelimiter:(NSString *)delimiter autoCompleteItems:(NSArray*)items;
19 | {
20 | return [self initWithDelimiter:delimiter autoCompleteItems:items cell:nil];
21 | }
22 |
23 | - (instancetype)initWithDelimiter:(NSString *)delimiter autoCompleteItems:(NSArray *)items cell:(NSString *)cell
24 | {
25 | self = [super init];
26 | if (self)
27 | {
28 | self.delimiter = delimiter;
29 | self.autoCompleteItemList = items;
30 | self.cell = cell;
31 | }
32 | return self;
33 | }
34 |
35 | - (NSString *)substringToBeAutoCompletedInString:(NSString *)string
36 | {
37 | // short circuit for an empty delimiter string
38 | if (!self.delimiter.length)
39 | {
40 | return string;
41 | }
42 |
43 | // make sure to search backwards, since that's where the user it typing
44 | NSCharacterSet *breakSet = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
45 | NSRange brRange = [string rangeOfCharacterFromSet:breakSet
46 | options:NSBackwardsSearch];
47 |
48 | NSRange dlRange = [string rangeOfString:self.delimiter options:NSBackwardsSearch];
49 | // non alphanumeric breaks the autoComplete suggestions
50 | if (dlRange.location != NSNotFound &&
51 | (dlRange.location >= brRange.location || brRange.location == NSNotFound))
52 | {
53 | return [string substringFromIndex:dlRange.location+1];
54 | }
55 |
56 | return nil;
57 | }
58 |
59 | /* Implement isEqual to help the NSSet make sure the triggers are unique */
60 | - (BOOL)isEqual:(id)object
61 | {
62 | if ([object isKindOfClass:[self class]])
63 | {
64 | return [[object delimiter] isEqual:self.delimiter];
65 | }
66 |
67 | return NO;
68 | }
69 |
70 | - (NSUInteger)hash
71 | {
72 | return [self.delimiter hash];
73 | }
74 |
75 | @end
76 |
--------------------------------------------------------------------------------
/MJAutoComplete/MJAutoCompleteTrigger.h:
--------------------------------------------------------------------------------
1 | //
2 | // MJAutoCompleteDelimiter.h
3 | // MJAutoCompleteDemo
4 | //
5 | // Created by Mazyad Alabduljaleel on 11/11/13.
6 | // Copyright (c) 2013 ArabianDevs. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | /**
12 | CLASS DESCRIPTION:
13 | ==================
14 |
15 | The autoComplete component is really a delimiter oriented
16 | component. Based on the delimiter, we would show a certain list.
17 | With that in mind, you can think of this class as a plugin enabler
18 | for the autoComplete component.
19 |
20 | The minimum requirement for the trigger is assigning the
21 | delimiter.
22 |
23 | Assigning an AutoCompleteItem list is optional, and will take
24 | the burden of maintaining the list of your hands. If it is not set,
25 | you must implement the AutoCompleteManagerDataSource
26 | itemListForTrigger:. If that method is implemented, it has higher
27 | priorty, and will be called. You can simply return trigger.list if
28 | you only want to customize one trigger.
29 |
30 | IMPORTANT:
31 | ==========
32 |
33 | Assining an empty string as the delimiter will cause the
34 | component to act as a general purpose search autoComplete engine.
35 | For example, if the user types, "I like turtles", an autoComplete
36 | string will be fetched based on the WHOLE string. Hence, we get:
37 | "You are a great Zombie".
38 |
39 | **/
40 |
41 | @interface MJAutoCompleteTrigger : NSObject
42 | /** the delimiter that will activate the trigger **/
43 | @property (copy, nonatomic) NSString *delimiter;
44 | /** an optional autoCompleteItem array that would automatically be used for this trigger, if the dataSource doesn't implement itemListForTrigger **/
45 | @property (copy, nonatomic) NSArray *autoCompleteItemList;
46 | /** assign the cusotm cell class OR nib, NOT BOTH!! (allows different triggers show different cells) **/
47 | @property (strong, nonatomic) NSString *cell;
48 |
49 | /* I am a big fan of providing the user with multiple signatures, to save them as much typing as possible */
50 | - (instancetype)initWithDelimiter:(NSString *)delimiter;
51 | - (instancetype)initWithDelimiter:(NSString *)delimiter autoCompleteItems:(NSArray*)items;
52 | - (instancetype)initWithDelimiter:(NSString *)delimiter autoCompleteItems:(NSArray*)items cell:(NSString *)cell;
53 |
54 | /* get the string that fires this trigger, or nil if none */
55 | - (NSString *)substringToBeAutoCompletedInString:(NSString *)string;
56 |
57 | @end
58 |
--------------------------------------------------------------------------------
/Vendor/Haneke/HNKDiskFetcher.m:
--------------------------------------------------------------------------------
1 | //
2 | // HNKDiskFetcher.m
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 7/23/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import "HNKDiskFetcher.h"
22 |
23 | @implementation HNKDiskFetcher {
24 | NSString *_path;
25 | BOOL _cancelled;
26 | }
27 |
28 | - (instancetype)initWithPath:(NSString*)path
29 | {
30 | if (self = [super init])
31 | {
32 | _path = path;
33 | }
34 | return self;
35 | }
36 |
37 | - (NSString*)key
38 | {
39 | return _path;
40 | }
41 |
42 | - (void)fetchImageWithSuccess:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
43 | {
44 | _cancelled = NO;
45 | __weak __typeof__(self) weakSelf = self;
46 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
47 | __strong __typeof__(weakSelf) strongSelf = weakSelf;
48 |
49 | if (!strongSelf) return;
50 |
51 | if (strongSelf->_cancelled) return;
52 |
53 | NSString *path = strongSelf->_path;
54 |
55 | NSError *error = nil;
56 | NSData *data = [NSData dataWithContentsOfFile:path options:kNilOptions error:&error];
57 | if (!data)
58 | {
59 | HanekeLog(@"Request %@ failed with error %@", path, error);
60 | dispatch_async(dispatch_get_main_queue(), ^{
61 | failureBlock(error);
62 | });
63 | return;
64 | }
65 |
66 | if (strongSelf->_cancelled) return;
67 |
68 | UIImage *image = [UIImage imageWithData:data];
69 |
70 | if (!image)
71 | {
72 | NSString *errorDescription = [NSString stringWithFormat:NSLocalizedString(@"Failed to load image from data at path %@", @""), path];
73 | HanekeLog(@"%@", errorDescription);
74 | NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : errorDescription , NSFilePathErrorKey : path};
75 | NSError *error = [NSError errorWithDomain:HNKErrorDomain code:HNKErrorDiskFetcherInvalidData userInfo:userInfo];
76 | dispatch_async(dispatch_get_main_queue(), ^{
77 | failureBlock(error);
78 | });
79 | return;
80 | }
81 |
82 | dispatch_async(dispatch_get_main_queue(), ^{
83 | if (strongSelf->_cancelled) return;
84 |
85 | successBlock(image);
86 | });
87 | });
88 | }
89 |
90 | - (void)cancelFetch
91 | {
92 | _cancelled = YES;
93 | }
94 |
95 | - (void)dealloc
96 | {
97 | [self cancelFetch];
98 | }
99 |
100 | @end
101 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/Vendor/Haneke/HNKDiskFetcher.m:
--------------------------------------------------------------------------------
1 | //
2 | // HNKDiskFetcher.m
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 7/23/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import "HNKDiskFetcher.h"
22 |
23 | @implementation HNKDiskFetcher {
24 | NSString *_path;
25 | BOOL _cancelled;
26 | }
27 |
28 | - (instancetype)initWithPath:(NSString*)path
29 | {
30 | if (self = [super init])
31 | {
32 | _path = path;
33 | }
34 | return self;
35 | }
36 |
37 | - (NSString*)key
38 | {
39 | return _path;
40 | }
41 |
42 | - (void)fetchImageWithSuccess:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
43 | {
44 | _cancelled = NO;
45 | __weak __typeof__(self) weakSelf = self;
46 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
47 | __strong __typeof__(weakSelf) strongSelf = weakSelf;
48 |
49 | if (!strongSelf) return;
50 |
51 | if (strongSelf->_cancelled) return;
52 |
53 | NSString *path = strongSelf->_path;
54 |
55 | NSError *error = nil;
56 | NSData *data = [NSData dataWithContentsOfFile:path options:kNilOptions error:&error];
57 | if (!data)
58 | {
59 | HanekeLog(@"Request %@ failed with error %@", path, error);
60 | dispatch_async(dispatch_get_main_queue(), ^{
61 | failureBlock(error);
62 | });
63 | return;
64 | }
65 |
66 | if (strongSelf->_cancelled) return;
67 |
68 | UIImage *image = [UIImage imageWithData:data];
69 |
70 | if (!image)
71 | {
72 | NSString *errorDescription = [NSString stringWithFormat:NSLocalizedString(@"Failed to load image from data at path %@", @""), path];
73 | HanekeLog(@"%@", errorDescription);
74 | NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : errorDescription , NSFilePathErrorKey : path};
75 | NSError *error = [NSError errorWithDomain:HNKErrorDomain code:HNKErrorDiskFetcherInvalidData userInfo:userInfo];
76 | dispatch_async(dispatch_get_main_queue(), ^{
77 | failureBlock(error);
78 | });
79 | return;
80 | }
81 |
82 | dispatch_async(dispatch_get_main_queue(), ^{
83 | if (strongSelf->_cancelled) return;
84 |
85 | successBlock(image);
86 | });
87 | });
88 | }
89 |
90 | - (void)cancelFetch
91 | {
92 | _cancelled = YES;
93 | }
94 |
95 | - (void)dealloc
96 | {
97 | [self cancelFetch];
98 | }
99 |
100 | @end
101 |
--------------------------------------------------------------------------------
/Vendor/Haneke/UIView+Haneke.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Haneke.m
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 8/20/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import "UIView+Haneke.h"
22 | #import "HNKCache.h"
23 | #import
24 |
25 | const CGFloat HNKViewFormatCompressionQuality = 0.75;
26 | const unsigned long long HNKViewFormatDiskCapacity = 10 * 1024 * 1024;
27 |
28 | static NSString *NSStringFromHNKScaleMode(HNKScaleMode scaleMode)
29 | {
30 | switch (scaleMode) {
31 | case HNKScaleModeFill:
32 | return @"fill";
33 | case HNKScaleModeAspectFill:
34 | return @"aspectfill";
35 | case HNKScaleModeAspectFit:
36 | return @"aspectfit";
37 | case HNKScaleModeNone:
38 | return @"scalenone";
39 | }
40 | return nil;
41 | }
42 |
43 | @implementation UIView (Haneke)
44 |
45 | - (HNKScaleMode)hnk_scaleMode
46 | {
47 | switch (self.contentMode) {
48 | case UIViewContentModeScaleToFill:
49 | return HNKScaleModeFill;
50 | case UIViewContentModeScaleAspectFit:
51 | return HNKScaleModeAspectFit;
52 | case UIViewContentModeScaleAspectFill:
53 | return HNKScaleModeAspectFill;
54 | case UIViewContentModeRedraw:
55 | case UIViewContentModeCenter:
56 | case UIViewContentModeTop:
57 | case UIViewContentModeBottom:
58 | case UIViewContentModeLeft:
59 | case UIViewContentModeRight:
60 | case UIViewContentModeTopLeft:
61 | case UIViewContentModeTopRight:
62 | case UIViewContentModeBottomLeft:
63 | case UIViewContentModeBottomRight:
64 | return HNKScaleModeNone;
65 | }
66 | }
67 |
68 | @end
69 |
70 | @implementation HNKCache(UIView)
71 |
72 | + (void)registerSharedFormat:(HNKCacheFormat*)format
73 | {
74 | HNKCache *cache = [HNKCache sharedCache];
75 | if (cache.formats[format.name] != format)
76 | {
77 | [[HNKCache sharedCache] registerFormat:format];
78 | }
79 | }
80 |
81 | + (HNKCacheFormat*)sharedFormatWithSize:(CGSize)size scaleMode:(HNKScaleMode)scaleMode
82 | {
83 | NSString *scaleModeName = NSStringFromHNKScaleMode(scaleMode);
84 | NSString *name = [NSString stringWithFormat:@"auto-%ldx%ld-%@", (long)size.width, (long)size.height, scaleModeName];
85 | HNKCache *cache = [HNKCache sharedCache];
86 | HNKCacheFormat *format = cache.formats[name];
87 | if (!format)
88 | {
89 | format = [[HNKCacheFormat alloc] initWithName:name];
90 | format.size = size;
91 | format.diskCapacity = HNKViewFormatDiskCapacity;
92 | format.allowUpscaling = YES;
93 | format.compressionQuality = HNKViewFormatCompressionQuality;
94 | format.scaleMode = scaleMode;
95 | [cache registerFormat:format];
96 | }
97 | return format;
98 | }
99 |
100 | @end
101 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/Vendor/Haneke/UIView+Haneke.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Haneke.m
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 8/20/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import "UIView+Haneke.h"
22 | #import "HNKCache.h"
23 | #import
24 |
25 | const CGFloat HNKViewFormatCompressionQuality = 0.75;
26 | const unsigned long long HNKViewFormatDiskCapacity = 10 * 1024 * 1024;
27 |
28 | static NSString *NSStringFromHNKScaleMode(HNKScaleMode scaleMode)
29 | {
30 | switch (scaleMode) {
31 | case HNKScaleModeFill:
32 | return @"fill";
33 | case HNKScaleModeAspectFill:
34 | return @"aspectfill";
35 | case HNKScaleModeAspectFit:
36 | return @"aspectfit";
37 | case HNKScaleModeNone:
38 | return @"scalenone";
39 | }
40 | return nil;
41 | }
42 |
43 | @implementation UIView (Haneke)
44 |
45 | - (HNKScaleMode)hnk_scaleMode
46 | {
47 | switch (self.contentMode) {
48 | case UIViewContentModeScaleToFill:
49 | return HNKScaleModeFill;
50 | case UIViewContentModeScaleAspectFit:
51 | return HNKScaleModeAspectFit;
52 | case UIViewContentModeScaleAspectFill:
53 | return HNKScaleModeAspectFill;
54 | case UIViewContentModeRedraw:
55 | case UIViewContentModeCenter:
56 | case UIViewContentModeTop:
57 | case UIViewContentModeBottom:
58 | case UIViewContentModeLeft:
59 | case UIViewContentModeRight:
60 | case UIViewContentModeTopLeft:
61 | case UIViewContentModeTopRight:
62 | case UIViewContentModeBottomLeft:
63 | case UIViewContentModeBottomRight:
64 | return HNKScaleModeNone;
65 | }
66 | }
67 |
68 | @end
69 |
70 | @implementation HNKCache(UIView)
71 |
72 | + (void)registerSharedFormat:(HNKCacheFormat*)format
73 | {
74 | HNKCache *cache = [HNKCache sharedCache];
75 | if (cache.formats[format.name] != format)
76 | {
77 | [[HNKCache sharedCache] registerFormat:format];
78 | }
79 | }
80 |
81 | + (HNKCacheFormat*)sharedFormatWithSize:(CGSize)size scaleMode:(HNKScaleMode)scaleMode
82 | {
83 | NSString *scaleModeName = NSStringFromHNKScaleMode(scaleMode);
84 | NSString *name = [NSString stringWithFormat:@"auto-%ldx%ld-%@", (long)size.width, (long)size.height, scaleModeName];
85 | HNKCache *cache = [HNKCache sharedCache];
86 | HNKCacheFormat *format = cache.formats[name];
87 | if (!format)
88 | {
89 | format = [[HNKCacheFormat alloc] initWithName:name];
90 | format.size = size;
91 | format.diskCapacity = HNKViewFormatDiskCapacity;
92 | format.allowUpscaling = YES;
93 | format.compressionQuality = HNKViewFormatCompressionQuality;
94 | format.scaleMode = scaleMode;
95 | [cache registerFormat:format];
96 | }
97 | return format;
98 | }
99 |
100 | @end
101 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemo/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/Vendor/Haneke/HNKNetworkFetcher.m:
--------------------------------------------------------------------------------
1 | //
2 | // HNKNetworkFetcher.m
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 7/23/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import "HNKNetworkFetcher.h"
22 |
23 | @implementation HNKNetworkFetcher {
24 | NSURL *_URL;
25 | BOOL _cancelled;
26 | NSURLSessionDataTask *_dataTask;
27 | }
28 |
29 | - (instancetype)initWithURL:(NSURL*)URL
30 | {
31 | if (self = [super init])
32 | {
33 | _URL = URL;
34 | }
35 | return self;
36 | }
37 |
38 | - (NSString*)key
39 | {
40 | return _URL.absoluteString;
41 | }
42 |
43 | - (void)fetchImageWithSuccess:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
44 | {
45 | _cancelled = NO;
46 | __weak __typeof__(self) weakSelf = self;
47 | _dataTask = [self.URLSession dataTaskWithURL:_URL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
48 | __strong __typeof__(weakSelf) strongSelf = weakSelf;
49 |
50 | if (!strongSelf) return;
51 |
52 | if (strongSelf->_cancelled) return;
53 |
54 | NSURL *URL = strongSelf->_URL;
55 |
56 | if (error)
57 | {
58 | if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) return;
59 |
60 | HanekeLog(@"Request %@ failed with error %@", URL.absoluteString, error);
61 | if (!failureBlock) return;
62 |
63 | dispatch_async(dispatch_get_main_queue(), ^{
64 | failureBlock(error);
65 | });
66 | return;
67 | }
68 |
69 | if (![response isKindOfClass:NSHTTPURLResponse.class])
70 | {
71 | HanekeLog(@"Request %@ received unknown response %@", URL.absoluteString, response);
72 | return;
73 | }
74 |
75 | NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
76 | if (httpResponse.statusCode != 200)
77 | {
78 | NSString *errorDescription = [NSHTTPURLResponse localizedStringForStatusCode:httpResponse.statusCode];
79 | [strongSelf failWithLocalizedDescription:errorDescription code:HNKErrorNetworkFetcherInvalidStatusCode block:failureBlock];
80 | return;
81 | }
82 |
83 | const long long expectedContentLength = response.expectedContentLength;
84 | if (expectedContentLength > -1)
85 | {
86 | const NSUInteger dataLength = data.length;
87 | if (dataLength < expectedContentLength)
88 | {
89 | NSString *errorDescription = [NSString stringWithFormat:NSLocalizedString(@"Request %@ received %ld out of %ld bytes", @""), URL.absoluteString, (long)dataLength, (long)expectedContentLength];
90 | [strongSelf failWithLocalizedDescription:errorDescription code:HNKErrorNetworkFetcherMissingData block:failureBlock];
91 | return;
92 | }
93 | }
94 |
95 | UIImage *image = [UIImage imageWithData:data];
96 |
97 | if (!image)
98 | {
99 | NSString *errorDescription = [NSString stringWithFormat:NSLocalizedString(@"Failed to load image from data at URL %@", @""), URL];
100 | [strongSelf failWithLocalizedDescription:errorDescription code:HNKErrorNetworkFetcherInvalidData block:failureBlock];
101 | return;
102 | }
103 |
104 | dispatch_async(dispatch_get_main_queue(), ^{
105 | successBlock(image);
106 | });
107 |
108 | }];
109 | [_dataTask resume];
110 | }
111 |
112 | - (void)cancelFetch
113 | {
114 | [_dataTask cancel];
115 | _cancelled = YES;
116 | }
117 |
118 | - (void)dealloc
119 | {
120 | [self cancelFetch];
121 | }
122 |
123 | #pragma mark Private
124 |
125 | - (void)failWithLocalizedDescription:(NSString*)localizedDescription code:(NSInteger)code block:(void (^)(NSError *error))failureBlock;
126 | {
127 | HanekeLog(@"%@", localizedDescription);
128 | if (!failureBlock) return;
129 |
130 | NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : localizedDescription , NSURLErrorKey : _URL};
131 | NSError *error = [NSError errorWithDomain:HNKErrorDomain code:code userInfo:userInfo];
132 | dispatch_async(dispatch_get_main_queue(), ^{
133 | failureBlock(error);
134 | });
135 | }
136 |
137 | @end
138 |
139 | @implementation HNKNetworkFetcher(Subclassing)
140 |
141 | - (NSURLSession*)URLSession
142 | {
143 | return [NSURLSession sharedSession];
144 | }
145 |
146 | @end
147 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/Vendor/Haneke/HNKNetworkFetcher.m:
--------------------------------------------------------------------------------
1 | //
2 | // HNKNetworkFetcher.m
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 7/23/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import "HNKNetworkFetcher.h"
22 |
23 | @implementation HNKNetworkFetcher {
24 | NSURL *_URL;
25 | BOOL _cancelled;
26 | NSURLSessionDataTask *_dataTask;
27 | }
28 |
29 | - (instancetype)initWithURL:(NSURL*)URL
30 | {
31 | if (self = [super init])
32 | {
33 | _URL = URL;
34 | }
35 | return self;
36 | }
37 |
38 | - (NSString*)key
39 | {
40 | return _URL.absoluteString;
41 | }
42 |
43 | - (void)fetchImageWithSuccess:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
44 | {
45 | _cancelled = NO;
46 | __weak __typeof__(self) weakSelf = self;
47 | _dataTask = [self.URLSession dataTaskWithURL:_URL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
48 | __strong __typeof__(weakSelf) strongSelf = weakSelf;
49 |
50 | if (!strongSelf) return;
51 |
52 | if (strongSelf->_cancelled) return;
53 |
54 | NSURL *URL = strongSelf->_URL;
55 |
56 | if (error)
57 | {
58 | if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) return;
59 |
60 | HanekeLog(@"Request %@ failed with error %@", URL.absoluteString, error);
61 | if (!failureBlock) return;
62 |
63 | dispatch_async(dispatch_get_main_queue(), ^{
64 | failureBlock(error);
65 | });
66 | return;
67 | }
68 |
69 | if (![response isKindOfClass:NSHTTPURLResponse.class])
70 | {
71 | HanekeLog(@"Request %@ received unknown response %@", URL.absoluteString, response);
72 | return;
73 | }
74 |
75 | NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
76 | if (httpResponse.statusCode != 200)
77 | {
78 | NSString *errorDescription = [NSHTTPURLResponse localizedStringForStatusCode:httpResponse.statusCode];
79 | [strongSelf failWithLocalizedDescription:errorDescription code:HNKErrorNetworkFetcherInvalidStatusCode block:failureBlock];
80 | return;
81 | }
82 |
83 | const long long expectedContentLength = response.expectedContentLength;
84 | if (expectedContentLength > -1)
85 | {
86 | const NSUInteger dataLength = data.length;
87 | if (dataLength < expectedContentLength)
88 | {
89 | NSString *errorDescription = [NSString stringWithFormat:NSLocalizedString(@"Request %@ received %ld out of %ld bytes", @""), URL.absoluteString, (long)dataLength, (long)expectedContentLength];
90 | [strongSelf failWithLocalizedDescription:errorDescription code:HNKErrorNetworkFetcherMissingData block:failureBlock];
91 | return;
92 | }
93 | }
94 |
95 | UIImage *image = [UIImage imageWithData:data];
96 |
97 | if (!image)
98 | {
99 | NSString *errorDescription = [NSString stringWithFormat:NSLocalizedString(@"Failed to load image from data at URL %@", @""), URL];
100 | [strongSelf failWithLocalizedDescription:errorDescription code:HNKErrorNetworkFetcherInvalidData block:failureBlock];
101 | return;
102 | }
103 |
104 | dispatch_async(dispatch_get_main_queue(), ^{
105 | successBlock(image);
106 | });
107 |
108 | }];
109 | [_dataTask resume];
110 | }
111 |
112 | - (void)cancelFetch
113 | {
114 | [_dataTask cancel];
115 | _cancelled = YES;
116 | }
117 |
118 | - (void)dealloc
119 | {
120 | [self cancelFetch];
121 | }
122 |
123 | #pragma mark Private
124 |
125 | - (void)failWithLocalizedDescription:(NSString*)localizedDescription code:(NSInteger)code block:(void (^)(NSError *error))failureBlock;
126 | {
127 | HanekeLog(@"%@", localizedDescription);
128 | if (!failureBlock) return;
129 |
130 | NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : localizedDescription , NSURLErrorKey : _URL};
131 | NSError *error = [NSError errorWithDomain:HNKErrorDomain code:code userInfo:userInfo];
132 | dispatch_async(dispatch_get_main_queue(), ^{
133 | failureBlock(error);
134 | });
135 | }
136 |
137 | @end
138 |
139 | @implementation HNKNetworkFetcher(Subclassing)
140 |
141 | - (NSURLSession*)URLSession
142 | {
143 | return [NSURLSession sharedSession];
144 | }
145 |
146 | @end
147 |
--------------------------------------------------------------------------------
/Vendor/Haneke/HNKDiskCache.h:
--------------------------------------------------------------------------------
1 | //
2 | // HNKDiskCache.h
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 8/21/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import
22 |
23 | /**
24 | A least-recently-used disk cache for data that performs all of its operations asynchronously in its own queue. The least recently used data is automatically deleted from disk when the cache capacity is reached.
25 | @discussion HNKDiskCache is a generic-purpose disk cache and can be used independently of the rest of Haneke.
26 | */
27 | @interface HNKDiskCache : NSObject
28 |
29 | #pragma mark Initializing the cache
30 | ///---------------------------------------------
31 | /// @name Initializing the cache
32 | ///---------------------------------------------
33 |
34 | /**
35 | Initializes a cache with the given directory and capacity. Upon being initialized the cache will inmmediately calculate its size and, if needed to keep it below capacity, delete the least recently used data.
36 | @param directory Path of an existing directory in which the cache will write data. Once initialized you should not read and write to this directory.
37 | @param capacity Capacity in bytes. If the cache size exceeds its capacity the least recently used data will be deleted until it doesn't.
38 | */
39 | - (instancetype)initWithDirectory:(NSString*)directory capacity:(unsigned long long)capacity;
40 |
41 | /**
42 | Cache capacity in bytes. If the cache size exceeds its capacity the least recently used data will be deleted until it doesn't.
43 | */
44 | @property (nonatomic, assign) unsigned long long capacity;
45 |
46 | /**
47 | Cache size in bytes. If the cache size exceeds its capacity the least recently used data will be deleted until it doesn't.
48 | */
49 | @property (nonatomic, readonly) unsigned long long size;
50 |
51 | /**
52 | Serial queue used by the cache to perform all of its operations asynchronously.
53 | @discussion Blocks dispatched in this queue will always run after the previous cache operation has completed.
54 | */
55 | @property (nonatomic, readonly) dispatch_queue_t queue;
56 |
57 | #pragma mark Setting and fetching data
58 | ///---------------------------------------------
59 | /// @name Setting and fetching data
60 | ///---------------------------------------------
61 |
62 | /**
63 | Asynchronously sets the given data for the given key. Upon completition the cache size will be updated.
64 | @param data Data to be cached.
65 | @param key Key to be associated with the data.
66 | @discussion If the cache size exceeds its capacity the least recently used data will be deleted until it doesn't.
67 | */
68 | - (void)setData:(NSData*)data forKey:(NSString*)key;
69 |
70 | /**
71 | Fetches the data associated with the given key and updates its access date.
72 | @param key Key associated with requested data.
73 | @param successBlock Block to be called with the requested data. Always called from the main queue.
74 | @param failureBlock Block to be called if there is no data associated with the given key or, less likely, if there is an error while reading the data. Always called from the main queue. If no data is found the error will be NSFileReadNoSuchFileError.
75 | */
76 | - (void)fetchDataForKey:(NSString*)key success:(void (^)(NSData *data))successBlock failure:(void (^)(NSError *error))failureBlock;
77 |
78 | #pragma mark Removing data
79 | ///---------------------------------------------
80 | /// @name Removing data
81 | ///---------------------------------------------
82 |
83 | /**
84 | Asynchronously removes the data for the given key. Upon completition the cache size will be updated.
85 | @param key Key associated with the data to be removed.
86 | */
87 | - (void)removeDataForKey:(NSString*)key;
88 |
89 | /**
90 | Asynchronously removes all data from the cache. Upon completition the cache size will be zero.
91 | */
92 | - (void)removeAllData;
93 |
94 | #pragma mark Managing data by access date
95 | ///---------------------------------------------
96 | /// @name Managing data by access date
97 | ///---------------------------------------------
98 |
99 | /**
100 | Asynchronously enumerates all key-data pairs in the cache by access date in descending order.
101 | @param block Block to apply to elements in the array. Called from the cache queue.
102 | */
103 | - (void)enumerateDataByAccessDateUsingBlock:(void(^)(NSString *key, NSData *data, NSDate *accessDate, BOOL *stop))block;
104 |
105 | /**
106 | Asynchronously updates the access date for the given key.
107 | @param key Key associated with the data whose access date will be updated.
108 | @param lazyData Block to be called if there is no data associated with the given key, in which case it must return it. Called from the cache queue.
109 | @discussion Calling this method is equivalent to calling setData:forKey: with the difference that the data is only requested if it isn't already cached.
110 | */
111 | - (void)updateAccessDateForKey:(NSString*)key data:(NSData* (^)())lazyData ;
112 |
113 | @end
114 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemo/MDCustomAutoCompleteCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
25 |
26 |
27 |
28 |
29 |
36 |
43 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/Vendor/Haneke/HNKDiskCache.h:
--------------------------------------------------------------------------------
1 | //
2 | // HNKDiskCache.h
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 8/21/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import
22 |
23 | /**
24 | A least-recently-used disk cache for data that performs all of its operations asynchronously in its own queue. The least recently used data is automatically deleted from disk when the cache capacity is reached.
25 | @discussion HNKDiskCache is a generic-purpose disk cache and can be used independently of the rest of Haneke.
26 | */
27 | @interface HNKDiskCache : NSObject
28 |
29 | #pragma mark Initializing the cache
30 | ///---------------------------------------------
31 | /// @name Initializing the cache
32 | ///---------------------------------------------
33 |
34 | /**
35 | Initializes a cache with the given directory and capacity. Upon being initialized the cache will inmmediately calculate its size and, if needed to keep it below capacity, delete the least recently used data.
36 | @param directory Path of an existing directory in which the cache will write data. Once initialized you should not read and write to this directory.
37 | @param capacity Capacity in bytes. If the cache size exceeds its capacity the least recently used data will be deleted until it doesn't.
38 | */
39 | - (instancetype)initWithDirectory:(NSString*)directory capacity:(unsigned long long)capacity;
40 |
41 | /**
42 | Cache capacity in bytes. If the cache size exceeds its capacity the least recently used data will be deleted until it doesn't.
43 | */
44 | @property (nonatomic, assign) unsigned long long capacity;
45 |
46 | /**
47 | Cache size in bytes. If the cache size exceeds its capacity the least recently used data will be deleted until it doesn't.
48 | */
49 | @property (nonatomic, readonly) unsigned long long size;
50 |
51 | /**
52 | Serial queue used by the cache to perform all of its operations asynchronously.
53 | @discussion Blocks dispatched in this queue will always run after the previous cache operation has completed.
54 | */
55 | @property (nonatomic, readonly) dispatch_queue_t queue;
56 |
57 | #pragma mark Setting and fetching data
58 | ///---------------------------------------------
59 | /// @name Setting and fetching data
60 | ///---------------------------------------------
61 |
62 | /**
63 | Asynchronously sets the given data for the given key. Upon completition the cache size will be updated.
64 | @param data Data to be cached.
65 | @param key Key to be associated with the data.
66 | @discussion If the cache size exceeds its capacity the least recently used data will be deleted until it doesn't.
67 | */
68 | - (void)setData:(NSData*)data forKey:(NSString*)key;
69 |
70 | /**
71 | Fetches the data associated with the given key and updates its access date.
72 | @param key Key associated with requested data.
73 | @param successBlock Block to be called with the requested data. Always called from the main queue.
74 | @param failureBlock Block to be called if there is no data associated with the given key or, less likely, if there is an error while reading the data. Always called from the main queue. If no data is found the error will be NSFileReadNoSuchFileError.
75 | */
76 | - (void)fetchDataForKey:(NSString*)key success:(void (^)(NSData *data))successBlock failure:(void (^)(NSError *error))failureBlock;
77 |
78 | #pragma mark Removing data
79 | ///---------------------------------------------
80 | /// @name Removing data
81 | ///---------------------------------------------
82 |
83 | /**
84 | Asynchronously removes the data for the given key. Upon completition the cache size will be updated.
85 | @param key Key associated with the data to be removed.
86 | */
87 | - (void)removeDataForKey:(NSString*)key;
88 |
89 | /**
90 | Asynchronously removes all data from the cache. Upon completition the cache size will be zero.
91 | */
92 | - (void)removeAllData;
93 |
94 | #pragma mark Managing data by access date
95 | ///---------------------------------------------
96 | /// @name Managing data by access date
97 | ///---------------------------------------------
98 |
99 | /**
100 | Asynchronously enumerates all key-data pairs in the cache by access date in descending order.
101 | @param block Block to apply to elements in the array. Called from the cache queue.
102 | */
103 | - (void)enumerateDataByAccessDateUsingBlock:(void(^)(NSString *key, NSData *data, NSDate *accessDate, BOOL *stop))block;
104 |
105 | /**
106 | Asynchronously updates the access date for the given key.
107 | @param key Key associated with the data whose access date will be updated.
108 | @param lazyData Block to be called if there is no data associated with the given key, in which case it must return it. Called from the cache queue.
109 | @discussion Calling this method is equivalent to calling setData:forKey: with the difference that the data is only requested if it isn't already cached.
110 | */
111 | - (void)updateAccessDateForKey:(NSString*)key data:(NSData* (^)())lazyData ;
112 |
113 | @end
114 |
--------------------------------------------------------------------------------
/MJAutoComplete/MJAutoCompleteManager.h:
--------------------------------------------------------------------------------
1 | //
2 | // MJAutoCompleteManager.h
3 | // MJAutoCompleteDemo
4 | //
5 | // Created by Mazyad Alabduljaleel on 11/9/13.
6 | // Copyright (c) 2013 ArabianDevs. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "MJAutoCompleteItem.h"
11 | #import "MJAutoCompleteTrigger.h"
12 |
13 | @class MJAutoCompleteManager;
14 | @class MJAutoCompleteTC;
15 |
16 | typedef void(^MJAutoCompleteListCallback)(NSArray* list);
17 |
18 | /**
19 | The dataSource is completely optional. You can assign the items in advance with the triggers, or implement these methods to provide the list as they are required.
20 |
21 | NOTE:
22 | • The list of strings can be accomplished by creating an item list and just assigning the stringValue.
23 | **/
24 | @protocol MJAutoCompleteManagerDataSource
25 | @optional
26 | /** This method requests the list of items to be shown based on the delimiter and string associated. A callback block is provided to give the convenience of fetching the list asynchronously, if needed **/
27 | - (void)autoCompleteManager:(MJAutoCompleteManager *)acManager
28 | itemListForTrigger:(MJAutoCompleteTrigger *)trigger
29 | withString:(NSString *)string
30 | callback:(MJAutoCompleteListCallback)callback;
31 |
32 | /** After the list is retrieved from the dataSource, it is sent back again to the dataSource to be filtered using whichever algorithm the dataSource prefers. If not implemented, it defaults to simple string matching the list retrieved with the captured string **/
33 | - (void)autoCompleteManager:(MJAutoCompleteManager *)acManager
34 | filterList:(NSMutableArray *)list
35 | forTrigger:(MJAutoCompleteTrigger *)trigger
36 | withString:(NSString *)string;
37 |
38 | @end
39 |
40 | @protocol MJAutoCompleteManagerDelegate
41 |
42 | @optional
43 | /** The user has selected an item on the autoComplete table view, and we inform the delegate through one of the methods below. You must implement one of them! **/
44 | - (void)autoCompleteManager:(MJAutoCompleteManager *)acManager
45 | shouldUpdateToText:(NSString *)newText;
46 |
47 | - (void)autoCompleteManager:(MJAutoCompleteManager *)acManager
48 | shouldUpdateToText:(NSString *)newText
49 | selectedItem:(MJAutoCompleteItem *)selectedItem;
50 |
51 | /** For the sake of lazy loading, we notify the delegate when an autoComplete cell will be presented, so the developer can postpone fetching the image until the cell is actually going to be presented to the user. **/
52 | - (void)autoCompleteManager:(MJAutoCompleteManager *)acManager
53 | willPresentCell:(id)autoCompleteCell
54 | forTrigger:(MJAutoCompleteTrigger *)trigger;
55 | /** Additional delegate methods when the autoComplete view will (dis)appear. **/
56 | - (void)autoCompleteManagerViewWillAppear:(MJAutoCompleteManager *)acManager;
57 | - (void)autoCompleteManagerViewWillDisappear:(MJAutoCompleteManager *)acManager;
58 |
59 | @end
60 |
61 | /**
62 | CLASS DESCRIPTION:
63 | ==================
64 |
65 | This is a single entry point for the developer who
66 | wants to use the MJAutoCompleteComponent. An instance of
67 | this class should be instantiated and retained by the
68 | developer to control the overall behavior of the component
69 | as well as pass data back and forth between the developer's
70 | code and the component.
71 |
72 | Even though the autoComplete component is mostly a list
73 | of items, making the component's top level object a
74 | tableViewController might cause rigidness and break the SRP
75 | design.
76 |
77 | STEPS:
78 | ------
79 | 1 - Instantiate an instance or add in NIB
80 | 2 - Assign a datasource & delegate
81 | 3 - When avaiable, assign the container view.
82 | 4 - Add some delimiters
83 | 5 - call (processString:) directly.
84 | 6 - On delimiter, itemListForDelimiter: is called if found, or trigger.list is used.
85 | 7 - shouldUpdateText: is called with the WHOLE text. Just replace what you have.
86 |
87 | **/
88 |
89 | @interface MJAutoCompleteManager : NSObject
90 |
91 | /*=== COMPONENT SETUP ===*/
92 | @property (weak, nonatomic) IBOutlet id dataSource;
93 | @property (weak, nonatomic) IBOutlet id delegate;
94 | /** The container must be set so the auto complete manager knows where to position it's table view. The table view will have an autoresizing mask of flexible width | flexible height to change with the container. **/
95 | @property (weak, nonatomic) IBOutlet UIView* container;
96 |
97 | /*=== COMPONENT CUSTOMIZATION ===*/
98 | /** Context set by the component owner, and can be accessed by the delegate/datasource for custom action **/
99 | @property (strong, nonatomic) NSDictionary *context;
100 | /** Only for checking what triggers were added. Use -addAutoCompleteDelimiter: and removeAutoCompleteDelimiter: to indirectly manipulate the array **/
101 | @property (readonly, nonatomic) NSSet* triggers;
102 | /** Ability to reverse autocomplete table scroll direction **/
103 | @property (getter=isScrollDirectionReversed, nonatomic) BOOL scrollDirectionReversed;
104 | /** Use explicit methods to add and remove delimiters to enforce type checking and protect the internal mutable array. **/
105 | - (void)addAutoCompleteTrigger:(MJAutoCompleteTrigger *)trigger;
106 | - (void)removeAutoCompleteTrigger:(MJAutoCompleteTrigger *)trigger;
107 | - (void)removeAllAutoCompleteTriggers;
108 | /** Send the string as it is typed by the user, and the autoComplete table will be displayed when a delimiter is found at the end of the string **/
109 | - (void)processString:(NSString *)string;
110 |
111 | @end
112 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/MJAutoCompleteDemo/MDViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MDViewController.m
3 | // MJAutoCompleteDemo
4 | //
5 | // Created by Mazyad Alabduljaleel on 2/18/14.
6 | // Copyright (c) 2014 ArabianDevs. All rights reserved.
7 | //
8 |
9 | #import "MDViewController.h"
10 | #import "MDCustomAutoCompleteCell.h"
11 | #import "UIImageView+Haneke.h"
12 |
13 | @interface MDViewController ()
14 |
15 | @property (nonatomic) BOOL didStartDownload;
16 |
17 | @property (strong, nonatomic) MJAutoCompleteManager *autoCompleteMgr;
18 |
19 | @property (weak, nonatomic) IBOutlet UIView *autoCompleteContainer;
20 | @property (weak, nonatomic) IBOutlet UITextView *textView;
21 |
22 | @end
23 |
24 | @implementation MDViewController
25 |
26 | - (id)initWithCoder:(NSCoder *)aDecoder
27 | {
28 | self = [super initWithCoder:aDecoder];
29 | if (self)
30 | {
31 | self.autoCompleteMgr = [[MJAutoCompleteManager alloc] init];
32 | self.autoCompleteMgr.dataSource = self;
33 | self.autoCompleteMgr.delegate = self;
34 | /* Add some autoComplete triggers */
35 | // let's get the names of the countries
36 | NSString *path = [[NSBundle mainBundle] pathForResource:@"Countries" ofType:@"plist"];
37 | NSArray *names = [[NSArray arrayWithContentsOfFile:path] valueForKey:@"name"];
38 | NSArray *items = [MJAutoCompleteItem autoCompleteCellModelFromObjects:names];
39 |
40 | // then assign them to the trigger
41 | /* For the # trigger, we demonstrate the absolute easiest way to get started */
42 | MJAutoCompleteTrigger *hashTrigger = [[MJAutoCompleteTrigger alloc] initWithDelimiter:@"#"
43 | autoCompleteItems:items];
44 | /* For the @ trigger, it is much more complex, with lots of async thingies */
45 | MJAutoCompleteTrigger *atTrigger = [[MJAutoCompleteTrigger alloc] initWithDelimiter:@"@"];
46 | atTrigger.cell = @"MDCustomAutoCompleteCell";
47 | // assign them triggers to autoCompleteMgr
48 | [self.autoCompleteMgr addAutoCompleteTrigger:hashTrigger];
49 | [self.autoCompleteMgr addAutoCompleteTrigger:atTrigger];
50 | }
51 | return self;
52 | }
53 |
54 | - (void)viewDidLoad
55 | {
56 | [super viewDidLoad];
57 | // hook up the container with the manager
58 | self.autoCompleteMgr.container = self.autoCompleteContainer;
59 | // and hook up the textView delegate
60 | self.textView.delegate = self;
61 | }
62 |
63 | #pragma mark - UITextView Delegate methods
64 |
65 | - (void)textViewDidChange:(UITextView *)textView
66 | {
67 | [self.autoCompleteMgr processString:textView.text];
68 | }
69 |
70 | #pragma mark - MJAutoCompleteMgr DataSource Methods
71 |
72 | - (void)autoCompleteManager:(MJAutoCompleteManager *)acManager
73 | itemListForTrigger:(MJAutoCompleteTrigger *)trigger
74 | withString:(NSString *)string
75 | callback:(MJAutoCompleteListCallback)callback
76 | {
77 | /* Since we implemented this method, we are stuck and must handle ALL triggers */
78 | // the # trigger is trivial:
79 | if ([trigger.delimiter isEqual:@"#"])
80 | {
81 | // we already provided the list
82 | callback(trigger.autoCompleteItemList);
83 | }
84 | else if ([trigger.delimiter isEqual:@"@"])
85 | {
86 | if (!self.didStartDownload)
87 | {
88 | self.didStartDownload = YES;
89 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
90 | {
91 | NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://alltheragefaces.com/api/all/faces"]];
92 | NSArray *jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
93 |
94 | NSMutableArray *itemList = [NSMutableArray array];
95 | for (NSDictionary *dict in jsonData)
96 | {
97 | // Use DICT_GET to handle NSNull cases
98 | NSString * acString = DICT_GET(dict, @"title");
99 | acString = [acString stringByReplacingOccurrencesOfString:@" " withString:@""];
100 |
101 | MJAutoCompleteItem *item = [[MJAutoCompleteItem alloc] init];
102 | item.autoCompleteString = acString;
103 | item.context = dict;
104 |
105 | [itemList addObject:item];
106 | }
107 |
108 | dispatch_async(dispatch_get_main_queue(), ^
109 | {
110 | trigger.autoCompleteItemList = itemList;
111 | callback(itemList);
112 | });
113 | });
114 | }
115 | else
116 | {
117 | callback(trigger.autoCompleteItemList);
118 | }
119 | }
120 | }
121 |
122 | #pragma mark - MJAutoCompleteMgr Delegate methods
123 |
124 | - (void)autoCompleteManager:(MJAutoCompleteManager *)acManager
125 | willPresentCell:(id)autoCompleteCell
126 | forTrigger:(MJAutoCompleteTrigger *)trigger
127 | {
128 | if ([trigger.delimiter isEqual:@"@"])
129 | {
130 | MDCustomAutoCompleteCell *cell = autoCompleteCell;
131 | NSDictionary *context = cell.autoCompleteItem.context;
132 |
133 | [cell.avatarImageView hnk_setImageFromURL:[NSURL URLWithString:DICT_GET(context, @"jpg")]
134 | placeholder:[UIImage imageNamed:@"placeholder"]];
135 | }
136 | }
137 |
138 | - (void)autoCompleteManager:(MJAutoCompleteManager *)acManager shouldUpdateToText:(NSString *)newText
139 | {
140 | self.textView.text = newText;
141 | }
142 |
143 | #pragma mark -
144 |
145 | @end
146 |
--------------------------------------------------------------------------------
/MJAutoComplete.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod spec lint MJAutoComplete.podspec' to ensure this is a
3 | # valid spec and to remove all comments including this before submitting the spec.
4 | #
5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html
6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/
7 | #
8 |
9 | Pod::Spec.new do |s|
10 |
11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
12 | #
13 | # These will help people to find your library, and whilst it
14 | # can feel like a chore to fill in it's definitely to your advantage. The
15 | # summary should be tweet-length, and the description more in depth.
16 | #
17 |
18 | s.name = "MJAutoComplete"
19 | s.version = "1.1.1"
20 | s.summary = "Simple drop-in for using autocomplete component on iOS."
21 |
22 | s.description = <<-DESC
23 |
24 | The idea behind this component is to manage the relationship between the controller (with the model) and the auto complete view.
25 |
26 | + **Triggers**: which are simple invocations that happen when a delimiter is entered by the user. The trigger can either contain the data beforehand, and will do everything for you, or you can provide the data dynamically using the data source.
27 |
28 | + **Custom Cells**: You can easily assign custom cells for the component, and use a "user info" dictionary that will be passed to your cell to initialize it.
29 |
30 | + **Centralized**: You can easily centralize the manager and the data source, so you can simply instantiate the `MJAutoCompleteManager` anywhere to present the view, but then use that centralized data source to feed in the data.
31 |
32 | ... And much, much more, so please check out the fully documented github demo to learn more.
33 |
34 | DESC
35 |
36 | s.homepage = "https://github.com/Mazyod/MJAutoComplete"
37 | s.screenshots = "https://raw.githubusercontent.com/Mazyod/MJAutoComplete/master/resources/MJAutoComplete-demo.gif"
38 |
39 |
40 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
41 | #
42 | # Licensing your code is important. See http://choosealicense.com for more info.
43 | # CocoaPods will detect a license file if there is a named LICENSE*
44 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'.
45 | #
46 |
47 | s.license = "MIT"
48 | s.license = { :type => "MIT", :file => "LICENSE" }
49 |
50 |
51 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
52 | #
53 | # Specify the authors of the library, with email addresses. Email addresses
54 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also
55 | # accepts just a name if you'd rather not provide an email address.
56 | #
57 | # Specify a social_media_url where others can refer to, for example a twitter
58 | # profile URL.
59 | #
60 |
61 | s.author = { "Mazyad Alabduljaleel" => "mazjaleel@gmail.com" }
62 | s.social_media_url = "http://twitter.com/Mazyod"
63 |
64 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
65 | #
66 | # If this Pod runs only on iOS or OS X, then specify the platform and
67 | # the deployment target. You can optionally include the target after the platform.
68 | #
69 |
70 | s.platform = :ios, "6.0"
71 | # s.platform = :ios, "5.0"
72 |
73 | # When using multiple platforms
74 | # s.ios.deployment_target = "5.0"
75 | # s.osx.deployment_target = "10.7"
76 |
77 |
78 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
79 | #
80 | # Specify the location from where the source should be retrieved.
81 | # Supports git, hg, bzr, svn and HTTP.
82 | #
83 |
84 | s.source = { :git => "https://github.com/Mazyod/MJAutoComplete.git", :tag => "1.1.1" }
85 |
86 |
87 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
88 | #
89 | # CocoaPods is smart about how it includes source code. For source files
90 | # giving a folder will include any h, m, mm, c & cpp files. For header
91 | # files it will include any header in the folder.
92 | # Not including the public_header_files will make all headers public.
93 | #
94 |
95 | s.source_files = "MJAutoComplete"
96 | s.exclude_files = ""
97 |
98 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
99 | #
100 | # A list of resources included with the Pod. These are copied into the
101 | # target bundle with a build phase script. Anything else will be cleaned.
102 | # You can preserve files from being cleaned, please don't preserve
103 | # non-essential files like tests, examples and documentation.
104 | #
105 |
106 | # s.resource = "icon.png"
107 | # s.resources = "Resources/*.png"
108 |
109 | # s.preserve_paths = "FilesToSave", "MoreFilesToSave"
110 |
111 |
112 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
113 | #
114 | # Link your library with frameworks, or libraries. Libraries do not include
115 | # the lib prefix of their name.
116 | #
117 |
118 | # s.framework = "SomeFramework"
119 | # s.frameworks = "SomeFramework", "AnotherFramework"
120 |
121 | # s.library = "iconv"
122 | # s.libraries = "iconv", "xml2"
123 |
124 |
125 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
126 | #
127 | # If your library depends on compiler flags you can set them in the xcconfig hash
128 | # where they will only apply to your library. If you depend on other Podspecs
129 | # you can include multiple dependencies to ensure it works.
130 |
131 | s.requires_arc = true
132 |
133 | # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" }
134 | # s.dependency "JSONKit", "~> 1.4"
135 |
136 | end
137 |
--------------------------------------------------------------------------------
/MJAutoComplete/MJAutoCompleteTC.m:
--------------------------------------------------------------------------------
1 | //
2 | // MJAutoCompleteTC.m
3 | // MJAutoCompleteDemo
4 | //
5 | // Created by Mazyad Alabduljaleel on 11/9/13.
6 | // Copyright (c) 2013 ArabianDevs. All rights reserved.
7 | //
8 |
9 | #import "MJAutoCompleteTC.h"
10 | #import "MJAutoCompleteCell.h"
11 | #import "MJAutoCompleteTrigger.h"
12 |
13 | static NSString *MJAutoCompleteCellReuseIdentifier = @"AutoCompleteCell";
14 |
15 | @interface MJAutoCompleteTC ()
16 |
17 | @property (weak, nonatomic) id delegate;
18 | /* The contents of this table view is expected to be an array of MJAutoCompleteItems */
19 | @property (readonly, nonatomic) NSArray* contents;
20 | /* Height of a cell, retreived from the NIB, or +[MJAutoCompleteCell height] */
21 | @property (nonatomic) CGFloat cellHeight;
22 |
23 | @end
24 |
25 | @implementation MJAutoCompleteTC
26 |
27 | - (instancetype)initWithDelegate:(id)delegate
28 | {
29 | self = [super init];
30 | if (self)
31 | {
32 | self.delegate = delegate;
33 | }
34 | return self;
35 | }
36 |
37 | - (void)viewDidLoad
38 | {
39 | [super viewDidLoad];
40 |
41 | [self.tableView setBackgroundColor:[UIColor whiteColor]];
42 | [self.tableView registerClass:[MJAutoCompleteCell class] forCellReuseIdentifier:MJAutoCompleteCellReuseIdentifier];
43 | [self.tableView setHidden:self.contents == nil];
44 | // make sure the table view fits the container
45 | [self.tableView setAutoresizingMask:(UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth)];
46 | }
47 |
48 | - (void)showAutoCompleteItems:(NSArray *)items reversed:(BOOL)reverse
49 | {
50 | /* Resolve the trigger parameter and cellHeight */
51 | self.cellHeight = [MJAutoCompleteCell height];
52 | MJAutoCompleteTrigger *trigger = self.delegate.currentTrigger;
53 | if (trigger.cell)
54 | {
55 | if ([[NSBundle mainBundle] pathForResource:trigger.cell ofType:@"nib"])
56 | {
57 | UINib *nib = [UINib nibWithNibName:trigger.cell bundle:nil];
58 | /* attempt to retreive the height of the cell */
59 | NSArray *objects = [nib instantiateWithOwner:nil options:nil];
60 | NSAssert([objects count] == 1, @"The Cell NIB %@ has more than one object!", trigger.cell);
61 | self.cellHeight = CGRectGetHeight([(UIView *)objects[0] bounds]);
62 |
63 | [self.tableView registerNib:nib forCellReuseIdentifier:trigger.cell];
64 | }
65 | else
66 | {
67 | Class cls = NSClassFromString(trigger.cell);
68 | NSAssert([cls isSubclassOfClass:[MJAutoCompleteCell class]], @"%@ must be MJAutoCompleteCell subclass", trigger.cell);
69 | self.cellHeight = [cls height];
70 |
71 | [self.tableView registerClass:cls forCellReuseIdentifier:trigger.cell];
72 | }
73 | }
74 |
75 | /* Resolve the reverse parameter */
76 | if (!reverse)
77 | {
78 | _contents = items;
79 | }
80 | else
81 | {
82 | /* First, reverse the model we got */
83 | NSMutableArray *reversed = [NSMutableArray arrayWithCapacity:items.count];
84 | for (id obj in items.reverseObjectEnumerator)
85 | {
86 | [reversed addObject:obj];
87 | }
88 | _contents = reversed;
89 | /* Then, let's adjust the tableView */
90 | /* if the frame of the table is smaller than the container, position it at the bottom and disable scrolling */
91 | CGFloat contentHeight = self.cellHeight * [items count];
92 | if (contentHeight < CGRectGetHeight(self.tableView.superview.bounds))
93 | {
94 | CGRect frame = self.tableView.frame;
95 | frame.origin.y = CGRectGetHeight(self.tableView.superview.bounds) - contentHeight;
96 | frame.size.height = contentHeight;
97 |
98 | self.tableView.frame = frame;
99 | self.tableView.scrollEnabled = NO;
100 | }
101 | else
102 | {
103 | self.tableView.frame = self.tableView.superview.bounds;
104 | self.tableView.scrollEnabled = YES;
105 | }
106 | }
107 |
108 | /* resolve the items parameter set to self.contents */
109 | [self.tableView setHidden:self.contents == nil];
110 | [self.tableView reloadData];
111 | /* update after reloading the data */
112 | if ([items count] && reverse)
113 | {
114 | NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:[items count]-1 inSection:0];
115 | [self.tableView scrollToRowAtIndexPath:lastIndexPath
116 | atScrollPosition:UITableViewScrollPositionBottom
117 | animated:NO];
118 | }
119 | }
120 |
121 | #pragma mark - Table view data source
122 |
123 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
124 | {
125 | return self.cellHeight;
126 | }
127 |
128 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
129 | {
130 | return [self.contents count];
131 | }
132 |
133 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
134 | {
135 | MJAutoCompleteCell *cell = [tableView dequeueReusableCellWithIdentifier:self.delegate.currentTrigger.cell];
136 | if (!cell)
137 | {
138 | cell = [tableView dequeueReusableCellWithIdentifier:MJAutoCompleteCellReuseIdentifier];
139 | NSAssert(cell, @"Cell couldn't be instantiated for identifier: %@", MJAutoCompleteCellReuseIdentifier);
140 | }
141 |
142 | MJAutoCompleteItem* item = self.contents[indexPath.row];
143 | [cell setAutoCompleteItem:item];
144 |
145 | [self.delegate autoCompleteTableController:self willPresentCell:cell];
146 |
147 | return cell;
148 | }
149 |
150 | #pragma mark - Table view delegate methods
151 |
152 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
153 | {
154 | [self.delegate autoCompleteTableController:self didSelectItem:self.contents[indexPath.row]];
155 | }
156 |
157 | #pragma mark -
158 |
159 | @end
160 |
--------------------------------------------------------------------------------
/MJAutoComplete/MJAutoCompleteManager.m:
--------------------------------------------------------------------------------
1 | //
2 | // MJAutoCompleteManager.m
3 | // MJAutoCompleteDemo
4 | //
5 | // Created by Mazyad Alabduljaleel on 11/9/13.
6 | // Copyright (c) 2013 ArabianDevs. All rights reserved.
7 | //
8 |
9 | #import "MJAutoCompleteManager.h"
10 | #import "MJAutoCompleteTC.h"
11 | #import "MJAutoCompleteCell.h"
12 |
13 | @interface MJAutoCompleteManager ()
14 |
15 | /* keep a track of the currently processed string and delimiter. The string is the whole text sent by the developer. Accessed by the handler block. */
16 | @property (strong, nonatomic) NSString* processingString;
17 | /* The delimiters that would trigger an autoComplete cycle */
18 | @property (strong, nonatomic) NSMutableSet *triggerSet;
19 | /* The tableViewController associated with displaying the autoCompleteItems */
20 | @property (strong, nonatomic) MJAutoCompleteTC *autoCompleteTC;
21 |
22 | @end
23 |
24 | @implementation MJAutoCompleteManager
25 | @synthesize currentTrigger = _currentTrigger;
26 |
27 | - (id)init
28 | {
29 | self = [super init];
30 | if (self)
31 | {
32 | self.triggerSet = [NSMutableSet setWithCapacity:5];
33 |
34 | _autoCompleteTC = [[MJAutoCompleteTC alloc] initWithDelegate:self];
35 | }
36 | return self;
37 | }
38 |
39 | - (NSSet *)triggers
40 | {
41 | // protect the internal mutable array
42 | return [self.triggerSet copy];
43 | }
44 | /* Override setContainer: to add the tableView to new container */
45 | - (void)setContainer:(UIView *)container
46 | {
47 | _container = container;
48 |
49 | [self.autoCompleteTC.tableView removeFromSuperview];
50 | [self.autoCompleteTC.tableView setFrame:container.bounds];
51 |
52 | [container addSubview:self.autoCompleteTC.tableView];
53 | }
54 | /* Update the AutoCompleteItems list with new items using the dataSource to filter, or apply simple filter */
55 | - (void)_updateAutoCompleteList:(NSArray *)list forTrigger:(MJAutoCompleteTrigger *)trigger withString:(NSString *)string
56 | {
57 | self.currentTrigger = trigger;
58 |
59 | NSMutableArray* filteredList = [list mutableCopy];
60 | if ([self.dataSource respondsToSelector:@selector(autoCompleteManager:filterList:forTrigger:withString:)])
61 | {
62 | [self.dataSource autoCompleteManager:self filterList:filteredList forTrigger:trigger withString:string];
63 | }
64 | else
65 | {
66 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"autoCompleteString beginswith[cd] %@", string];
67 | [filteredList filterUsingPredicate:predicate];
68 | }
69 |
70 | if ([self.delegate respondsToSelector:@selector(autoCompleteManagerViewWillAppear:)])
71 | {
72 | [self.delegate autoCompleteManagerViewWillAppear:self];
73 | }
74 |
75 | [_autoCompleteTC showAutoCompleteItems:filteredList reversed:self.isScrollDirectionReversed];
76 | }
77 |
78 | - (void)addAutoCompleteTrigger:(MJAutoCompleteTrigger *)trigger
79 | {
80 | [self.triggerSet addObject:trigger];
81 | }
82 |
83 | - (void)removeAutoCompleteTrigger:(MJAutoCompleteTrigger *)trigger
84 | {
85 | [self.triggerSet removeObject:trigger];
86 | }
87 |
88 | - (void)removeAllAutoCompleteTriggers
89 | {
90 | [self.triggerSet removeAllObjects];
91 | }
92 |
93 | - (void)processString:(NSString *)string
94 | {
95 | self.processingString = string;
96 | BOOL didTriggerAutoComplete = NO;
97 | // iterate the triggers, note how the order is significant
98 | for (MJAutoCompleteTrigger* trigger in self.triggerSet)
99 | {
100 | NSString *substring = [trigger substringToBeAutoCompletedInString:self.processingString];
101 | // if the trigger found a string to be autoCompleted
102 | if (substring)
103 | {
104 | // let's inform the delegate and update the list
105 | __weak MJAutoCompleteManager *weakSelf = self;
106 | MJAutoCompleteListCallback cb = ^(NSArray *list)
107 | {
108 | [weakSelf _updateAutoCompleteList:list forTrigger:trigger withString:substring];
109 | };
110 |
111 | if ([self.dataSource respondsToSelector:@selector(autoCompleteManager:itemListForTrigger:withString:callback:)])
112 | {
113 | /* get the list of items from the dataSource EVERY TIME. The user should be given the ability to implement a heuristic if the list is empty, for example, adding an autocorrect on top of autocomplete!) */
114 | [self.dataSource autoCompleteManager:self
115 | itemListForTrigger:trigger
116 | withString:substring
117 | callback:cb];
118 | }
119 | else
120 | {
121 | cb(trigger.autoCompleteItemList);
122 | }
123 |
124 | didTriggerAutoComplete = YES;
125 | break;
126 | }
127 | }
128 | // if there was no trigger invoked, get rid of the tableview
129 | if (!didTriggerAutoComplete)
130 | {
131 | if ([self.delegate respondsToSelector:@selector(autoCompleteManagerViewWillDisappear:)])
132 | {
133 | [self.delegate autoCompleteManagerViewWillDisappear:self];
134 | }
135 |
136 | [_autoCompleteTC showAutoCompleteItems:nil reversed:NO];
137 | }
138 | }
139 |
140 | #pragma mark - MJAutoCompleteTC Delegate Methods
141 | /* Optionally inform the delegate when an item is about to be displayed. */
142 | - (void)autoCompleteTableController:(MJAutoCompleteTC *)acTableController
143 | willPresentCell:(MJAutoCompleteCell *)cell
144 | {
145 | if ([self.delegate respondsToSelector:@selector(autoCompleteManager:willPresentCell:forTrigger:)])
146 | {
147 | [self.delegate autoCompleteManager:self willPresentCell:cell forTrigger:self.currentTrigger];
148 | }
149 | }
150 | /* send the new string, with the replaced autoComplete string, back to the user */
151 | - (void)autoCompleteTableController:(MJAutoCompleteTC *)acTableController
152 | didSelectItem:(MJAutoCompleteItem *)selectedItem
153 | {
154 | NSString* autoCompleteString = selectedItem.autoCompleteString;
155 | NSString* delimiter = self.currentTrigger.delimiter;
156 |
157 | NSRange dlRange = [self.processingString rangeOfString:delimiter
158 | options:NSBackwardsSearch];
159 | if (!delimiter.length)
160 | {
161 | dlRange = NSMakeRange(0, 0);
162 | }
163 | /* If the autoCompleteString already has the delimiter, let's strip it */
164 | NSInteger offset = [autoCompleteString hasPrefix:delimiter] ? delimiter.length : 0;
165 | NSInteger index = NSMaxRange(dlRange) - offset;
166 |
167 | NSString* prevString = [self.processingString substringToIndex:index];
168 | NSString* newString = [NSString stringWithFormat:@"%@%@ ", prevString, autoCompleteString];
169 |
170 | if ([self.delegate respondsToSelector:@selector(autoCompleteManager:shouldUpdateToText:selectedItem:)]) {
171 | [self.delegate autoCompleteManager:self shouldUpdateToText:newString selectedItem:selectedItem];
172 | }
173 | else if ([self.delegate respondsToSelector:@selector(autoCompleteManager:shouldUpdateToText:)])
174 | {
175 | [self.delegate autoCompleteManager:self shouldUpdateToText:newString];
176 | }
177 |
178 | // process new string:
179 | [self processString:newString];
180 | }
181 |
182 | @end
183 |
--------------------------------------------------------------------------------
/Vendor/Haneke/UIImageView+Haneke.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIImageView+Haneke.m
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 12/02/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import "UIImageView+Haneke.h"
22 | #import "HNKDiskFetcher.h"
23 | #import "HNKSimpleFetcher.h"
24 | #import "HNKNetworkFetcher.h"
25 | #import "UIView+Haneke.h"
26 | #import
27 |
28 | @implementation UIImageView (Haneke)
29 |
30 | - (void)hnk_setImageFromFile:(NSString*)path
31 | {
32 | [self hnk_setImageFromFile:path placeholder:nil success:nil failure:nil];
33 | }
34 |
35 | - (void)hnk_setImageFromFile:(NSString*)path placeholder:(UIImage *)placeholder
36 | {
37 | [self hnk_setImageFromFile:path placeholder:placeholder success:nil failure:nil];
38 | }
39 |
40 | - (void)hnk_setImageFromFile:(NSString*)path placeholder:(UIImage*)placeholder success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock
41 | {
42 | id fetcher = [[HNKDiskFetcher alloc] initWithPath:path];
43 | [self hnk_setImageFromFetcher:fetcher placeholder:placeholder success:successBlock failure:failureBlock];
44 | }
45 |
46 | - (void)hnk_setImageFromURL:(NSURL*)url
47 | {
48 | [self hnk_setImageFromURL:url placeholder:nil success:nil failure:nil];
49 | }
50 |
51 | - (void)hnk_setImageFromURL:(NSURL*)url placeholder:(UIImage *)placeholder
52 | {
53 | [self hnk_setImageFromURL:url placeholder:placeholder success:nil failure:nil];
54 | }
55 |
56 | - (void)hnk_setImageFromURL:(NSURL*)url placeholder:(UIImage*)placeholder success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock
57 | {
58 | id fetcher = [[HNKNetworkFetcher alloc] initWithURL:url];
59 | [self hnk_setImageFromFetcher:fetcher placeholder:placeholder success:successBlock failure:failureBlock];
60 | }
61 |
62 | - (void)hnk_setImage:(UIImage*)originalImage withKey:(NSString*)key
63 | {
64 | [self hnk_setImage:originalImage withKey:key placeholder:nil success:nil failure:nil];
65 | }
66 |
67 | - (void)hnk_setImage:(UIImage*)originalImage withKey:(NSString*)key placeholder:(UIImage*)placeholder
68 | {
69 | [self hnk_setImage:originalImage withKey:key placeholder:placeholder success:nil failure:nil];
70 | }
71 |
72 | - (void)hnk_setImage:(UIImage*)originalImage withKey:(NSString*)key placeholder:(UIImage*)placeholder success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock
73 | {
74 | id fetcher = [[HNKSimpleFetcher alloc] initWithKey:key image:originalImage];
75 | [self hnk_setImageFromFetcher:fetcher placeholder:placeholder success:successBlock failure:failureBlock];
76 | }
77 |
78 | - (void)hnk_setImageFromFetcher:(id)fetcher
79 | {
80 | [self hnk_setImageFromFetcher:fetcher placeholder:nil success:nil failure:nil];
81 | }
82 |
83 | - (void)hnk_setImageFromFetcher:(id)fetcher placeholder:(UIImage*)placeholder
84 | {
85 | [self hnk_setImageFromFetcher:fetcher placeholder:placeholder success:nil failure:nil];
86 | }
87 |
88 | - (void)hnk_setImageFromFetcher:(id)fetcher placeholder:(UIImage*)placeholder success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock
89 | {
90 | [self hnk_cancelSetImage];
91 | self.hnk_fetcher = fetcher;
92 | const BOOL didSetImage = [self hnk_fetchImageForFetcher:fetcher success:successBlock failure:failureBlock];
93 | if (!didSetImage && placeholder != nil)
94 | {
95 | self.image = placeholder;
96 | }
97 | }
98 |
99 | - (void)setHnk_cacheFormat:(HNKCacheFormat *)cacheFormat
100 | {
101 | [HNKCache registerSharedFormat:cacheFormat];
102 | objc_setAssociatedObject(self, @selector(hnk_cacheFormat), cacheFormat, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
103 | self.contentMode = (UIViewContentMode)cacheFormat.scaleMode;
104 | }
105 |
106 | - (HNKCacheFormat*)hnk_cacheFormat
107 | {
108 | HNKCacheFormat *format = (HNKCacheFormat *)objc_getAssociatedObject(self, @selector(hnk_cacheFormat));
109 | if (format) return format;
110 |
111 | CGSize viewSize = self.bounds.size;
112 | NSAssert(viewSize.width > 0 && viewSize.height > 0, @"%s: UImageView size is zero. Set its frame, call sizeToFit or force layout first. You can also set a custom format with a defined size if you don't want to force layout.", __PRETTY_FUNCTION__);
113 | HNKScaleMode scaleMode = self.hnk_scaleMode;
114 | format = [HNKCache sharedFormatWithSize:viewSize scaleMode:scaleMode];
115 | return format;
116 | }
117 |
118 | - (void)hnk_cancelSetImage
119 | {
120 | if ([self.hnk_fetcher respondsToSelector:@selector(cancelFetch)])
121 | {
122 | [self.hnk_fetcher cancelFetch];
123 | }
124 | self.hnk_fetcher = nil;
125 | }
126 |
127 | #pragma mark Private
128 |
129 | - (BOOL)hnk_fetchImageForFetcher:(id)fetcher success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock
130 | {
131 | HNKCacheFormat *format = self.hnk_cacheFormat;
132 | __block BOOL animated = NO;
133 | __weak __typeof__(self) weakSelf = self;
134 | const BOOL didSetImage = [[HNKCache sharedCache] fetchImageForFetcher:fetcher formatName:format.name success:^(UIImage *image) {
135 | __typeof__(weakSelf) strongSelf = weakSelf;
136 | if ([strongSelf hnk_shouldCancelForKey:fetcher.key]) return;
137 |
138 | [strongSelf hnk_setImage:image animated:animated success:successBlock];
139 | } failure:^(NSError *error) {
140 | __typeof__(weakSelf) strongSelf = weakSelf;
141 | if ([strongSelf hnk_shouldCancelForKey:fetcher.key]) return;
142 |
143 | strongSelf.hnk_fetcher = nil;
144 |
145 | if (failureBlock) failureBlock(error);
146 | }];
147 | animated = YES;
148 | return didSetImage;
149 | }
150 |
151 | - (void)hnk_setImage:(UIImage*)image animated:(BOOL)animated success:(void (^)(UIImage *image))successBlock
152 | {
153 | self.hnk_fetcher = nil;
154 |
155 | if (successBlock)
156 | {
157 | successBlock(image);
158 | }
159 | else
160 | {
161 | const NSTimeInterval duration = animated ? 0.1 : 0;
162 | [UIView transitionWithView:self duration:duration options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
163 | self.image = image;
164 | } completion:nil];
165 | }
166 | }
167 |
168 | - (BOOL)hnk_shouldCancelForKey:(NSString*)key
169 | {
170 | if ([self.hnk_fetcher.key isEqualToString:key]) return NO;
171 |
172 | HanekeLog(@"Cancelled set image for %@", key.lastPathComponent);
173 | return YES;
174 | }
175 |
176 | #pragma mark Properties (Private)
177 |
178 | - (id)hnk_fetcher
179 | {
180 | return (id)objc_getAssociatedObject(self, @selector(hnk_fetcher));
181 | }
182 |
183 | - (void)setHnk_fetcher:(id)fetcher
184 | {
185 | objc_setAssociatedObject(self, @selector(hnk_fetcher), fetcher, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
186 | }
187 |
188 | @end
189 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MJAutoComplete
2 |
3 | A simple drop-in for using an autocomplete component on iOS. It comes with a simple demo app that should help you get started in a jiffy!
4 |
5 | 
6 |
7 | ## Outline
8 |
9 | + **[How To Use](#how-to-use)**: tl;dr ppl go there. Now.
10 | + **[How It Works](#how-it-works)**: The scary innards and plumbings.
11 | + **[Gotchas](#gotchas-important)**: **IMPORTANT**, please read before taking Aspirin.
12 | + **[Demo Walk-through](#demo-walk-through)**: The demo is pretty extensive! Read to make the most out of this component.
13 | + **[Dependencies](#dependencies)**: Libraries required for this to work.
14 | + **[Challenges](#challenges)**: Meh, remarks.
15 | + **[Special Thanks](#special-thanks)**: Hall of Fame.
16 |
17 | ## How To Use
18 |
19 | + Get the repo in whichever way you like. I endorse submodules, but you are free to use whatever you like.
20 | + Add the `MJAutoComplete` folder located inside the repo into your project.
21 | + Write some code!!
22 | - `#import "MJAutoCompleteManager.h"`
23 | - Instantiate an instance of the manager, and assign the delegate and the optional dataSource.
24 | - The delegate must implement one of the `shouldUpdateText` methods from the protocol.
25 | - Instantiate a few `MJAutoCompleteTriggers` with a delimiter and a list of items (for testing).
26 | - Add the triggers to the `MJAutoCompleteManager` instance.
27 | - When you have the container view for the AutoComplete component ready, assign the `MJAutoCompleteManager`'s `container` property to that container.
28 | - Finally, send the string you want to process using `-[MJAutoCompleteManager processString:]`!
29 |
30 | ## How It Works
31 |
32 | ### Terminology
33 |
34 | + **Manager:** The top level object that manages the AutoComplete component.
35 | + **Trigger:** Defines the delimiter that should display the AutoComplete list, and optionally the list itself and a custom `UITableViewCell` subclass.
36 | + **Item:** Defines a single AutoComplete option. Other than the arbitrary context `NSDictionary` property, there are two interesting properties:
37 | - `autoCompleteString`: The string that will be checked against when filtering the list
38 | - `displayedString`: The string that the user will see in the default `MJAutoCompleteCell` implementation. *(e.g.: autoCompleteString = twitter handle, displayedString = Full name).*
39 |
40 | ### Set Up
41 |
42 | The top level object that you interact with mainly, is the `MJAutoCompleteManager`. The manager retains a `TableViewController` and asks you to assign its `container` to a *UIView* that will host the AutoComplete table view. The manager also provides a delegate & dataSource interface to interact with the component.
43 |
44 | ### Processing The Input
45 |
46 | ##### SENDING THE INPUT TO THE AUTOCOMPLETE MANAGER
47 |
48 | After initialization, you would send the whole string the user is typing to the AutoComplete manager through `processString:`. The manager will iterate over the triggers and check whether the input fires any of them.
49 |
50 | ##### UPON FIRING AN AUTOCOMPLETE TRIGGER
51 |
52 | If the input fires a trigger, the manager will check if the dataSource implements the `itemList:` selector. This method is meant for developers who plan to fetch the list asynchronously. This selector is called with a callback that must be called once the list is ready. If the method is not implemented, then the developer must set the item list on the trigger (`trigger.autoCompleteItemList`).
53 |
54 | Immidiatly afte the manager gets the result of the trigger, it sends another optional message to the dataSource to filter the list in-place. (i.e. an `NSMutableArray` is passed to the dataSource, and that same object is expected to contain the filtered list on return). If this method is not implemented by the dataSource, the manager will apply a default filter to the list (e.g. `autoCompleteString beginswith[cd] %@`).
55 |
56 | Finally, an optional message is sent to the delegate that the AutoComplete table view will appear.
57 |
58 | This process actually takes place every time the user types more stuff, even if the AutoComplete table is already displayed. This allows the developer to implement heuristics, such as autoCorrection or something.
59 |
60 | ##### DISPLAYING THEM ITEMS
61 |
62 | The `MJAutoCompleteTC` (where TC stands for TableViewController) simply dequeues a reusable cell and assigns it with the `MJAutoCompleteItem` for that row to the cell. before returning the cell to the table view, the controller sends a message to the manager, which in turn calls the optional selector on the delegate `autoCompleteManager:willPresentCell:forTrigger:`. This is intended for developers who would like to load data just when the cell is presented (i.e. avatars).
63 |
64 | ##### SELECTING AN AUTOCOMPLETE OPTION
65 |
66 | At this point, the user either enters a character that hides the autoComplete table, such as a whitespace, or they select an AutoComplete option (or they exit the app, or the app crashes, or...). If they select an option, the only required `MJAutoCompleteManagerDelegate` method is called, which is `-[autoCompleteManager:shouldUpdateToText:]`. The implementation of this method is typically to set the string passed by the manager as the new string of the component the user was typing in.
67 |
68 | ## Gotchas *IMPORTANT!*
69 |
70 | + When sending the list of autoComplete items to the component, make sure it's an array of `MJAutoCompleteItem`s!! Use `[MJAutoCompleteItem autoCompleteCellModelFromStrings:myObjectArray]` if necessary. This will return an `MJAutoCompleteItem` array by passing the `-[NSObject description]` message to all the items in the array.
71 |
72 | + If you would like to use a custom `MJAutoCompleteCell` subclass for the component, make sure that:
73 | - If you are loading from a nib, set the reuse identifier in the NIB to the subclass name.
74 | - Assign the subclass string to the corresponding trigger, and it will try to load a nib, and if it can't find one, will simply register the class with the tableView `-[UITableView registerClass:NSClassFromString(...) ...]`.
75 |
76 |
77 | ## Demo Walk-through
78 |
79 | ####*UNDER CONSTRUCTION*
80 |
81 | ## Dependencies
82 |
83 | + **NONE**.
84 |
85 | ## Challenges
86 |
87 | ### Thumbnail support
88 |
89 | #### Problem:
90 |
91 | After a default very simple implementation that allowed the developer to lazily load the thumbnails and cache them on their side, I realized that most of the async image loading components for iOS out there are **`UIImageView` categories**. This meant that providing the developer with a model object that has an image property will not be compatible with these components. I thought about embedding a component within the project, like the notorious [SDWebImage](https://github.com/rs/SDWebImage), but that meant that there will be a different internal cache for the component that I must either expose to the developer, or somehow allow the developer to manage/disable.
92 |
93 | #### Solution:
94 |
95 | The final adapted solution was to remove the thumbnail support *completely*. Instead, a feature was implemented that allowed the developer to subclass the `MJAutoCompleteCell`, hence customize the cell to their heart's content. The reason behind this approach is because I am from a school of thought that supports fine-grain maintainable/replaceable components rather than a huge monolithic component that just tries to do everything.
96 |
97 | ## Special Thanks
98 |
99 | + The awesome [Haneke](https://github.com/Haneke/Haneke), used for demo purpose only.
100 | + [alltheragefaces.com](http://alltheragefaces.com/), imagine how much our examples would suck without these services.
101 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/Vendor/Haneke/UIImageView+Haneke.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIImageView+Haneke.m
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 12/02/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import "UIImageView+Haneke.h"
22 | #import "HNKDiskFetcher.h"
23 | #import "HNKSimpleFetcher.h"
24 | #import "HNKNetworkFetcher.h"
25 | #import "UIView+Haneke.h"
26 | #import
27 |
28 | @implementation UIImageView (Haneke)
29 |
30 | - (void)hnk_setImageFromFile:(NSString*)path
31 | {
32 | [self hnk_setImageFromFile:path placeholder:nil success:nil failure:nil];
33 | }
34 |
35 | - (void)hnk_setImageFromFile:(NSString*)path placeholder:(UIImage *)placeholder
36 | {
37 | [self hnk_setImageFromFile:path placeholder:placeholder success:nil failure:nil];
38 | }
39 |
40 | - (void)hnk_setImageFromFile:(NSString*)path placeholder:(UIImage*)placeholder success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock
41 | {
42 | id fetcher = [[HNKDiskFetcher alloc] initWithPath:path];
43 | [self hnk_setImageFromFetcher:fetcher placeholder:placeholder success:successBlock failure:failureBlock];
44 | }
45 |
46 | - (void)hnk_setImageFromURL:(NSURL*)url
47 | {
48 | [self hnk_setImageFromURL:url placeholder:nil success:nil failure:nil];
49 | }
50 |
51 | - (void)hnk_setImageFromURL:(NSURL*)url placeholder:(UIImage *)placeholder
52 | {
53 | [self hnk_setImageFromURL:url placeholder:placeholder success:nil failure:nil];
54 | }
55 |
56 | - (void)hnk_setImageFromURL:(NSURL*)url placeholder:(UIImage*)placeholder success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock
57 | {
58 | id fetcher = [[HNKNetworkFetcher alloc] initWithURL:url];
59 | [self hnk_setImageFromFetcher:fetcher placeholder:placeholder success:successBlock failure:failureBlock];
60 | }
61 |
62 | - (void)hnk_setImage:(UIImage*)originalImage withKey:(NSString*)key
63 | {
64 | [self hnk_setImage:originalImage withKey:key placeholder:nil success:nil failure:nil];
65 | }
66 |
67 | - (void)hnk_setImage:(UIImage*)originalImage withKey:(NSString*)key placeholder:(UIImage*)placeholder
68 | {
69 | [self hnk_setImage:originalImage withKey:key placeholder:placeholder success:nil failure:nil];
70 | }
71 |
72 | - (void)hnk_setImage:(UIImage*)originalImage withKey:(NSString*)key placeholder:(UIImage*)placeholder success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock
73 | {
74 | id fetcher = [[HNKSimpleFetcher alloc] initWithKey:key image:originalImage];
75 | [self hnk_setImageFromFetcher:fetcher placeholder:placeholder success:successBlock failure:failureBlock];
76 | }
77 |
78 | - (void)hnk_setImageFromFetcher:(id)fetcher
79 | {
80 | [self hnk_setImageFromFetcher:fetcher placeholder:nil success:nil failure:nil];
81 | }
82 |
83 | - (void)hnk_setImageFromFetcher:(id)fetcher placeholder:(UIImage*)placeholder
84 | {
85 | [self hnk_setImageFromFetcher:fetcher placeholder:placeholder success:nil failure:nil];
86 | }
87 |
88 | - (void)hnk_setImageFromFetcher:(id)fetcher placeholder:(UIImage*)placeholder success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock
89 | {
90 | [self hnk_cancelSetImage];
91 | self.hnk_fetcher = fetcher;
92 | const BOOL didSetImage = [self hnk_fetchImageForFetcher:fetcher success:successBlock failure:failureBlock];
93 | if (!didSetImage && placeholder != nil)
94 | {
95 | self.image = placeholder;
96 | }
97 | }
98 |
99 | - (void)setHnk_cacheFormat:(HNKCacheFormat *)cacheFormat
100 | {
101 | [HNKCache registerSharedFormat:cacheFormat];
102 | objc_setAssociatedObject(self, @selector(hnk_cacheFormat), cacheFormat, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
103 | self.contentMode = (UIViewContentMode)cacheFormat.scaleMode;
104 | }
105 |
106 | - (HNKCacheFormat*)hnk_cacheFormat
107 | {
108 | HNKCacheFormat *format = (HNKCacheFormat *)objc_getAssociatedObject(self, @selector(hnk_cacheFormat));
109 | if (format) return format;
110 |
111 | CGSize viewSize = self.bounds.size;
112 | NSAssert(viewSize.width > 0 && viewSize.height > 0, @"%s: UImageView size is zero. Set its frame, call sizeToFit or force layout first. You can also set a custom format with a defined size if you don't want to force layout.", __PRETTY_FUNCTION__);
113 | HNKScaleMode scaleMode = self.hnk_scaleMode;
114 | format = [HNKCache sharedFormatWithSize:viewSize scaleMode:scaleMode];
115 | return format;
116 | }
117 |
118 | - (void)hnk_cancelSetImage
119 | {
120 | if ([self.hnk_fetcher respondsToSelector:@selector(cancelFetch)])
121 | {
122 | [self.hnk_fetcher cancelFetch];
123 | }
124 | self.hnk_fetcher = nil;
125 | }
126 |
127 | #pragma mark Private
128 |
129 | - (BOOL)hnk_fetchImageForFetcher:(id)fetcher success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock
130 | {
131 | HNKCacheFormat *format = self.hnk_cacheFormat;
132 | __block BOOL animated = NO;
133 | __weak __typeof__(self) weakSelf = self;
134 | const BOOL didSetImage = [[HNKCache sharedCache] fetchImageForFetcher:fetcher formatName:format.name success:^(UIImage *image) {
135 | __typeof__(weakSelf) strongSelf = weakSelf;
136 | if ([strongSelf hnk_shouldCancelForKey:fetcher.key]) return;
137 |
138 | [strongSelf hnk_setImage:image animated:animated success:successBlock];
139 | } failure:^(NSError *error) {
140 | __typeof__(weakSelf) strongSelf = weakSelf;
141 | if ([strongSelf hnk_shouldCancelForKey:fetcher.key]) return;
142 |
143 | strongSelf.hnk_fetcher = nil;
144 |
145 | if (failureBlock) failureBlock(error);
146 | }];
147 | animated = YES;
148 | return didSetImage;
149 | }
150 |
151 | - (void)hnk_setImage:(UIImage*)image animated:(BOOL)animated success:(void (^)(UIImage *image))successBlock
152 | {
153 | self.hnk_fetcher = nil;
154 |
155 | if (successBlock)
156 | {
157 | successBlock(image);
158 | }
159 | else
160 | {
161 | const NSTimeInterval duration = animated ? 0.1 : 0;
162 | [UIView transitionWithView:self duration:duration options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
163 | self.image = image;
164 | } completion:nil];
165 | }
166 | }
167 |
168 | - (BOOL)hnk_shouldCancelForKey:(NSString*)key
169 | {
170 | if ([self.hnk_fetcher.key isEqualToString:key]) return NO;
171 |
172 | HanekeLog(@"Cancelled set image for %@", key.lastPathComponent);
173 | return YES;
174 | }
175 |
176 | #pragma mark Properties (Private)
177 |
178 | - (id)hnk_fetcher
179 | {
180 | return (id)objc_getAssociatedObject(self, @selector(hnk_fetcher));
181 | }
182 |
183 | - (void)setHnk_fetcher:(id)fetcher
184 | {
185 | objc_setAssociatedObject(self, @selector(hnk_fetcher), fetcher, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
186 | }
187 |
188 | @end
189 |
--------------------------------------------------------------------------------
/Vendor/Haneke/HNKCache.h:
--------------------------------------------------------------------------------
1 | //
2 | // HNKCache.h
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 10/02/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import
22 |
23 | @protocol HNKFetcher;
24 | @class HNKCacheFormat;
25 |
26 | #if HANEKE_DEBUG
27 | #define HanekeLog(...) NSLog(@"HANEKE: %@", [NSString stringWithFormat:__VA_ARGS__]);
28 | #else
29 | #define HanekeLog(...)
30 | #endif
31 |
32 | /**
33 | A cache for images.
34 | */
35 | @interface HNKCache : NSObject
36 |
37 | #pragma mark Initializing the cache
38 | ///---------------------------------------------
39 | /// @name Initializing the cache
40 | ///---------------------------------------------
41 |
42 | /**
43 | Initializes a cache with the given name.
44 | @param name Name of the cache. Used as the name for the subdirectory of the disk cache.
45 | */
46 | - (id)initWithName:(NSString*)name;
47 |
48 | /**
49 | Returns the shared cache used by the UIKit categories.
50 | @discussion It is recommended to use the shared cache unless you need separate caches.
51 | */
52 | + (HNKCache*)sharedCache;
53 |
54 | /**
55 | Registers a format in the cache. If a format with the same name already exists in the cache, it will be cleared first.
56 | @param Format to be registered in the cache.
57 | @discussion If the format preload policy allows it, Haneke will add some or all images cached on disk to the memory cache. If an image of the given format is requested, Haneke will cancel preloading to give priority to the request.
58 | @discussion A format can only be registered in one cache.
59 | */
60 | - (void)registerFormat:(HNKCacheFormat*)format;
61 |
62 | /**
63 | Dictionary of formats registered with the cache.
64 | */
65 | @property (nonatomic, readonly) NSDictionary *formats;
66 |
67 | #pragma mark Getting images
68 | ///---------------------------------------------
69 | /// @name Getting images
70 | ///---------------------------------------------
71 |
72 | /**
73 | Retrieves an image from the cache, or creates one if it doesn't exist. If the image exists in the memory cache, the success block will be executed synchronously. If the image has to be retreived from the disk cache or has to be created, the success block will be executed asynchronously.
74 | @param fetcher Fetcher that can provide the original image. If the image doesn't exist in the cache, the fetcher will be asked to provide the original image to add it. Any calls to the fetcher will be done in the main queue.
75 | @param formatName Name of the format in which the image is desired. The format must have been previously registered with the cache. If the image doesn't exist in the cache, it will be created based on the format. If by creating the image the format disk capacity is surpassed, the least recently used images of the format will be removed until it isn't.
76 | @param successBlock Block to be called with the requested image. Always called from the main queue. Will be called synchronously if the image exists in the memory cache, or asynchronously if the image has to be retreived from the disk cache or has to be created.
77 | @param failureBlock Block to be called if the image is not in the cache and the fetcher fails to provide the original. Called asynchronously from the main queue.
78 | @return YES if image exists in the memory cache (and thus, the success block was called synchronously), NO otherwise.
79 | */
80 | - (BOOL)fetchImageForFetcher:(id)fetcher formatName:(NSString *)formatName success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
81 |
82 | /**
83 | Retrieves an image from the cache. If the image exists in the memory cache, the success block will be executed synchronously. If the image has to be retreived from the disk cache, the success block will be executed asynchronously.
84 | @param key Image cache key.
85 | @param formatName Name of the format in which the image is desired. The format must have been previously registered with the cache.
86 | @param successBlock Block to be called with the requested image. Always called from the main queue. Will be called synchronously if the image exists in the memory cache, or asynchronously if the image has to be retreived from the disk cache.
87 | @param failureBlock Block to be called if the image is not in the cache or if there is another error. Called asynchronously from the main queue.
88 | @return YES if image exists in the memory cache (and thus, the success block was called synchronously), NO otherwise.
89 | */
90 | - (BOOL)fetchImageForKey:(NSString*)key formatName:(NSString *)formatName success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
91 |
92 | #pragma mark Setting images
93 | ///---------------------------------------------
94 | /// @name Setting images
95 | ///---------------------------------------------
96 |
97 | /**
98 | Sets the image of the given key for the given format. The image is added to the memory cache and the disk cache if the format allows it.
99 | @param image Image to add to the cache. Can be nil, in which case the current image associated to the given key and format will be removed.
100 | @param key Image cache key.
101 | @param formatName Name of the format of the given image.
102 | @discussion You can use this method to pre-populate the cache, invalidate a specific image or to add resized images obtained elsewhere (e.g., a web service that generates thumbnails). In other cases, it's best to let the cache create the resized images.
103 | @warning The image size should match the format. This method won't validate this.
104 | */
105 | - (void)setImage:(UIImage*)image forKey:(NSString*)key formatName:(NSString*)formatName;
106 |
107 | #pragma mark Removing images
108 | ///---------------------------------------------
109 | /// @name Removing images
110 | ///---------------------------------------------
111 |
112 | /**
113 | Removes all cached images.
114 | */
115 | - (void)removeAllImages;
116 |
117 | /** Removes all cached images of the given format.
118 | @param formatName Name of the format whose images will be removed.
119 | */
120 | - (void)removeImagesOfFormatNamed:(NSString*)formatName;
121 |
122 | /** Removes all cached images for the given key.
123 | @param key Key whose images will be removed.
124 | */
125 | - (void)removeImagesForKey:(NSString*)key;
126 |
127 | @end
128 |
129 | /** Fetches an image asynchronously. Used by the cache to fetch the original image from which resized images will be created.
130 | */
131 | @protocol HNKFetcher
132 |
133 | /**
134 | Returns the key of the original image returned by the fetcher.
135 | @discussion If two different fetchers provide the same image, they should return the same key for better performance.
136 | */
137 | @property (nonatomic, readonly) NSString *key;
138 |
139 | /**
140 | Retrieves the original image associated with the fetcher.
141 | @param successBlock Block to be called with the original image. Must be called from the main queue.
142 | @param failureBlock Block to be called if the fetcher fails to provide the original image. Must be called from the main queue.
143 | @discussion If the fetch is cancelled the fetcher must not call any of the provided blocks.
144 | @see cancelFetch
145 | */
146 | - (void)fetchImageWithSuccess:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
147 |
148 | @optional
149 |
150 | /**
151 | Cancels the current fetch. When a fetch is cancelled it should not call any of the provided blocks.
152 | @discussion This will be typically used by UI logic to cancel fetches during view reuse.
153 | */
154 | - (void)cancelFetch;
155 |
156 | @end
157 |
158 | typedef NS_ENUM(NSInteger, HNKScaleMode)
159 | {
160 | HNKScaleModeFill = UIViewContentModeScaleToFill,
161 | HNKScaleModeAspectFit = UIViewContentModeScaleAspectFit,
162 | HNKScaleModeAspectFill = UIViewContentModeScaleAspectFill,
163 | HNKScaleModeNone
164 | };
165 |
166 | typedef NS_ENUM(NSInteger, HNKPreloadPolicy)
167 | {
168 | HNKPreloadPolicyNone,
169 | HNKPreloadPolicyLastSession,
170 | HNKPreloadPolicyAll
171 | };
172 |
173 | /**
174 | Image cache format. Defines the transformation applied to images as well as cache policies such as disk capacity.
175 | */
176 | @interface HNKCacheFormat : NSObject
177 |
178 | /**
179 | Allow upscalling. Images smaller than the format size will be upscaled if set to YES. NO by default.
180 | @discussion Has no effect if the scale mode is HNKScaleModeNone.
181 | */
182 | @property (nonatomic, assign) BOOL allowUpscaling;
183 |
184 | /**
185 | The quality of the resulting JPEG image, expressed as a value from 0.0 to 1.0. The value 0.0 represents the maximum compression (or lowest quality) while the value 1.0 represents the least compression (or best quality). 1.0 by default.
186 | @discussion Only affects opaque images.
187 | */
188 | @property (nonatomic, assign) CGFloat compressionQuality;
189 |
190 | /**
191 | Format name. Used by Haneke as the format subdirectory name of the disk cache and to uniquely identify the disk queue of the format. Avoid special characters.
192 | */
193 | @property (nonatomic, readonly) NSString *name;
194 |
195 | /**
196 | Format image size. Images will be scaled to fit or fill this size or ignore it based on scaleMode.
197 | @discussion Has no effect if the scale mode is HNKScaleModeNone.
198 | */
199 | @property (nonatomic, assign) CGSize size;
200 |
201 | /**
202 | Format scale mode. Determines if images will fit or fill the format size or not. HNKScaleModeFill by default.
203 | */
204 | @property (nonatomic, assign) HNKScaleMode scaleMode;
205 |
206 | /**
207 | The disk cache capacity for the format. If 0 Haneke will only use memory cache. 0 by default.
208 | */
209 | @property (nonatomic, assign) unsigned long long diskCapacity;
210 |
211 | /**
212 | Current size in bytes of the disk cache used by the format.
213 | */
214 | @property (nonatomic, readonly) unsigned long long diskSize;
215 |
216 | /**
217 | Preload policy. If set, Haneke will add some or all images cached on disk to the memory cache. HNKPreloadPolicyNone by default.
218 | */
219 | @property (nonatomic, assign) HNKPreloadPolicy preloadPolicy;
220 |
221 | /**
222 | Block to be called before an image is resized. The returned image will be resized. nil by default.
223 | @warning The block will be called only if the requested image is not found in the cache.
224 | @warning The block will be called in background when using the asynchronous methods of the cache.
225 | */
226 | @property (nonatomic, copy) UIImage* (^preResizeBlock)(NSString *key, UIImage *image);
227 |
228 | /**
229 | Block to be called after an image is resized. The returned image will be used by the cache. nil by default.
230 | @warning The block will be called only if the requested image is not found in the cache.
231 | @warning The block will be called in background when using the asynchronous methods of the cache.
232 | */
233 | @property (nonatomic, copy) UIImage* (^postResizeBlock)(NSString *key, UIImage *image);
234 |
235 | /** Initializes a format with the given name.
236 | @param name Name of the format.
237 | */
238 | - (id)initWithName:(NSString*)name;
239 |
240 | /**
241 | Resized the given image based on the format. Used by the cache to create its images.
242 | @param image Image to resize.
243 | @return A resized image based on the format.
244 | */
245 | - (UIImage*)resizedImageFromImage:(UIImage*)image;
246 |
247 | @end
248 |
249 | /**
250 | Haneke error domain. All errors returned by the cache will have this domain.
251 | */
252 | extern NSString *const HNKErrorDomain;
253 |
254 | /**
255 | Extended file attribute used to associate a key with the file saved on disk.
256 | */
257 | extern NSString *const HNKExtendedFileAttributeKey;
258 |
259 | enum
260 | {
261 | HNKErrorImageNotFound = -100,
262 |
263 | HNKErrorFetcherMustReturnImage = -200,
264 |
265 | HNKErrorDiskCacheCannotReadImageFromData = -300
266 | };
267 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/Vendor/Haneke/HNKCache.h:
--------------------------------------------------------------------------------
1 | //
2 | // HNKCache.h
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 10/02/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import
22 |
23 | @protocol HNKFetcher;
24 | @class HNKCacheFormat;
25 |
26 | #if HANEKE_DEBUG
27 | #define HanekeLog(...) NSLog(@"HANEKE: %@", [NSString stringWithFormat:__VA_ARGS__]);
28 | #else
29 | #define HanekeLog(...)
30 | #endif
31 |
32 | /**
33 | A cache for images.
34 | */
35 | @interface HNKCache : NSObject
36 |
37 | #pragma mark Initializing the cache
38 | ///---------------------------------------------
39 | /// @name Initializing the cache
40 | ///---------------------------------------------
41 |
42 | /**
43 | Initializes a cache with the given name.
44 | @param name Name of the cache. Used as the name for the subdirectory of the disk cache.
45 | */
46 | - (id)initWithName:(NSString*)name;
47 |
48 | /**
49 | Returns the shared cache used by the UIKit categories.
50 | @discussion It is recommended to use the shared cache unless you need separate caches.
51 | */
52 | + (HNKCache*)sharedCache;
53 |
54 | /**
55 | Registers a format in the cache. If a format with the same name already exists in the cache, it will be cleared first.
56 | @param Format to be registered in the cache.
57 | @discussion If the format preload policy allows it, Haneke will add some or all images cached on disk to the memory cache. If an image of the given format is requested, Haneke will cancel preloading to give priority to the request.
58 | @discussion A format can only be registered in one cache.
59 | */
60 | - (void)registerFormat:(HNKCacheFormat*)format;
61 |
62 | /**
63 | Dictionary of formats registered with the cache.
64 | */
65 | @property (nonatomic, readonly) NSDictionary *formats;
66 |
67 | #pragma mark Getting images
68 | ///---------------------------------------------
69 | /// @name Getting images
70 | ///---------------------------------------------
71 |
72 | /**
73 | Retrieves an image from the cache, or creates one if it doesn't exist. If the image exists in the memory cache, the success block will be executed synchronously. If the image has to be retreived from the disk cache or has to be created, the success block will be executed asynchronously.
74 | @param fetcher Fetcher that can provide the original image. If the image doesn't exist in the cache, the fetcher will be asked to provide the original image to add it. Any calls to the fetcher will be done in the main queue.
75 | @param formatName Name of the format in which the image is desired. The format must have been previously registered with the cache. If the image doesn't exist in the cache, it will be created based on the format. If by creating the image the format disk capacity is surpassed, the least recently used images of the format will be removed until it isn't.
76 | @param successBlock Block to be called with the requested image. Always called from the main queue. Will be called synchronously if the image exists in the memory cache, or asynchronously if the image has to be retreived from the disk cache or has to be created.
77 | @param failureBlock Block to be called if the image is not in the cache and the fetcher fails to provide the original. Called asynchronously from the main queue.
78 | @return YES if image exists in the memory cache (and thus, the success block was called synchronously), NO otherwise.
79 | */
80 | - (BOOL)fetchImageForFetcher:(id)fetcher formatName:(NSString *)formatName success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
81 |
82 | /**
83 | Retrieves an image from the cache. If the image exists in the memory cache, the success block will be executed synchronously. If the image has to be retreived from the disk cache, the success block will be executed asynchronously.
84 | @param key Image cache key.
85 | @param formatName Name of the format in which the image is desired. The format must have been previously registered with the cache.
86 | @param successBlock Block to be called with the requested image. Always called from the main queue. Will be called synchronously if the image exists in the memory cache, or asynchronously if the image has to be retreived from the disk cache.
87 | @param failureBlock Block to be called if the image is not in the cache or if there is another error. Called asynchronously from the main queue.
88 | @return YES if image exists in the memory cache (and thus, the success block was called synchronously), NO otherwise.
89 | */
90 | - (BOOL)fetchImageForKey:(NSString*)key formatName:(NSString *)formatName success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
91 |
92 | #pragma mark Setting images
93 | ///---------------------------------------------
94 | /// @name Setting images
95 | ///---------------------------------------------
96 |
97 | /**
98 | Sets the image of the given key for the given format. The image is added to the memory cache and the disk cache if the format allows it.
99 | @param image Image to add to the cache. Can be nil, in which case the current image associated to the given key and format will be removed.
100 | @param key Image cache key.
101 | @param formatName Name of the format of the given image.
102 | @discussion You can use this method to pre-populate the cache, invalidate a specific image or to add resized images obtained elsewhere (e.g., a web service that generates thumbnails). In other cases, it's best to let the cache create the resized images.
103 | @warning The image size should match the format. This method won't validate this.
104 | */
105 | - (void)setImage:(UIImage*)image forKey:(NSString*)key formatName:(NSString*)formatName;
106 |
107 | #pragma mark Removing images
108 | ///---------------------------------------------
109 | /// @name Removing images
110 | ///---------------------------------------------
111 |
112 | /**
113 | Removes all cached images.
114 | */
115 | - (void)removeAllImages;
116 |
117 | /** Removes all cached images of the given format.
118 | @param formatName Name of the format whose images will be removed.
119 | */
120 | - (void)removeImagesOfFormatNamed:(NSString*)formatName;
121 |
122 | /** Removes all cached images for the given key.
123 | @param key Key whose images will be removed.
124 | */
125 | - (void)removeImagesForKey:(NSString*)key;
126 |
127 | @end
128 |
129 | /** Fetches an image asynchronously. Used by the cache to fetch the original image from which resized images will be created.
130 | */
131 | @protocol HNKFetcher
132 |
133 | /**
134 | Returns the key of the original image returned by the fetcher.
135 | @discussion If two different fetchers provide the same image, they should return the same key for better performance.
136 | */
137 | @property (nonatomic, readonly) NSString *key;
138 |
139 | /**
140 | Retrieves the original image associated with the fetcher.
141 | @param successBlock Block to be called with the original image. Must be called from the main queue.
142 | @param failureBlock Block to be called if the fetcher fails to provide the original image. Must be called from the main queue.
143 | @discussion If the fetch is cancelled the fetcher must not call any of the provided blocks.
144 | @see cancelFetch
145 | */
146 | - (void)fetchImageWithSuccess:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
147 |
148 | @optional
149 |
150 | /**
151 | Cancels the current fetch. When a fetch is cancelled it should not call any of the provided blocks.
152 | @discussion This will be typically used by UI logic to cancel fetches during view reuse.
153 | */
154 | - (void)cancelFetch;
155 |
156 | @end
157 |
158 | typedef NS_ENUM(NSInteger, HNKScaleMode)
159 | {
160 | HNKScaleModeFill = UIViewContentModeScaleToFill,
161 | HNKScaleModeAspectFit = UIViewContentModeScaleAspectFit,
162 | HNKScaleModeAspectFill = UIViewContentModeScaleAspectFill,
163 | HNKScaleModeNone
164 | };
165 |
166 | typedef NS_ENUM(NSInteger, HNKPreloadPolicy)
167 | {
168 | HNKPreloadPolicyNone,
169 | HNKPreloadPolicyLastSession,
170 | HNKPreloadPolicyAll
171 | };
172 |
173 | /**
174 | Image cache format. Defines the transformation applied to images as well as cache policies such as disk capacity.
175 | */
176 | @interface HNKCacheFormat : NSObject
177 |
178 | /**
179 | Allow upscalling. Images smaller than the format size will be upscaled if set to YES. NO by default.
180 | @discussion Has no effect if the scale mode is HNKScaleModeNone.
181 | */
182 | @property (nonatomic, assign) BOOL allowUpscaling;
183 |
184 | /**
185 | The quality of the resulting JPEG image, expressed as a value from 0.0 to 1.0. The value 0.0 represents the maximum compression (or lowest quality) while the value 1.0 represents the least compression (or best quality). 1.0 by default.
186 | @discussion Only affects opaque images.
187 | */
188 | @property (nonatomic, assign) CGFloat compressionQuality;
189 |
190 | /**
191 | Format name. Used by Haneke as the format subdirectory name of the disk cache and to uniquely identify the disk queue of the format. Avoid special characters.
192 | */
193 | @property (nonatomic, readonly) NSString *name;
194 |
195 | /**
196 | Format image size. Images will be scaled to fit or fill this size or ignore it based on scaleMode.
197 | @discussion Has no effect if the scale mode is HNKScaleModeNone.
198 | */
199 | @property (nonatomic, assign) CGSize size;
200 |
201 | /**
202 | Format scale mode. Determines if images will fit or fill the format size or not. HNKScaleModeFill by default.
203 | */
204 | @property (nonatomic, assign) HNKScaleMode scaleMode;
205 |
206 | /**
207 | The disk cache capacity for the format. If 0 Haneke will only use memory cache. 0 by default.
208 | */
209 | @property (nonatomic, assign) unsigned long long diskCapacity;
210 |
211 | /**
212 | Current size in bytes of the disk cache used by the format.
213 | */
214 | @property (nonatomic, readonly) unsigned long long diskSize;
215 |
216 | /**
217 | Preload policy. If set, Haneke will add some or all images cached on disk to the memory cache. HNKPreloadPolicyNone by default.
218 | */
219 | @property (nonatomic, assign) HNKPreloadPolicy preloadPolicy;
220 |
221 | /**
222 | Block to be called before an image is resized. The returned image will be resized. nil by default.
223 | @warning The block will be called only if the requested image is not found in the cache.
224 | @warning The block will be called in background when using the asynchronous methods of the cache.
225 | */
226 | @property (nonatomic, copy) UIImage* (^preResizeBlock)(NSString *key, UIImage *image);
227 |
228 | /**
229 | Block to be called after an image is resized. The returned image will be used by the cache. nil by default.
230 | @warning The block will be called only if the requested image is not found in the cache.
231 | @warning The block will be called in background when using the asynchronous methods of the cache.
232 | */
233 | @property (nonatomic, copy) UIImage* (^postResizeBlock)(NSString *key, UIImage *image);
234 |
235 | /** Initializes a format with the given name.
236 | @param name Name of the format.
237 | */
238 | - (id)initWithName:(NSString*)name;
239 |
240 | /**
241 | Resized the given image based on the format. Used by the cache to create its images.
242 | @param image Image to resize.
243 | @return A resized image based on the format.
244 | */
245 | - (UIImage*)resizedImageFromImage:(UIImage*)image;
246 |
247 | @end
248 |
249 | /**
250 | Haneke error domain. All errors returned by the cache will have this domain.
251 | */
252 | extern NSString *const HNKErrorDomain;
253 |
254 | /**
255 | Extended file attribute used to associate a key with the file saved on disk.
256 | */
257 | extern NSString *const HNKExtendedFileAttributeKey;
258 |
259 | enum
260 | {
261 | HNKErrorImageNotFound = -100,
262 |
263 | HNKErrorFetcherMustReturnImage = -200,
264 |
265 | HNKErrorDiskCacheCannotReadImageFromData = -300
266 | };
267 |
--------------------------------------------------------------------------------
/Vendor/Haneke/UIImageView+Haneke.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIImageView+Haneke.h
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 12/02/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import
22 | #import "HNKCache.h"
23 |
24 | @interface UIImageView (Haneke)
25 |
26 | /** Loads, resizes, displays and caches an appropiately sized image from the given path.
27 | @param path Path from which the image will be loaded if it's not available in the cache.
28 | @see hnk_setImageFromFile:placeholder:success:failure:
29 | */
30 | - (void)hnk_setImageFromFile:(NSString*)path;
31 |
32 | /** Loads, resizes, displays and caches an appropiately sized image from the given path.
33 | @param path Path from which the image will be loaded if it's not available in the cache.
34 | @param placeholder Image to be used as a placeholder until the requested image is ready. The placeholder image will only be used if the requested image is not available in the memory cache. If nil, the image view will not change its image until the requested image is ready.
35 | @see hnk_setImageFromFile:placeholder:success:failure:
36 | */
37 | - (void)hnk_setImageFromFile:(NSString*)path placeholder:(UIImage*)placeholder;
38 |
39 | /** Loads, resizes, displays and caches an appropiately sized image from the given path. If a success block is provided you will be responsible for setting the image.
40 | @param path Path from which the image will be loaded if it's not available in the cache.
41 | @param placeholder Image to be used as a placeholder until the requested image is ready. The placeholder image will only be used if the requested image is not available in the memory cache. If nil, the image view will not change its image until the requested image is ready.
42 | @param successBlock Block to be called when the requested image is ready to be set. If provided, the block is reponsible for setting the image. Can be nil.
43 | @param failureBlock Block to be called if an error occurs. The most likely cause of error is that the given path does not contain an image. Can be nil.
44 | @discussion Retrieves an appropiately sized image (based on the bounds and contentMode of the UIImageView) from the memory or disk cache. Disk access is performed in background. If not cached, loads the original image from disk, produces an appropiately sized image and caches the result, everything in background.
45 | @discussion If no success block is provided, the requested image will be set with a short fade transition, or synchronously and without transition when retrieved from the memory cache.
46 | @discussion If needed, the least recently used images in the cache will be evicted in background.
47 | @discussion If the success block is nil, the image will be set with a short fade transition, or inmmediatly if the image was retrieved from the memory cache.
48 | @warning If a success block is provided you will be responsible for setting the image.
49 | */
50 | - (void)hnk_setImageFromFile:(NSString*)path placeholder:(UIImage*)placeholder success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
51 |
52 | /** Loads, resizes, displays and caches an appropiately sized image from the given url.
53 | @param url Url from which the image will be loaded if it's not available in the cache.
54 | @see hnk_setImageFromURL:placeholder:success:failure:
55 | */
56 | - (void)hnk_setImageFromURL:(NSURL*)url;
57 |
58 | /** Loads, resizes, displays and caches an appropiately sized image from the given url.
59 | @param url Url from which the image will be loaded if it's not available in the cache.
60 | @param placeholder Image to be used as a placeholder until the requested image is ready. The placeholder image will only be used if the requested image is not available in the memory cache. If nil, the image view will not change its image until the requested image is ready.
61 | @see hnk_setImageFromURL:placeholder:success:failure:
62 | */
63 | - (void)hnk_setImageFromURL:(NSURL*)url placeholder:(UIImage*)placeholder;
64 |
65 | /** Loads, resizes, displays and caches an appropiately sized image from the given url. If a success block is provided you will be responsible for setting the image.
66 | @param url Url from which the image will be loaded if it's not available in the cache.
67 | @param placeholder Image to be used as a placeholder until the requested image is ready. The placeholder image will only be used if the requested image is not available in the memory cache. If nil, the image view will not change its image until the requested image is ready.
68 | @param successBlock Block to be called when the requested image is ready to be set. If provided, the block is reponsible for setting the image. Can be nil.
69 | @param failureBlock Block to be called if an error occurs. Can be nil.
70 | @discussion Retrieves an appropiately sized image (based on the bounds and contentMode of the UIImageView) from the memory or disk cache. Disk access is performed in background. If not cached, loads the original image from the given url, produces an appropiately sized image and caches the result, everything in background.
71 | @discussion If no success block is provided, the requested image will be set with a short fade transition, or synchronously and without transition when retrieved from the memory cache.
72 | @discussion If needed, the least recently used images in the cache will be evicted in background.
73 | @warning If a success block is provided you will be responsible for setting the image.
74 | */
75 | - (void)hnk_setImageFromURL:(NSURL*)url placeholder:(UIImage*)placeholder success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
76 |
77 | /** Resizes, displays and caches an appropiately sized image from the given image.
78 | @param image Original image.
79 | @param key A key. Used by the cache to uniquely identify an image.
80 | @see hnk_setImage:withKey:placeholder:success:failure:
81 | */
82 | - (void)hnk_setImage:(UIImage*)image withKey:(NSString*)key;
83 |
84 | /** Resizes, displays and caches an appropiately sized image from the given image.
85 | @param image Original image.
86 | @param key A key. Used by the cache to uniquely identify an image.
87 | @param placeholder Image to be used as a placeholder until the requested image is ready. The placeholder image will only be used if the requested image is not available in the memory cache. If nil, the image view will not change its image until the requested image is ready.
88 | @see hnk_setImage:withKey:placeholder:success:failure:
89 | */
90 | - (void)hnk_setImage:(UIImage*)image withKey:(NSString*)key placeholder:(UIImage*)placeholder;
91 |
92 | /** Resizes, displays and caches an appropiately sized image from the given image. If a success block is provided you will be responsible for setting the image.
93 | @param image Original image.
94 | @param key A key. Used by the cache to uniquely identify an image.
95 | @param placeholder Image to be used as a placeholder until the requested image is ready. The placeholder image will only be used if the requested image is not available in the memory cache. If nil, the image view will not change its image until the requested image is ready.
96 | @param successBlock Block to be called when the requested image is ready to be set. If provided, the block is reponsible for setting the image. Can be nil.
97 | @param failureBlock Block to be called if an error occurs. Highly unlikely for this method. Can be nil.
98 | @discussion Retrieves an appropiately sized image (based on the bounds and contentMode of the UIImageView) from the memory or disk cache. Disk access is performed in background. If not cached, takes the given image, produces an appropiately sized image and caches the result, everything in background.
99 | @discussion If no success block is provided, the requested image will be set with a short fade transition, or synchronously and without transition when retrieved from the memory cache.
100 | @discussion If needed, the least recently used images in the cache will be evicted in background.
101 | @warning If a success block is provided you will be responsible for setting the image.
102 | */
103 | - (void)hnk_setImage:(UIImage*)image withKey:(NSString*)key placeholder:(UIImage*)placeholder success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
104 |
105 | /** Loads, resizes, displays and caches an appropiately sized image from the given fetcher.
106 | @param fetcher Fetcher from which the original image will be retrieved if needed. The fetcher will have to provide the original image only if it can't be found in the cache.
107 | @see hnk_setImageFromFetcher:placeholder:success:failure:
108 | */
109 | - (void)hnk_setImageFromFetcher:(id)fetcher;
110 |
111 | /** Loads, resizes, displays and caches an appropiately sized image from the given fetcher.
112 | @param fetcher Fetcher from which the original image will be retrieved if needed. The fetcher will have to provide the original image only if it can't be found in the cache.
113 | @param placeholder Image to be used as a placeholder until the requested image is ready. The placeholder image will only be used if the requested image is not available in the memory cache. If nil, the image view will not change its image until the requested image is ready.
114 | @see hnk_setImageFromFetcher:placeholder:success:failure:
115 | */
116 | - (void)hnk_setImageFromFetcher:(id)fetcher placeholder:(UIImage*)placeholder;
117 |
118 | /** Loads, resizes, displays and caches an appropiately sized image from the given fetcher. If a success block is provided you will be responsible for setting the image.
119 | @param fetcher Fetcher from which the original image will be retrieved if needed. The fetcher will have to provide the original image only if it can't be found in the cache.
120 | @param placeholder Image to be used as a placeholder until the requested image is ready. The placeholder image will only be used if the requested image is not available in the memory cache. If nil, the image view will not change its image until the requested image is ready.
121 | @param successBlock Block to be called when the requested image is ready to be set. If provided, the block is reponsible for setting the image. Can be nil.
122 | @param failureBlock Block to be called if an error occurs. The most likely cause of error is that the given fetcher failed to provide the original image. Can be nil.
123 | @discussion Retrieves an appropiately sized image (based on the bounds and contentMode of the UIImageView) from the memory or disk cache. Disk access is performed in background. If not cached, fetches the original image from the given fetcher, produces an appropiately sized image and caches the result, everything in background.
124 | @discussion If no success block is provided, the requested image will be set with a short fade transition, or synchronously and without transition when retrieved from the memory cache.
125 | @discussion If needed, the least recently used images in the cache will be evicted in background.
126 | @warning If a success block is provided you will be responsible for setting the image.
127 | */
128 | - (void)hnk_setImageFromFetcher:(id)fetcher placeholder:(UIImage*)placeholder success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
129 |
130 | /**
131 | Cancels the current set image request, if any.
132 | @discussion It is recommended to call this from [UITableViewCell prepareForReuse] or [UICollectionViewCell prepareForReuse], or as soon as you don't need the image view anymore.
133 | */
134 | - (void)hnk_cancelSetImage;
135 |
136 | /**
137 | The cache format used by the image view.
138 | @discussion Each image view has a default format created on demand. The default format size matches the bounds of the image view and will scale images based on the contentMode of the the image view.
139 | @discussion Modifying the default format is discouraged. Instead, you can set your own custom format. To apply the same custom format to various image views you must use the same format instance.
140 | */
141 | @property (nonatomic, strong) HNKCacheFormat *hnk_cacheFormat;
142 |
143 | @end
144 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/Vendor/Haneke/UIImageView+Haneke.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIImageView+Haneke.h
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 12/02/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import
22 | #import "HNKCache.h"
23 |
24 | @interface UIImageView (Haneke)
25 |
26 | /** Loads, resizes, displays and caches an appropiately sized image from the given path.
27 | @param path Path from which the image will be loaded if it's not available in the cache.
28 | @see hnk_setImageFromFile:placeholder:success:failure:
29 | */
30 | - (void)hnk_setImageFromFile:(NSString*)path;
31 |
32 | /** Loads, resizes, displays and caches an appropiately sized image from the given path.
33 | @param path Path from which the image will be loaded if it's not available in the cache.
34 | @param placeholder Image to be used as a placeholder until the requested image is ready. The placeholder image will only be used if the requested image is not available in the memory cache. If nil, the image view will not change its image until the requested image is ready.
35 | @see hnk_setImageFromFile:placeholder:success:failure:
36 | */
37 | - (void)hnk_setImageFromFile:(NSString*)path placeholder:(UIImage*)placeholder;
38 |
39 | /** Loads, resizes, displays and caches an appropiately sized image from the given path. If a success block is provided you will be responsible for setting the image.
40 | @param path Path from which the image will be loaded if it's not available in the cache.
41 | @param placeholder Image to be used as a placeholder until the requested image is ready. The placeholder image will only be used if the requested image is not available in the memory cache. If nil, the image view will not change its image until the requested image is ready.
42 | @param successBlock Block to be called when the requested image is ready to be set. If provided, the block is reponsible for setting the image. Can be nil.
43 | @param failureBlock Block to be called if an error occurs. The most likely cause of error is that the given path does not contain an image. Can be nil.
44 | @discussion Retrieves an appropiately sized image (based on the bounds and contentMode of the UIImageView) from the memory or disk cache. Disk access is performed in background. If not cached, loads the original image from disk, produces an appropiately sized image and caches the result, everything in background.
45 | @discussion If no success block is provided, the requested image will be set with a short fade transition, or synchronously and without transition when retrieved from the memory cache.
46 | @discussion If needed, the least recently used images in the cache will be evicted in background.
47 | @discussion If the success block is nil, the image will be set with a short fade transition, or inmmediatly if the image was retrieved from the memory cache.
48 | @warning If a success block is provided you will be responsible for setting the image.
49 | */
50 | - (void)hnk_setImageFromFile:(NSString*)path placeholder:(UIImage*)placeholder success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
51 |
52 | /** Loads, resizes, displays and caches an appropiately sized image from the given url.
53 | @param url Url from which the image will be loaded if it's not available in the cache.
54 | @see hnk_setImageFromURL:placeholder:success:failure:
55 | */
56 | - (void)hnk_setImageFromURL:(NSURL*)url;
57 |
58 | /** Loads, resizes, displays and caches an appropiately sized image from the given url.
59 | @param url Url from which the image will be loaded if it's not available in the cache.
60 | @param placeholder Image to be used as a placeholder until the requested image is ready. The placeholder image will only be used if the requested image is not available in the memory cache. If nil, the image view will not change its image until the requested image is ready.
61 | @see hnk_setImageFromURL:placeholder:success:failure:
62 | */
63 | - (void)hnk_setImageFromURL:(NSURL*)url placeholder:(UIImage*)placeholder;
64 |
65 | /** Loads, resizes, displays and caches an appropiately sized image from the given url. If a success block is provided you will be responsible for setting the image.
66 | @param url Url from which the image will be loaded if it's not available in the cache.
67 | @param placeholder Image to be used as a placeholder until the requested image is ready. The placeholder image will only be used if the requested image is not available in the memory cache. If nil, the image view will not change its image until the requested image is ready.
68 | @param successBlock Block to be called when the requested image is ready to be set. If provided, the block is reponsible for setting the image. Can be nil.
69 | @param failureBlock Block to be called if an error occurs. Can be nil.
70 | @discussion Retrieves an appropiately sized image (based on the bounds and contentMode of the UIImageView) from the memory or disk cache. Disk access is performed in background. If not cached, loads the original image from the given url, produces an appropiately sized image and caches the result, everything in background.
71 | @discussion If no success block is provided, the requested image will be set with a short fade transition, or synchronously and without transition when retrieved from the memory cache.
72 | @discussion If needed, the least recently used images in the cache will be evicted in background.
73 | @warning If a success block is provided you will be responsible for setting the image.
74 | */
75 | - (void)hnk_setImageFromURL:(NSURL*)url placeholder:(UIImage*)placeholder success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
76 |
77 | /** Resizes, displays and caches an appropiately sized image from the given image.
78 | @param image Original image.
79 | @param key A key. Used by the cache to uniquely identify an image.
80 | @see hnk_setImage:withKey:placeholder:success:failure:
81 | */
82 | - (void)hnk_setImage:(UIImage*)image withKey:(NSString*)key;
83 |
84 | /** Resizes, displays and caches an appropiately sized image from the given image.
85 | @param image Original image.
86 | @param key A key. Used by the cache to uniquely identify an image.
87 | @param placeholder Image to be used as a placeholder until the requested image is ready. The placeholder image will only be used if the requested image is not available in the memory cache. If nil, the image view will not change its image until the requested image is ready.
88 | @see hnk_setImage:withKey:placeholder:success:failure:
89 | */
90 | - (void)hnk_setImage:(UIImage*)image withKey:(NSString*)key placeholder:(UIImage*)placeholder;
91 |
92 | /** Resizes, displays and caches an appropiately sized image from the given image. If a success block is provided you will be responsible for setting the image.
93 | @param image Original image.
94 | @param key A key. Used by the cache to uniquely identify an image.
95 | @param placeholder Image to be used as a placeholder until the requested image is ready. The placeholder image will only be used if the requested image is not available in the memory cache. If nil, the image view will not change its image until the requested image is ready.
96 | @param successBlock Block to be called when the requested image is ready to be set. If provided, the block is reponsible for setting the image. Can be nil.
97 | @param failureBlock Block to be called if an error occurs. Highly unlikely for this method. Can be nil.
98 | @discussion Retrieves an appropiately sized image (based on the bounds and contentMode of the UIImageView) from the memory or disk cache. Disk access is performed in background. If not cached, takes the given image, produces an appropiately sized image and caches the result, everything in background.
99 | @discussion If no success block is provided, the requested image will be set with a short fade transition, or synchronously and without transition when retrieved from the memory cache.
100 | @discussion If needed, the least recently used images in the cache will be evicted in background.
101 | @warning If a success block is provided you will be responsible for setting the image.
102 | */
103 | - (void)hnk_setImage:(UIImage*)image withKey:(NSString*)key placeholder:(UIImage*)placeholder success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
104 |
105 | /** Loads, resizes, displays and caches an appropiately sized image from the given fetcher.
106 | @param fetcher Fetcher from which the original image will be retrieved if needed. The fetcher will have to provide the original image only if it can't be found in the cache.
107 | @see hnk_setImageFromFetcher:placeholder:success:failure:
108 | */
109 | - (void)hnk_setImageFromFetcher:(id)fetcher;
110 |
111 | /** Loads, resizes, displays and caches an appropiately sized image from the given fetcher.
112 | @param fetcher Fetcher from which the original image will be retrieved if needed. The fetcher will have to provide the original image only if it can't be found in the cache.
113 | @param placeholder Image to be used as a placeholder until the requested image is ready. The placeholder image will only be used if the requested image is not available in the memory cache. If nil, the image view will not change its image until the requested image is ready.
114 | @see hnk_setImageFromFetcher:placeholder:success:failure:
115 | */
116 | - (void)hnk_setImageFromFetcher:(id)fetcher placeholder:(UIImage*)placeholder;
117 |
118 | /** Loads, resizes, displays and caches an appropiately sized image from the given fetcher. If a success block is provided you will be responsible for setting the image.
119 | @param fetcher Fetcher from which the original image will be retrieved if needed. The fetcher will have to provide the original image only if it can't be found in the cache.
120 | @param placeholder Image to be used as a placeholder until the requested image is ready. The placeholder image will only be used if the requested image is not available in the memory cache. If nil, the image view will not change its image until the requested image is ready.
121 | @param successBlock Block to be called when the requested image is ready to be set. If provided, the block is reponsible for setting the image. Can be nil.
122 | @param failureBlock Block to be called if an error occurs. The most likely cause of error is that the given fetcher failed to provide the original image. Can be nil.
123 | @discussion Retrieves an appropiately sized image (based on the bounds and contentMode of the UIImageView) from the memory or disk cache. Disk access is performed in background. If not cached, fetches the original image from the given fetcher, produces an appropiately sized image and caches the result, everything in background.
124 | @discussion If no success block is provided, the requested image will be set with a short fade transition, or synchronously and without transition when retrieved from the memory cache.
125 | @discussion If needed, the least recently used images in the cache will be evicted in background.
126 | @warning If a success block is provided you will be responsible for setting the image.
127 | */
128 | - (void)hnk_setImageFromFetcher:(id)fetcher placeholder:(UIImage*)placeholder success:(void (^)(UIImage *image))successBlock failure:(void (^)(NSError *error))failureBlock;
129 |
130 | /**
131 | Cancels the current set image request, if any.
132 | @discussion It is recommended to call this from [UITableViewCell prepareForReuse] or [UICollectionViewCell prepareForReuse], or as soon as you don't need the image view anymore.
133 | */
134 | - (void)hnk_cancelSetImage;
135 |
136 | /**
137 | The cache format used by the image view.
138 | @discussion Each image view has a default format created on demand. The default format size matches the bounds of the image view and will scale images based on the contentMode of the the image view.
139 | @discussion Modifying the default format is discouraged. Instead, you can set your own custom format. To apply the same custom format to various image views you must use the same format instance.
140 | */
141 | @property (nonatomic, strong) HNKCacheFormat *hnk_cacheFormat;
142 |
143 | @end
144 |
--------------------------------------------------------------------------------
/Vendor/Haneke/HNKDiskCache.m:
--------------------------------------------------------------------------------
1 | //
2 | // HNKDiskCache.m
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 8/21/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import "HNKDiskCache.h"
22 | #import // For hnk_MD5String
23 | #import // For hnk_setValue:forExtendedFileAttribute:
24 |
25 | NSString *const HNKExtendedFileAttributeKey = @"io.haneke.key";
26 |
27 | @interface NSString (hnk_utils)
28 |
29 | - (NSString*)hnk_MD5String;
30 | - (BOOL)hnk_setValue:(NSString*)value forExtendedFileAttribute:(NSString*)attribute;
31 | - (NSString*)hnk_stringByEscapingFilename;
32 | - (NSString*)hnk_valueForExtendedFileAttribute:(NSString*)attribute;
33 |
34 | @end
35 |
36 | @interface NSFileManager(Haneke)
37 |
38 | - (void)hnk_enumerateContentsOfDirectoryAtPath:(NSString*)path orderedByProperty:(NSString*)property ascending:(BOOL)ascending usingBlock:(void(^)(NSURL *url, NSUInteger idx, BOOL *stop))block;
39 |
40 | @end
41 |
42 | @implementation HNKDiskCache {
43 | NSString *_directory;
44 | }
45 |
46 | #pragma mark Initializing the cache
47 |
48 | - (instancetype)initWithDirectory:(NSString*)directory capacity:(unsigned long long)capacity
49 | {
50 | if (self = [super init])
51 | {
52 | _directory = [directory copy];
53 | _capacity = capacity;
54 | NSString *queueName = [NSString stringWithFormat:@"io.haneke.disk.%@", directory.lastPathComponent];
55 | _queue = dispatch_queue_create(queueName.UTF8String, NULL);
56 | dispatch_async(_queue, ^{
57 | [self calculateSize];
58 | [self controlCapacity];
59 | });
60 | }
61 | return self;
62 | }
63 |
64 | - (void)setCapacity:(unsigned long long)capacity
65 | {
66 | _capacity = capacity;
67 | dispatch_async(_queue, ^{
68 | [self controlCapacity];
69 | });
70 | }
71 |
72 | #pragma mark Setting and fetching data
73 |
74 | - (void)setData:(NSData*)data forKey:(NSString*)key
75 | {
76 | dispatch_async(_queue, ^{
77 | [self syncSetData:data forKey:key];
78 | });
79 | }
80 |
81 | - (void)fetchDataForKey:(NSString*)key success:(void (^)(NSData *data))successBlock failure:(void (^)(NSError *error))failureBlock
82 | {
83 | dispatch_async(_queue, ^{
84 | NSString *path = [self pathForKey:key];
85 | NSError *error = nil;
86 | NSData *data = [NSData dataWithContentsOfFile:path options:kNilOptions error:&error];
87 | if (!data)
88 | {
89 | if (failureBlock)
90 | {
91 | dispatch_async(dispatch_get_main_queue(), ^{
92 | failureBlock(error);
93 | });
94 | }
95 | return;
96 | }
97 |
98 | if (successBlock)
99 | {
100 | dispatch_async(dispatch_get_main_queue(), ^{
101 | successBlock(data);
102 | });
103 | }
104 |
105 | [self syncUpdateAccessDateForKey:key data:^NSData *{ return data; }];
106 | });
107 | }
108 |
109 | #pragma mark Removing data
110 |
111 | - (void)removeDataForKey:(NSString*)key
112 | {
113 | dispatch_async(_queue, ^{
114 | NSString *path = [self pathForKey:key];
115 | [self removeFileAtPath:path];
116 | });
117 | }
118 |
119 | - (void)removeAllData
120 | {
121 | dispatch_async(_queue, ^{
122 | NSFileManager *fileManager = [NSFileManager defaultManager];
123 | NSError *error;
124 | NSArray *contents = [fileManager contentsOfDirectoryAtPath:_directory error:&error];
125 | if (!contents) {
126 | NSLog(@"Failed to list directory with error %@", error);
127 | return;
128 | }
129 | for (NSString *pathComponent in contents)
130 | {
131 | NSString *path = [_directory stringByAppendingPathComponent:pathComponent];
132 | if (![fileManager removeItemAtPath:path error:&error])
133 | {
134 | NSLog(@"Failed to remove file with error %@", error);
135 | }
136 | }
137 | [self calculateSize];
138 | });
139 | }
140 |
141 | #pragma mark Managing data by access date
142 |
143 | - (void)enumerateDataByAccessDateUsingBlock:(void(^)(NSString *key, NSData *data, NSDate *accessDate, BOOL *stop))block
144 | {
145 | dispatch_async(_queue, ^{
146 | [[NSFileManager defaultManager] hnk_enumerateContentsOfDirectoryAtPath:_directory orderedByProperty:NSURLContentModificationDateKey ascending:NO usingBlock:^(NSURL *url, NSUInteger idx, BOOL *stop) {
147 | NSDate *accessDate;
148 | [url getResourceValue:&accessDate forKey:NSURLContentModificationDateKey error:nil];
149 |
150 | NSString *path = url.path;
151 | NSString *key = [path hnk_valueForExtendedFileAttribute:HNKExtendedFileAttributeKey];
152 | if (!key) return;
153 |
154 | NSData *data = [NSData dataWithContentsOfFile:path];
155 | if (!data) return;
156 |
157 | __block BOOL innerStop = NO;
158 |
159 | if (block)
160 | {
161 | block(key, data, accessDate, &innerStop);
162 | }
163 |
164 | if (innerStop) *stop = YES;
165 | }];
166 | });
167 | }
168 |
169 | - (void)updateAccessDateForKey:(NSString*)key data:(NSData* (^)())lazyData
170 | {
171 | dispatch_async(_queue, ^{
172 | [self syncUpdateAccessDateForKey:key data:lazyData];
173 | });
174 | }
175 |
176 | #pragma mark Private (in _queue)
177 |
178 | - (void)calculateSize
179 | {
180 | NSFileManager *fileManager = [NSFileManager defaultManager];
181 | _size = 0;
182 | NSError *error;
183 | NSArray *contents = [fileManager contentsOfDirectoryAtPath:_directory error:&error];
184 | if (!contents)
185 | {
186 | NSLog(@"Failed to list directory with error %@", error);
187 | return;
188 | }
189 | for (NSString *pathComponent in contents)
190 | {
191 | NSString *path = [_directory stringByAppendingPathComponent:pathComponent];
192 | NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:&error];
193 | if (!attributes) continue;
194 |
195 | _size += attributes.fileSize;
196 | }
197 | }
198 |
199 | - (void)controlCapacity
200 | {
201 | if (self.size <= self.capacity) return;
202 |
203 | NSFileManager *fileManager = [NSFileManager defaultManager];
204 | [fileManager hnk_enumerateContentsOfDirectoryAtPath:_directory orderedByProperty:NSURLContentModificationDateKey ascending:YES usingBlock:^(NSURL *url, NSUInteger idx, BOOL *stop) {
205 | NSString *path = url.path;
206 | [self removeFileAtPath:path];
207 | if (self.size <= self.capacity)
208 | {
209 | *stop = YES;
210 | }
211 | }];
212 | }
213 |
214 | - (NSString*)pathForKey:(NSString*)key
215 | {
216 | NSString *filename = [key hnk_stringByEscapingFilename];
217 | if (filename.length >= NAME_MAX)
218 | {
219 | NSString *MD5 = [key hnk_MD5String];
220 | NSString *pathExtension = key.pathExtension;
221 | filename = pathExtension.length > 0 ? [MD5 stringByAppendingPathExtension:pathExtension] : MD5;
222 | }
223 | NSString *path = [_directory stringByAppendingPathComponent:filename];
224 | return path;
225 | }
226 |
227 | - (void)removeFileAtPath:(NSString*)path
228 | {
229 | NSError *error;
230 | NSFileManager *fileManager = [NSFileManager defaultManager];
231 | NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:&error];
232 | if (attributes)
233 | {
234 | unsigned long long fileSize = attributes.fileSize;
235 | if ([fileManager removeItemAtPath:path error:&error])
236 | {
237 | _size -= fileSize;
238 | }
239 | else
240 | {
241 | NSLog(@"Failed to remove file with error %@", error);
242 | }
243 | }
244 | }
245 |
246 | - (void)syncSetData:(NSData*)data forKey:(NSString*)key
247 | {
248 | NSError *error;
249 | NSString *path = [self pathForKey:key];
250 | NSFileManager *fileManager = [NSFileManager defaultManager];
251 | NSDictionary *previousAttributes = [fileManager attributesOfItemAtPath:path error:nil];
252 | if ([data writeToFile:path options:kNilOptions error:&error])
253 | {
254 | [path hnk_setValue:key forExtendedFileAttribute:HNKExtendedFileAttributeKey];
255 | const NSUInteger byteCount = data.length;
256 | if (previousAttributes)
257 | {
258 | _size -= previousAttributes.fileSize;
259 | }
260 | _size += byteCount;
261 | [self controlCapacity];
262 | }
263 | else
264 | {
265 | NSLog(@"Failed to write to file %@", error);
266 | }
267 | }
268 |
269 | - (void)syncUpdateAccessDateForKey:(NSString*)key data:(NSData* (^)())lazyData
270 | {
271 | NSString *path = [self pathForKey:key];
272 | NSDate *now = [NSDate date];
273 | NSDictionary* attributes = @{NSFileModificationDate : now};
274 | NSError *error;
275 | NSFileManager *fileManager = [NSFileManager defaultManager];
276 | if (![[NSFileManager defaultManager] setAttributes:attributes ofItemAtPath:path error:&error])
277 | {
278 | if ([fileManager fileExistsAtPath:path isDirectory:nil])
279 | {
280 | NSLog(@"Set attributes failed with error %@", [error localizedDescription]);
281 | }
282 | else if (lazyData)
283 | { // The data was removed from disk cache but is still in memory
284 | NSData *data = lazyData();
285 | [self syncSetData:data forKey:key];
286 | }
287 | }
288 | }
289 |
290 | @end
291 |
292 | @implementation NSFileManager(hnk_utils)
293 |
294 | - (void)hnk_enumerateContentsOfDirectoryAtPath:(NSString*)path orderedByProperty:(NSString*)property ascending:(BOOL)ascending usingBlock:(void(^)(NSURL *url, NSUInteger idx, BOOL *stop))block
295 | {
296 | NSURL *directoryURL = [NSURL fileURLWithPath:path];
297 | NSError *error;
298 | NSArray *contents = [self contentsOfDirectoryAtURL:directoryURL includingPropertiesForKeys:@[property] options:kNilOptions error:&error];
299 | if (!contents)
300 | {
301 | NSLog(@"Failed to list directory with error %@", error);
302 | return;
303 | }
304 | contents = [contents sortedArrayUsingComparator:^NSComparisonResult(NSURL *url1, NSURL *url2) {
305 | id value1;
306 | if ([url1 getResourceValue:&value1 forKey:property error:nil]) return ascending ? NSOrderedAscending : NSOrderedDescending;
307 | id value2;
308 | if ([url2 getResourceValue:&value2 forKey:property error:nil]) return ascending ? NSOrderedDescending : NSOrderedAscending;
309 | return ascending ? [value1 compare:value2] : [value2 compare:value1];
310 | }];
311 | [contents enumerateObjectsUsingBlock:block];
312 | }
313 |
314 | @end
315 |
316 | @implementation NSString(hnk_utils)
317 |
318 | - (NSString*)hnk_MD5String
319 | {
320 | NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
321 | unsigned char result[CC_MD5_DIGEST_LENGTH];
322 | CC_MD5(data.bytes, (CC_LONG)data.length, result);
323 | NSMutableString *MD5String = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
324 | for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
325 | {
326 | [MD5String appendFormat:@"%02x",result[i]];
327 | }
328 | return MD5String;
329 | }
330 |
331 | - (BOOL)hnk_setValue:(NSString*)value forExtendedFileAttribute:(NSString*)attribute
332 | {
333 | const char *attributeC = [attribute UTF8String];
334 | const char *path = [self fileSystemRepresentation];
335 | const char *valueC = [value UTF8String];
336 | const int result = setxattr(path, attributeC, valueC, strlen(valueC), 0, 0);
337 | return result == 0;
338 | }
339 |
340 | - (NSString*)hnk_stringByEscapingFilename
341 | {
342 | // TODO: Add more characters to leave unescaped that are valid for paths but not for URLs
343 | NSString *filename = CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,(CFStringRef)self, CFSTR(" \\"), CFSTR("/:"), kCFStringEncodingUTF8));
344 | return filename;
345 | }
346 |
347 | - (NSString*)hnk_valueForExtendedFileAttribute:(NSString*)attribute
348 | {
349 | const char *attributeC = [attribute UTF8String];
350 | const char *path = [self fileSystemRepresentation];
351 |
352 | const ssize_t length = getxattr(path, attributeC, NULL, 0, 0, 0);
353 |
354 | if (length <= 0) return nil;
355 |
356 | char *buffer = malloc(length);
357 | getxattr(path, attributeC, buffer, length, 0, 0);
358 |
359 | NSString *value = [[NSString alloc] initWithBytes:buffer length:length encoding:NSUTF8StringEncoding];
360 |
361 | free(buffer);
362 |
363 | return value;
364 | }
365 |
366 | @end
367 |
--------------------------------------------------------------------------------
/MJAutoCompleteDemo/Vendor/Haneke/HNKDiskCache.m:
--------------------------------------------------------------------------------
1 | //
2 | // HNKDiskCache.m
3 | // Haneke
4 | //
5 | // Created by Hermes Pique on 8/21/14.
6 | // Copyright (c) 2014 Hermes Pique. All rights reserved.
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 | //
20 |
21 | #import "HNKDiskCache.h"
22 | #import // For hnk_MD5String
23 | #import // For hnk_setValue:forExtendedFileAttribute:
24 |
25 | NSString *const HNKExtendedFileAttributeKey = @"io.haneke.key";
26 |
27 | @interface NSString (hnk_utils)
28 |
29 | - (NSString*)hnk_MD5String;
30 | - (BOOL)hnk_setValue:(NSString*)value forExtendedFileAttribute:(NSString*)attribute;
31 | - (NSString*)hnk_stringByEscapingFilename;
32 | - (NSString*)hnk_valueForExtendedFileAttribute:(NSString*)attribute;
33 |
34 | @end
35 |
36 | @interface NSFileManager(Haneke)
37 |
38 | - (void)hnk_enumerateContentsOfDirectoryAtPath:(NSString*)path orderedByProperty:(NSString*)property ascending:(BOOL)ascending usingBlock:(void(^)(NSURL *url, NSUInteger idx, BOOL *stop))block;
39 |
40 | @end
41 |
42 | @implementation HNKDiskCache {
43 | NSString *_directory;
44 | }
45 |
46 | #pragma mark Initializing the cache
47 |
48 | - (instancetype)initWithDirectory:(NSString*)directory capacity:(unsigned long long)capacity
49 | {
50 | if (self = [super init])
51 | {
52 | _directory = [directory copy];
53 | _capacity = capacity;
54 | NSString *queueName = [NSString stringWithFormat:@"io.haneke.disk.%@", directory.lastPathComponent];
55 | _queue = dispatch_queue_create(queueName.UTF8String, NULL);
56 | dispatch_async(_queue, ^{
57 | [self calculateSize];
58 | [self controlCapacity];
59 | });
60 | }
61 | return self;
62 | }
63 |
64 | - (void)setCapacity:(unsigned long long)capacity
65 | {
66 | _capacity = capacity;
67 | dispatch_async(_queue, ^{
68 | [self controlCapacity];
69 | });
70 | }
71 |
72 | #pragma mark Setting and fetching data
73 |
74 | - (void)setData:(NSData*)data forKey:(NSString*)key
75 | {
76 | dispatch_async(_queue, ^{
77 | [self syncSetData:data forKey:key];
78 | });
79 | }
80 |
81 | - (void)fetchDataForKey:(NSString*)key success:(void (^)(NSData *data))successBlock failure:(void (^)(NSError *error))failureBlock
82 | {
83 | dispatch_async(_queue, ^{
84 | NSString *path = [self pathForKey:key];
85 | NSError *error = nil;
86 | NSData *data = [NSData dataWithContentsOfFile:path options:kNilOptions error:&error];
87 | if (!data)
88 | {
89 | if (failureBlock)
90 | {
91 | dispatch_async(dispatch_get_main_queue(), ^{
92 | failureBlock(error);
93 | });
94 | }
95 | return;
96 | }
97 |
98 | if (successBlock)
99 | {
100 | dispatch_async(dispatch_get_main_queue(), ^{
101 | successBlock(data);
102 | });
103 | }
104 |
105 | [self syncUpdateAccessDateForKey:key data:^NSData *{ return data; }];
106 | });
107 | }
108 |
109 | #pragma mark Removing data
110 |
111 | - (void)removeDataForKey:(NSString*)key
112 | {
113 | dispatch_async(_queue, ^{
114 | NSString *path = [self pathForKey:key];
115 | [self removeFileAtPath:path];
116 | });
117 | }
118 |
119 | - (void)removeAllData
120 | {
121 | dispatch_async(_queue, ^{
122 | NSFileManager *fileManager = [NSFileManager defaultManager];
123 | NSError *error;
124 | NSArray *contents = [fileManager contentsOfDirectoryAtPath:_directory error:&error];
125 | if (!contents) {
126 | NSLog(@"Failed to list directory with error %@", error);
127 | return;
128 | }
129 | for (NSString *pathComponent in contents)
130 | {
131 | NSString *path = [_directory stringByAppendingPathComponent:pathComponent];
132 | if (![fileManager removeItemAtPath:path error:&error])
133 | {
134 | NSLog(@"Failed to remove file with error %@", error);
135 | }
136 | }
137 | [self calculateSize];
138 | });
139 | }
140 |
141 | #pragma mark Managing data by access date
142 |
143 | - (void)enumerateDataByAccessDateUsingBlock:(void(^)(NSString *key, NSData *data, NSDate *accessDate, BOOL *stop))block
144 | {
145 | dispatch_async(_queue, ^{
146 | [[NSFileManager defaultManager] hnk_enumerateContentsOfDirectoryAtPath:_directory orderedByProperty:NSURLContentModificationDateKey ascending:NO usingBlock:^(NSURL *url, NSUInteger idx, BOOL *stop) {
147 | NSDate *accessDate;
148 | [url getResourceValue:&accessDate forKey:NSURLContentModificationDateKey error:nil];
149 |
150 | NSString *path = url.path;
151 | NSString *key = [path hnk_valueForExtendedFileAttribute:HNKExtendedFileAttributeKey];
152 | if (!key) return;
153 |
154 | NSData *data = [NSData dataWithContentsOfFile:path];
155 | if (!data) return;
156 |
157 | __block BOOL innerStop = NO;
158 |
159 | if (block)
160 | {
161 | block(key, data, accessDate, &innerStop);
162 | }
163 |
164 | if (innerStop) *stop = YES;
165 | }];
166 | });
167 | }
168 |
169 | - (void)updateAccessDateForKey:(NSString*)key data:(NSData* (^)())lazyData
170 | {
171 | dispatch_async(_queue, ^{
172 | [self syncUpdateAccessDateForKey:key data:lazyData];
173 | });
174 | }
175 |
176 | #pragma mark Private (in _queue)
177 |
178 | - (void)calculateSize
179 | {
180 | NSFileManager *fileManager = [NSFileManager defaultManager];
181 | _size = 0;
182 | NSError *error;
183 | NSArray *contents = [fileManager contentsOfDirectoryAtPath:_directory error:&error];
184 | if (!contents)
185 | {
186 | NSLog(@"Failed to list directory with error %@", error);
187 | return;
188 | }
189 | for (NSString *pathComponent in contents)
190 | {
191 | NSString *path = [_directory stringByAppendingPathComponent:pathComponent];
192 | NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:&error];
193 | if (!attributes) continue;
194 |
195 | _size += attributes.fileSize;
196 | }
197 | }
198 |
199 | - (void)controlCapacity
200 | {
201 | if (self.size <= self.capacity) return;
202 |
203 | NSFileManager *fileManager = [NSFileManager defaultManager];
204 | [fileManager hnk_enumerateContentsOfDirectoryAtPath:_directory orderedByProperty:NSURLContentModificationDateKey ascending:YES usingBlock:^(NSURL *url, NSUInteger idx, BOOL *stop) {
205 | NSString *path = url.path;
206 | [self removeFileAtPath:path];
207 | if (self.size <= self.capacity)
208 | {
209 | *stop = YES;
210 | }
211 | }];
212 | }
213 |
214 | - (NSString*)pathForKey:(NSString*)key
215 | {
216 | NSString *filename = [key hnk_stringByEscapingFilename];
217 | if (filename.length >= NAME_MAX)
218 | {
219 | NSString *MD5 = [key hnk_MD5String];
220 | NSString *pathExtension = key.pathExtension;
221 | filename = pathExtension.length > 0 ? [MD5 stringByAppendingPathExtension:pathExtension] : MD5;
222 | }
223 | NSString *path = [_directory stringByAppendingPathComponent:filename];
224 | return path;
225 | }
226 |
227 | - (void)removeFileAtPath:(NSString*)path
228 | {
229 | NSError *error;
230 | NSFileManager *fileManager = [NSFileManager defaultManager];
231 | NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:&error];
232 | if (attributes)
233 | {
234 | unsigned long long fileSize = attributes.fileSize;
235 | if ([fileManager removeItemAtPath:path error:&error])
236 | {
237 | _size -= fileSize;
238 | }
239 | else
240 | {
241 | NSLog(@"Failed to remove file with error %@", error);
242 | }
243 | }
244 | }
245 |
246 | - (void)syncSetData:(NSData*)data forKey:(NSString*)key
247 | {
248 | NSError *error;
249 | NSString *path = [self pathForKey:key];
250 | NSFileManager *fileManager = [NSFileManager defaultManager];
251 | NSDictionary *previousAttributes = [fileManager attributesOfItemAtPath:path error:nil];
252 | if ([data writeToFile:path options:kNilOptions error:&error])
253 | {
254 | [path hnk_setValue:key forExtendedFileAttribute:HNKExtendedFileAttributeKey];
255 | const NSUInteger byteCount = data.length;
256 | if (previousAttributes)
257 | {
258 | _size -= previousAttributes.fileSize;
259 | }
260 | _size += byteCount;
261 | [self controlCapacity];
262 | }
263 | else
264 | {
265 | NSLog(@"Failed to write to file %@", error);
266 | }
267 | }
268 |
269 | - (void)syncUpdateAccessDateForKey:(NSString*)key data:(NSData* (^)())lazyData
270 | {
271 | NSString *path = [self pathForKey:key];
272 | NSDate *now = [NSDate date];
273 | NSDictionary* attributes = @{NSFileModificationDate : now};
274 | NSError *error;
275 | NSFileManager *fileManager = [NSFileManager defaultManager];
276 | if (![[NSFileManager defaultManager] setAttributes:attributes ofItemAtPath:path error:&error])
277 | {
278 | if ([fileManager fileExistsAtPath:path isDirectory:nil])
279 | {
280 | NSLog(@"Set attributes failed with error %@", [error localizedDescription]);
281 | }
282 | else if (lazyData)
283 | { // The data was removed from disk cache but is still in memory
284 | NSData *data = lazyData();
285 | [self syncSetData:data forKey:key];
286 | }
287 | }
288 | }
289 |
290 | @end
291 |
292 | @implementation NSFileManager(hnk_utils)
293 |
294 | - (void)hnk_enumerateContentsOfDirectoryAtPath:(NSString*)path orderedByProperty:(NSString*)property ascending:(BOOL)ascending usingBlock:(void(^)(NSURL *url, NSUInteger idx, BOOL *stop))block
295 | {
296 | NSURL *directoryURL = [NSURL fileURLWithPath:path];
297 | NSError *error;
298 | NSArray *contents = [self contentsOfDirectoryAtURL:directoryURL includingPropertiesForKeys:@[property] options:kNilOptions error:&error];
299 | if (!contents)
300 | {
301 | NSLog(@"Failed to list directory with error %@", error);
302 | return;
303 | }
304 | contents = [contents sortedArrayUsingComparator:^NSComparisonResult(NSURL *url1, NSURL *url2) {
305 | id value1;
306 | if ([url1 getResourceValue:&value1 forKey:property error:nil]) return ascending ? NSOrderedAscending : NSOrderedDescending;
307 | id value2;
308 | if ([url2 getResourceValue:&value2 forKey:property error:nil]) return ascending ? NSOrderedDescending : NSOrderedAscending;
309 | return ascending ? [value1 compare:value2] : [value2 compare:value1];
310 | }];
311 | [contents enumerateObjectsUsingBlock:block];
312 | }
313 |
314 | @end
315 |
316 | @implementation NSString(hnk_utils)
317 |
318 | - (NSString*)hnk_MD5String
319 | {
320 | NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
321 | unsigned char result[CC_MD5_DIGEST_LENGTH];
322 | CC_MD5(data.bytes, (CC_LONG)data.length, result);
323 | NSMutableString *MD5String = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
324 | for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
325 | {
326 | [MD5String appendFormat:@"%02x",result[i]];
327 | }
328 | return MD5String;
329 | }
330 |
331 | - (BOOL)hnk_setValue:(NSString*)value forExtendedFileAttribute:(NSString*)attribute
332 | {
333 | const char *attributeC = [attribute UTF8String];
334 | const char *path = [self fileSystemRepresentation];
335 | const char *valueC = [value UTF8String];
336 | const int result = setxattr(path, attributeC, valueC, strlen(valueC), 0, 0);
337 | return result == 0;
338 | }
339 |
340 | - (NSString*)hnk_stringByEscapingFilename
341 | {
342 | // TODO: Add more characters to leave unescaped that are valid for paths but not for URLs
343 | NSString *filename = CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,(CFStringRef)self, CFSTR(" \\"), CFSTR("/:"), kCFStringEncodingUTF8));
344 | return filename;
345 | }
346 |
347 | - (NSString*)hnk_valueForExtendedFileAttribute:(NSString*)attribute
348 | {
349 | const char *attributeC = [attribute UTF8String];
350 | const char *path = [self fileSystemRepresentation];
351 |
352 | const ssize_t length = getxattr(path, attributeC, NULL, 0, 0, 0);
353 |
354 | if (length <= 0) return nil;
355 |
356 | char *buffer = malloc(length);
357 | getxattr(path, attributeC, buffer, length, 0, 0);
358 |
359 | NSString *value = [[NSString alloc] initWithBytes:buffer length:length encoding:NSUTF8StringEncoding];
360 |
361 | free(buffer);
362 |
363 | return value;
364 | }
365 |
366 | @end
367 |
--------------------------------------------------------------------------------