From 38b48e7cdfc06ec8b60d11d2c53e2b341a91c0b3 Mon Sep 17 00:00:00 2001 From: marcoschmickler Date: Mon, 10 May 2021 11:49:26 +0200 Subject: [PATCH] Nimbus --- Pods/Nimbus/LICENSE | 202 +++++ Pods/Nimbus/README.mdown | 13 + .../src/core/src/NIActions+Subclassing.h | 37 + Pods/Nimbus/src/core/src/NIActions.h | 422 ++++++++++ Pods/Nimbus/src/core/src/NIActions.m | 247 ++++++ Pods/Nimbus/src/core/src/NIButtonUtilities.h | 92 +++ Pods/Nimbus/src/core/src/NIButtonUtilities.m | 83 ++ Pods/Nimbus/src/core/src/NICommonMetrics.h | 172 ++++ Pods/Nimbus/src/core/src/NICommonMetrics.m | 69 ++ Pods/Nimbus/src/core/src/NIDebuggingTools.h | 194 +++++ Pods/Nimbus/src/core/src/NIDebuggingTools.m | 61 ++ .../Nimbus/src/core/src/NIDeviceOrientation.h | 92 +++ .../Nimbus/src/core/src/NIDeviceOrientation.m | 76 ++ Pods/Nimbus/src/core/src/NIError.h | 54 ++ Pods/Nimbus/src/core/src/NIError.m | 24 + .../Nimbus/src/core/src/NIFoundationMethods.h | 421 ++++++++++ .../Nimbus/src/core/src/NIFoundationMethods.m | 314 ++++++++ Pods/Nimbus/src/core/src/NIImageUtilities.h | 48 ++ Pods/Nimbus/src/core/src/NIImageUtilities.m | 24 + Pods/Nimbus/src/core/src/NIInMemoryCache.h | 316 ++++++++ Pods/Nimbus/src/core/src/NIInMemoryCache.m | 453 +++++++++++ Pods/Nimbus/src/core/src/NINetworkActivity.h | 101 +++ Pods/Nimbus/src/core/src/NINetworkActivity.m | 171 ++++ .../core/src/NINonEmptyCollectionTesting.h | 58 ++ .../core/src/NINonEmptyCollectionTesting.m | 35 + .../src/core/src/NINonRetainingCollections.h | 65 ++ .../src/core/src/NINonRetainingCollections.m | 36 + .../src/core/src/NIOperations+Subclassing.h | 20 + Pods/Nimbus/src/core/src/NIOperations.h | 209 +++++ Pods/Nimbus/src/core/src/NIOperations.m | 111 +++ Pods/Nimbus/src/core/src/NIPaths.h | 69 ++ Pods/Nimbus/src/core/src/NIPaths.m | 61 ++ .../src/core/src/NIPreprocessorMacros.h | 141 ++++ .../core/src/NIRuntimeClassModifications.h | 74 ++ .../core/src/NIRuntimeClassModifications.m | 37 + Pods/Nimbus/src/core/src/NISDKAvailability.h | 257 ++++++ Pods/Nimbus/src/core/src/NISDKAvailability.m | 72 ++ Pods/Nimbus/src/core/src/NISnapshotRotation.h | 224 ++++++ Pods/Nimbus/src/core/src/NISnapshotRotation.m | 299 +++++++ Pods/Nimbus/src/core/src/NIState.h | 84 ++ Pods/Nimbus/src/core/src/NIState.m | 58 ++ Pods/Nimbus/src/core/src/NIViewRecycler.h | 164 ++++ Pods/Nimbus/src/core/src/NIViewRecycler.m | 111 +++ .../src/core/src/NimbusCore+Additions.h | 26 + Pods/Nimbus/src/core/src/NimbusCore.h | 120 +++ .../src/core/src/UIResponder+NimbusCore.h | 26 + .../src/core/src/UIResponder+NimbusCore.m | 49 ++ .../src/NIPagingScrollView+Subclassing.h | 123 +++ .../pagingscrollview/src/NIPagingScrollView.h | 386 +++++++++ .../pagingscrollview/src/NIPagingScrollView.m | 760 ++++++++++++++++++ .../src/NIPagingScrollViewPage.h | 36 + .../src/NIPagingScrollViewPage.m | 26 + .../src/NimbusPagingScrollView.h | 53 ++ .../src/photos/src/NIPhotoAlbumScrollView.h | 170 ++++ .../src/photos/src/NIPhotoAlbumScrollView.m | 221 +++++ .../src/NIPhotoAlbumScrollViewDataSource.h | 93 +++ .../src/NIPhotoAlbumScrollViewDelegate.h | 55 ++ .../Nimbus/src/photos/src/NIPhotoScrollView.h | 162 ++++ .../Nimbus/src/photos/src/NIPhotoScrollView.m | 510 ++++++++++++ .../photos/src/NIPhotoScrollViewDelegate.h | 41 + .../photos/src/NIPhotoScrollViewPhotoSize.h | 31 + .../src/photos/src/NIPhotoScrubberView.h | 168 ++++ .../src/photos/src/NIPhotoScrubberView.m | 465 +++++++++++ .../photos/src/NIToolbarPhotoViewController.h | 201 +++++ .../photos/src/NIToolbarPhotoViewController.m | 533 ++++++++++++ Pods/Nimbus/src/photos/src/NimbusPhotos.h | 122 +++ 66 files changed, 10248 insertions(+) create mode 100644 Pods/Nimbus/LICENSE create mode 100644 Pods/Nimbus/README.mdown create mode 100644 Pods/Nimbus/src/core/src/NIActions+Subclassing.h create mode 100644 Pods/Nimbus/src/core/src/NIActions.h create mode 100644 Pods/Nimbus/src/core/src/NIActions.m create mode 100644 Pods/Nimbus/src/core/src/NIButtonUtilities.h create mode 100644 Pods/Nimbus/src/core/src/NIButtonUtilities.m create mode 100644 Pods/Nimbus/src/core/src/NICommonMetrics.h create mode 100644 Pods/Nimbus/src/core/src/NICommonMetrics.m create mode 100644 Pods/Nimbus/src/core/src/NIDebuggingTools.h create mode 100644 Pods/Nimbus/src/core/src/NIDebuggingTools.m create mode 100644 Pods/Nimbus/src/core/src/NIDeviceOrientation.h create mode 100644 Pods/Nimbus/src/core/src/NIDeviceOrientation.m create mode 100644 Pods/Nimbus/src/core/src/NIError.h create mode 100644 Pods/Nimbus/src/core/src/NIError.m create mode 100644 Pods/Nimbus/src/core/src/NIFoundationMethods.h create mode 100644 Pods/Nimbus/src/core/src/NIFoundationMethods.m create mode 100644 Pods/Nimbus/src/core/src/NIImageUtilities.h create mode 100644 Pods/Nimbus/src/core/src/NIImageUtilities.m create mode 100644 Pods/Nimbus/src/core/src/NIInMemoryCache.h create mode 100644 Pods/Nimbus/src/core/src/NIInMemoryCache.m create mode 100644 Pods/Nimbus/src/core/src/NINetworkActivity.h create mode 100644 Pods/Nimbus/src/core/src/NINetworkActivity.m create mode 100644 Pods/Nimbus/src/core/src/NINonEmptyCollectionTesting.h create mode 100644 Pods/Nimbus/src/core/src/NINonEmptyCollectionTesting.m create mode 100644 Pods/Nimbus/src/core/src/NINonRetainingCollections.h create mode 100644 Pods/Nimbus/src/core/src/NINonRetainingCollections.m create mode 100644 Pods/Nimbus/src/core/src/NIOperations+Subclassing.h create mode 100644 Pods/Nimbus/src/core/src/NIOperations.h create mode 100644 Pods/Nimbus/src/core/src/NIOperations.m create mode 100644 Pods/Nimbus/src/core/src/NIPaths.h create mode 100644 Pods/Nimbus/src/core/src/NIPaths.m create mode 100644 Pods/Nimbus/src/core/src/NIPreprocessorMacros.h create mode 100644 Pods/Nimbus/src/core/src/NIRuntimeClassModifications.h create mode 100644 Pods/Nimbus/src/core/src/NIRuntimeClassModifications.m create mode 100644 Pods/Nimbus/src/core/src/NISDKAvailability.h create mode 100644 Pods/Nimbus/src/core/src/NISDKAvailability.m create mode 100644 Pods/Nimbus/src/core/src/NISnapshotRotation.h create mode 100644 Pods/Nimbus/src/core/src/NISnapshotRotation.m create mode 100644 Pods/Nimbus/src/core/src/NIState.h create mode 100644 Pods/Nimbus/src/core/src/NIState.m create mode 100644 Pods/Nimbus/src/core/src/NIViewRecycler.h create mode 100644 Pods/Nimbus/src/core/src/NIViewRecycler.m create mode 100644 Pods/Nimbus/src/core/src/NimbusCore+Additions.h create mode 100644 Pods/Nimbus/src/core/src/NimbusCore.h create mode 100644 Pods/Nimbus/src/core/src/UIResponder+NimbusCore.h create mode 100644 Pods/Nimbus/src/core/src/UIResponder+NimbusCore.m create mode 100644 Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollView+Subclassing.h create mode 100644 Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollView.h create mode 100644 Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollView.m create mode 100644 Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollViewPage.h create mode 100644 Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollViewPage.m create mode 100644 Pods/Nimbus/src/pagingscrollview/src/NimbusPagingScrollView.h create mode 100644 Pods/Nimbus/src/photos/src/NIPhotoAlbumScrollView.h create mode 100644 Pods/Nimbus/src/photos/src/NIPhotoAlbumScrollView.m create mode 100644 Pods/Nimbus/src/photos/src/NIPhotoAlbumScrollViewDataSource.h create mode 100644 Pods/Nimbus/src/photos/src/NIPhotoAlbumScrollViewDelegate.h create mode 100644 Pods/Nimbus/src/photos/src/NIPhotoScrollView.h create mode 100644 Pods/Nimbus/src/photos/src/NIPhotoScrollView.m create mode 100644 Pods/Nimbus/src/photos/src/NIPhotoScrollViewDelegate.h create mode 100644 Pods/Nimbus/src/photos/src/NIPhotoScrollViewPhotoSize.h create mode 100644 Pods/Nimbus/src/photos/src/NIPhotoScrubberView.h create mode 100644 Pods/Nimbus/src/photos/src/NIPhotoScrubberView.m create mode 100644 Pods/Nimbus/src/photos/src/NIToolbarPhotoViewController.h create mode 100644 Pods/Nimbus/src/photos/src/NIToolbarPhotoViewController.m create mode 100644 Pods/Nimbus/src/photos/src/NimbusPhotos.h diff --git a/Pods/Nimbus/LICENSE b/Pods/Nimbus/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/Pods/Nimbus/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Pods/Nimbus/README.mdown b/Pods/Nimbus/README.mdown new file mode 100644 index 0000000..36411e8 --- /dev/null +++ b/Pods/Nimbus/README.mdown @@ -0,0 +1,13 @@ +Nimbus is an iOS framework whose feature set grows only as fast as its documentation. + +[![Build Status](https://travis-ci.org/jverkoey/nimbus.svg)](https://travis-ci.org/jverkoey/nimbus) + +Getting Started +--------------- + +- Visit the Nimbus website at [nimbuskit.info](http://nimbuskit.info). +- [Add Nimbus to your project](http://wiki.nimbuskit.info/Add-Nimbus-to-your-project). +- Follow Nimbus' development through its [version history](http://docs.nimbuskit.info/group___version-_history.html). +- See the [latest API diffs](http://docs.nimbuskit.info/group___version-9-3.html). +- Read the [Three20 Migration Guide](http://docs.nimbuskit.info/group___three20-_migration-_guide.html). +- Ask questions and get updates via the [Nimbus mailing list](http://groups.google.com/group/nimbusios). diff --git a/Pods/Nimbus/src/core/src/NIActions+Subclassing.h b/Pods/Nimbus/src/core/src/NIActions+Subclassing.h new file mode 100644 index 0000000..ff397bc --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIActions+Subclassing.h @@ -0,0 +1,37 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIActions.h" + +@interface NIObjectActions : NSObject + +@property (nonatomic, copy) NIActionBlock tapAction; +@property (nonatomic, copy) NIActionBlock detailAction; +@property (nonatomic, copy) NIActionBlock navigateAction; + +@property (nonatomic) SEL tapSelector; +@property (nonatomic) SEL detailSelector; +@property (nonatomic) SEL navigateSelector; + +@end + +@interface NIActions () + +@property (nonatomic, weak) id target; + +- (NIObjectActions *)actionForObjectOrClassOfObject:(id)object; + +@end diff --git a/Pods/Nimbus/src/core/src/NIActions.h b/Pods/Nimbus/src/core/src/NIActions.h new file mode 100644 index 0000000..2ea0fc8 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIActions.h @@ -0,0 +1,422 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +/** + * For attaching actions to objects. + * + * @ingroup NimbusCore + * @defgroup Actions Actions + * @{ + */ + +/** + * @param object An action was performed on this object. + * @param target The target that was attached to the NIActions instance. + * @param indexPath The index path of the object. + */ +typedef BOOL (^NIActionBlock)(id object, id target, NSIndexPath* indexPath); + +/** + * The attachable types of actions for NIAction. + */ +typedef NS_OPTIONS(NSUInteger, NIActionType) { + NIActionTypeNone = 0, + NIActionTypeTap = 1 << 0, + NIActionTypeDetail = 1 << 1, + NIActionTypeNavigate = 1 << 2, +}; + +/** + * The NIActions class provides a generic interface for attaching actions to objects. + * + * NIActions are used to implement user interaction in UITableViews and UICollectionViews via the + * corresponding classes (NITableViewActions and NICollectionViewActions) in the respective + * feature. NIActions separates the necessity + * + *

Types of Actions

+ * + * The three primary types of actions are: + * + * - buttons, + * - detail views, + * - and pushing a new controller onto the navigation controller. + * + *

Attaching Actions

+ * + * Actions may be attached to specific instances of objects or to entire classes of objects. When + * an action is attached to both a class of object and an instance of that class, only the instance + * action should be executed. + * + * All attachment methods return the object that was provided. This makes it simple to attach + * actions within an array creation statement. + * + * Actions come in two forms: blocks and selector invocations. Both can be attached to an object + * for each type of action and both will be executed, with the block being executed first. Blocks + * should be used for simple executions while selectors should be used when the action is complex. + * + * The following is an example of using NITableViewActions: + * +@code +NSArray *objects = @[ + [NITitleCellObject objectWithTitle:@"Implicit tap handler"], + [self.actions attachToObject:[NITitleCellObject objectWithTitle:@"Explicit tap handler"] + tapBlock: + ^BOOL(id object, id target) { + NSLog(@"Object was tapped with an explicit action: %@", object); + }] +]; + +[self.actions attachToClass:[NITitleCellObject class] + tapBlock: + ^BOOL(id object, id target) { + NSLog(@"Object was tapped: %@", object); + }]; +@endcode + * + */ +@interface NIActions : NSObject + +// Designated initializer. +- (id)initWithTarget:(id)target; + +#pragma mark Mapping Objects + +- (id)attachToObject:(id)object tapBlock:(NIActionBlock)action; +- (id)attachToObject:(id)object detailBlock:(NIActionBlock)action; +- (id)attachToObject:(id)object navigationBlock:(NIActionBlock)action; + +- (id)attachToObject:(id)object tapSelector:(SEL)selector; +- (id)attachToObject:(id)object detailSelector:(SEL)selector; +- (id)attachToObject:(id)object navigationSelector:(SEL)selector; + +#pragma mark Mapping Classes + +- (void)attachToClass:(Class)aClass tapBlock:(NIActionBlock)action; +- (void)attachToClass:(Class)aClass detailBlock:(NIActionBlock)action; +- (void)attachToClass:(Class)aClass navigationBlock:(NIActionBlock)action; + +- (void)attachToClass:(Class)aClass tapSelector:(SEL)selector; +- (void)attachToClass:(Class)aClass detailSelector:(SEL)selector; +- (void)attachToClass:(Class)aClass navigationSelector:(SEL)selector; + +#pragma mark Object State + +- (BOOL)isObjectActionable:(id)object; +- (NIActionType)attachedActionTypesForObject:(id)object; + ++ (id)objectFromKeyClass:(Class)keyClass map:(NSMutableDictionary *)map; + +@end + +#if defined __cplusplus +extern "C" { +#endif + +/** + * Returns a block that pushes an instance of the controllerClass onto the navigation stack. + * + * Allocates an instance of the controller class and calls the init selector. + * + * The target property of the NIActions instance must be an instance of UIViewController + * with an attached navigationController. + * + * @param controllerClass The class of controller to instantiate. + */ +NIActionBlock NIPushControllerAction(Class controllerClass); + +#if defined __cplusplus +}; +#endif + +/** The protocol for a data source that can be used with NIActions. */ +@protocol NIActionsDataSource + +/** + * The object located at the given indexPath. + * + * @param indexPath The index path of the requested object. + */ +- (id)objectAtIndexPath:(NSIndexPath *)indexPath; + +@end + +/** @name Creating Table View Actions */ + +/** + * Initializes a newly allocated table view actions object with the given controller. + * + * @attention This method is deprecated. Use the new method + * @link NIActions::initWithTarget: initWithTarget:@endlink. + * + * The controller is stored as a weak reference internally. + * + * @param controller The controller that will be used in action blocks. + * @fn NIActions::initWithController: + */ + +/** + * Initializes a newly allocated table view actions object with the given target. + * + * This is the designated initializer. + * + * The target is stored as a weak reference internally. + * + * @param target The target that will be provided to action blocks and on which selectors will + * be performed. + * @fn NIActions::initWithTarget: + */ + +/** @name Mapping Objects */ + +/** + * Attaches a tap action to the given object. + * + * A cell with an attached tap action will have its selectionStyle set to + * @c tableViewCellSelectionStyle when the cell is displayed. + * + * The action will be executed when the object's corresponding cell is tapped. The object argument + * of the block will be the object to which this action was attached. The target argument of the + * block will be this receiver's @c target. + * + * Return NO if the tap action is used to present a modal view controller. This provides a visual + * reminder to the user when the modal controller is dismissed as to which cell was tapped to invoke + * the modal controller. + * + * The tap action will be invoked first, followed by the navigation action if one is attached. + * + * @param object The object to attach the action to. This object must be contained within + * an NITableViewModel. + * @param action The tap action block. + * @returns The object that you attached this action to. + * @fn NIActions::attachToObject:tapBlock: + * @sa NIActions::attachToObject:tapSelector: + */ + +/** + * Attaches a detail action to the given object. + * + * When a cell with a detail action is displayed, its accessoryType will be set to + * UITableViewCellAccessoryDetailDisclosureButton. + * + * When a cell's detail button is tapped, the detail action block will be executed. The return + * value of the block is ignored. + * + * @param object The object to attach the action to. This object must be contained within + * an NITableViewModel. + * @param action The detail action block. + * @returns The object that you attached this action to. + * @fn NIActions::attachToObject:detailBlock: + */ + +/** + * Attaches a navigation action to the given object. + * + * When a cell with a navigation action is displayed, its accessoryType will be set to + * UITableViewCellAccessoryDisclosureIndicator if there is no detail action, otherwise the + * detail disclosure indicator takes precedence. + * + * When a cell with a navigation action is tapped the navigation block will be executed. + * + * If a tap action also exists for this object then the tap action will be executed first, followed + * by the navigation action. + * + * @param object The object to attach the action to. This object must be contained within + * an NITableViewModel. + * @param action The navigation action block. + * @returns The object that you attached this action to. + * @fn NIActions::attachToObject:navigationBlock: + */ + +/** + * Attaches a tap selector to the given object. + * + * The method signature for the selector is: + @code + - (BOOL)didTapObject:(id)object; + @endcode + * + * A cell with an attached tap action will have its selectionStyle set to + * @c tableViewCellSelectionStyle when the cell is displayed. + * + * The selector will be performed on the action object's target when a cell with a tap selector is + * tapped, unless that selector does not exist on the @c target in which case nothing happens. + * + * If the selector invocation returns YES then the cell will be deselected immediately after the + * invocation completes its execution. If NO is returned then the cell's selection will remain. + * + * Return NO if the tap action is used to present a modal view controller. This provides a visual + * reminder to the user when the modal controller is dismissed as to which cell was tapped to invoke + * the modal controller. + * + * The tap action will be invoked first, followed by the navigation action if one is attached. + * + * @param object The object to attach the selector to. This object must be contained within + * an NITableViewModel. + * @param selector The selector that will be invoked by this action. + * @returns The object that you attached this action to. + * @fn NIActions::attachToObject:tapSelector: + * @sa NIActions::attachToObject:tapBlock: + */ + +/** + * Attaches a detail selector to the given object. + * + * The method signature for the selector is: + @code + - (void)didTapObject:(id)object; + @endcode + * + * A cell with an attached tap action will have its selectionStyle set to + * @c tableViewCellSelectionStyle and its accessoryType set to + * @c UITableViewCellAccessoryDetailDisclosureButton when the cell is displayed. + * + * The selector will be performed on the action object's target when a cell with a detail selector's + * accessory indicator is tapped, unless that selector does not exist on the @c target in which + * case nothing happens. + * + * @param object The object to attach the selector to. This object must be contained within + * an NITableViewModel. + * @param selector The selector that will be invoked by this action. + * @returns The object that you attached this action to. + * @fn NIActions::attachToObject:detailSelector: + * @sa NIActions::attachToObject:detailBlock: + */ + +/** + * Attaches a navigation selector to the given object. + * + * The method signature for the selector is: + @code + - (void)didTapObject:(id)object; + @endcode + * + * A cell with an attached navigation action will have its selectionStyle set to + * @c tableViewCellSelectionStyle and its accessoryType set to + * @c UITableViewCellAccessoryDetailDisclosureButton, unless it also has an attached detail action, + * in which case its accessoryType will be set to @c UITableViewCellAccessoryDisclosureIndicator + * when the cell is displayed. + * + * The selector will be performed on the action object's target when a cell with a navigation + * selector is tapped, unless that selector does not exist on the @c target in which case nothing + * happens. + * + * @param object The object to attach the selector to. This object must be contained within + * an NITableViewModel. + * @param selector The selector that will be invoked by this action. + * @returns The object that you attached this action to. + * @fn NIActions::attachToObject:navigationSelector: + * @sa NIActions::attachToObject:navigationBlock: + */ + +/** @name Mapping Classes */ + +/** + * Attaches a tap block to a class. + * + * This method behaves similarly to attachToObject:tapBlock: except it attaches a tap action to + * all instances and subclassed instances of a given class. + * + * @param aClass The class to attach the action to. + * @param action The tap action block. + * @fn NIActions::attachToClass:tapBlock: + */ + +/** + * Attaches a detail block to a class. + * + * This method behaves similarly to attachToObject:detailBlock: except it attaches a detail action + * to all instances and subclassed instances of a given class. + * + * @param aClass The class to attach the action to. + * @param action The detail action block. + * @fn NIActions::attachToClass:detailBlock: + */ + +/** + * Attaches a navigation block to a class. + * + * This method behaves similarly to attachToObject:navigationBlock: except it attaches a navigation + * action to all instances and subclassed instances of a given class. + * + * @param aClass The class to attach the action to. + * @param action The navigation action block. + * @fn NIActions::attachToClass:navigationBlock: + */ + +/** + * Attaches a tap selector to a class. + * + * This method behaves similarly to attachToObject:tapBlock: except it attaches a tap action to + * all instances and subclassed instances of a given class. + * + * @param aClass The class to attach the action to. + * @param selector The tap selector. + * @fn NIActions::attachToClass:tapSelector: + */ + +/** + * Attaches a detail selector to a class. + * + * This method behaves similarly to attachToObject:detailBlock: except it attaches a detail action + * to all instances and subclassed instances of a given class. + * + * @param aClass The class to attach the action to. + * @param selector The tap selector. + * @fn NIActions::attachToClass:detailSelector: + */ + +/** + * Attaches a navigation selector to a class. + * + * This method behaves similarly to attachToObject:navigationBlock: except it attaches a navigation + * action to all instances and subclassed instances of a given class. + * + * @param aClass The class to attach the action to. + * @param selector The tap selector. + * @fn NIActions::attachToClass:navigationSelector: + */ + +/** @name Object State */ + +/** + * Returns whether or not the object has any actions attached to it. + * + * @fn NIActions::isObjectActionable: + */ + +/** + * Returns a bitmask of flags indicating the types of actions attached to the provided object. + * + * @fn NIActions::attachedActionTypesForObject: + */ + +/** + * Returns a mapped object from the given key class. + * + * If the key class is a subclass of any mapped key classes, the nearest ancestor class's mapped + * object will be returned and keyClass will be added to the map for future accesses. + * + * @param keyClass The key class that will be used to find the mapping in map. + * @param map A map of key classes to classes. May be modified if keyClass is a subclass of + * any existing key classes. + * @returns The mapped object if a match for keyClass was found in map. nil is returned + * otherwise. + * @fn NIActions::objectFromKeyClass:map: + */ + +/**@}*/// End of Actions ////////////////////////////////////////////////////////////////////////// diff --git a/Pods/Nimbus/src/core/src/NIActions.m b/Pods/Nimbus/src/core/src/NIActions.m new file mode 100644 index 0000000..1b8b4a7 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIActions.m @@ -0,0 +1,247 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIActions.h" +#import "NIActions+Subclassing.h" + +#import + +#import "NIDebuggingTools.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +@interface NIActions () + +@property (nonatomic, strong) NSMutableDictionary* objectToAction; +@property (nonatomic, strong) NSMutableDictionary* classToAction; +@property (nonatomic, strong) NSMutableSet* objectSet; + +@end + +@implementation NIActions + +- (id)initWithTarget:(id)target { + if ((self = [super init])) { + _target = target; + + _objectToAction = [[NSMutableDictionary alloc] init]; + _classToAction = [[NSMutableDictionary alloc] init]; + _objectSet = [[NSMutableSet alloc] init]; + } + return self; +} + +- (id)init { + return [self initWithTarget:nil]; +} + +#pragma mark - Private + +- (id)keyForObject:(id)object { + return @(object.hash); +} + +// +// actionForObject: and actionForClass: are used when attaching actions to objects and classes and +// will always return an NIObjectActions object. These methods should not be used for determining +// whether an action is attached to a given object or class. +// +// actionForObjectOrClassOfObject: determines whether an action has been attached to an object +// or class of object and then returns the NIObjectActions or nil if no actions have been attached. +// + +// Retrieves an NIObjectActions object for the given object or creates one if it doesn't yet exist +// so that actions may be attached. +- (NIObjectActions *)actionForObject:(id)object { + id key = [self keyForObject:object]; + NIObjectActions* action = [self.objectToAction objectForKey:key]; + if (nil == action) { + action = [[NIObjectActions alloc] init]; + [self.objectToAction setObject:action forKey:key]; + } + return action; +} + +// Retrieves an NIObjectActions object for the given class or creates one if it doesn't yet exist +// so that actions may be attached. +- (NIObjectActions *)actionForClass:(Class)class { + NIObjectActions* action = [self.classToAction objectForKey:class]; + if (nil == action) { + action = [[NIObjectActions alloc] init]; + [self.classToAction setObject:action forKey:(id)class]; + } + return action; +} + +// Fetches any attached actions for a given object. +- (NIObjectActions *)actionForObjectOrClassOfObject:(id)object { + id key = [self keyForObject:object]; + NIObjectActions* action = [self.objectToAction objectForKey:key]; + if (nil == action) { + action = [self.class objectFromKeyClass:object.class map:self.classToAction]; + } + return action; +} + +#pragma mark - Public + +- (id)attachToObject:(id)object tapBlock:(NIActionBlock)action { + [self.objectSet addObject:object]; + [self actionForObject:object].tapAction = action; + return object; +} + +- (id)attachToObject:(id)object detailBlock:(NIActionBlock)action { + [self.objectSet addObject:object]; + [self actionForObject:object].detailAction = action; + return object; +} + +- (id)attachToObject:(id)object navigationBlock:(NIActionBlock)action { + [self.objectSet addObject:object]; + [self actionForObject:object].navigateAction = action; + return object; +} + +- (id)attachToObject:(id)object tapSelector:(SEL)selector { + [self.objectSet addObject:object]; + [self actionForObject:object].tapSelector = selector; + return object; +} + +- (id)attachToObject:(id)object detailSelector:(SEL)selector { + [self.objectSet addObject:object]; + [self actionForObject:object].detailSelector = selector; + return object; +} + +- (id)attachToObject:(id)object navigationSelector:(SEL)selector { + [self.objectSet addObject:object]; + [self actionForObject:object].navigateSelector = selector; + return object; +} + +- (void)attachToClass:(Class)aClass tapBlock:(NIActionBlock)action { + [self actionForClass:aClass].tapAction = action; +} + +- (void)attachToClass:(Class)aClass detailBlock:(NIActionBlock)action { + [self actionForClass:aClass].detailAction = action; +} + +- (void)attachToClass:(Class)aClass navigationBlock:(NIActionBlock)action { + [self actionForClass:aClass].navigateAction = action; +} + +- (void)attachToClass:(Class)aClass tapSelector:(SEL)selector { + [self actionForClass:aClass].tapSelector = selector; +} + +- (void)attachToClass:(Class)aClass detailSelector:(SEL)selector { + [self actionForClass:aClass].detailSelector = selector; +} + +- (void)attachToClass:(Class)aClass navigationSelector:(SEL)selector { + [self actionForClass:aClass].navigateSelector = selector; +} + +- (BOOL)isObjectActionable:(id)object { + return NIActionTypeNone != [self attachedActionTypesForObject:object]; +} + +- (NIActionType)attachedActionTypesForObject:(id)object { + if (nil == object) { + return NIActionTypeNone; + } + + NIObjectActions* actions = [self actionForObjectOrClassOfObject:object]; + NIActionType attachedActionTypes = 0; + if (actions.tapAction || actions.tapSelector) { + attachedActionTypes |= NIActionTypeTap; + } + if (actions.detailAction || actions.detailSelector) { + attachedActionTypes |= NIActionTypeDetail; + } + if (actions.navigateAction || actions.navigateSelector) { + attachedActionTypes |= NIActionTypeNavigate; + } + return attachedActionTypes; +} + ++ (id)objectFromKeyClass:(Class)keyClass map:(NSMutableDictionary *)map { + id object = [map objectForKey:keyClass]; + + if (nil == object) { + // No mapping found for this key class, but it may be a subclass of another object that does + // have a mapping, so let's see what we can find. + Class superClass = nil; + for (Class class in map.allKeys) { + // We want to find the lowest node in the class hierarchy so that we pick the lowest ancestor + // in the hierarchy tree. + if ([keyClass isSubclassOfClass:class] + && (nil == superClass || [class isSubclassOfClass:superClass])) { + superClass = class; + } + } + + if (nil != superClass) { + object = [map objectForKey:superClass]; + + // Add this subclass to the map so that next time this result is instant. + [map setObject:object forKey:(id)keyClass]; + } + } + + if (nil == object) { + // We couldn't find a mapping at all so let's add an empty mapping. + [map setObject:[NSNull class] forKey:(id)keyClass]; + + } else if (object == [NSNull class]) { + // Don't return null mappings. + object = nil; + } + + return object; +} + +@end + +@implementation NIObjectActions +@end + +NIActionBlock NIPushControllerAction(Class controllerClass) { + return [^(id object, id target, NSIndexPath* indexPath) { + // You must initialize the actions object with initWithTarget: and pass a valid + // controller. + NIDASSERT(nil != target); + NIDASSERT([target isKindOfClass:[UIViewController class]]); + UIViewController *controller = target; + + if (nil != controller && [controller isKindOfClass:[UIViewController class]]) { + // No navigation controller to push this new controller; this controller + // is going to be lost. + NIDASSERT(nil != controller.navigationController); + + UIViewController* controllerToPush = [[controllerClass alloc] init]; + [controller.navigationController pushViewController:controllerToPush + animated:YES]; + } + + return NO; + } copy]; +} diff --git a/Pods/Nimbus/src/core/src/NIButtonUtilities.h b/Pods/Nimbus/src/core/src/NIButtonUtilities.h new file mode 100644 index 0000000..4174e25 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIButtonUtilities.h @@ -0,0 +1,92 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +#if defined __cplusplus +extern "C" { +#endif + +/** + * For manipulating UIButton objects. + * + * @ingroup NimbusCore + * @defgroup Button-Utilities Button Utilities + * @{ + * + * The methods provided here make it possible to specify different properties for different button + * states in a scalable way. For example, you can define a stylesheet class that has a number of + * class methods that return the various properties for buttons. + * +@code +@implementation Stylesheet + ++ (UIImage *)backgroundImageForButtonWithState:(UIControlState)state { + if (state & UIControlStateHighlighted) { + return [UIImage imageNamed:@"button_highlighted"]; + + } else if (state == UIControlStateNormal) { + return [UIImage imageNamed:@"button"]; + } + return nil; +} + +@end + +// The result of the implementation above will set the background images for the button's +// highlighted and default states. +NIApplyBackgroundImageSelectorToButton(@selector(backgroundImageForButtonWithState:), + [Stylesheet class], + button); +@endcode + */ + +/** + * Sets the images for a button's states. + * + * @param selector A selector of the form: + * (UIImage *)imageWithControlState:(UIControlState)controlState + * @param target The target upon which the selector will be invoked. + * @param button The button object whose properties should be modified. + */ +void NIApplyImageSelectorToButton(SEL selector, id target, UIButton* button); + +/** + * Sets the background images for a button's states. + * + * @param selector A selector of the form: + * (UIImage *)backgroundImageWithControlState:(UIControlState)controlState + * @param target The target upon which the selector will be invoked. + * @param button The button object whose properties should be modified. + */ +void NIApplyBackgroundImageSelectorToButton(SEL selector, id target, UIButton* button); + +/** + * Sets the title colors for a button's states. + * + * @param selector A selector of the form: + * (UIColor *)colorWithControlState:(UIControlState)controlState + * @param target The target upon which the selector will be invoked. + * @param button The button object whose properties should be modified. + */ +void NIApplyTitleColorSelectorToButton(SEL selector, id target, UIButton* button); + +/**@}*/// End of Button Utilities ///////////////////////////////////////////////////////////////// + +#if defined __cplusplus +}; +#endif diff --git a/Pods/Nimbus/src/core/src/NIButtonUtilities.m b/Pods/Nimbus/src/core/src/NIButtonUtilities.m new file mode 100644 index 0000000..aa135a1 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIButtonUtilities.m @@ -0,0 +1,83 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIButtonUtilities.h" + +void NIApplyImageSelectorToButton(SEL selector, id target, UIButton* button) { + typedef UIImage* (*ImageMethod)(id, SEL, UIControlState); + ImageMethod method = (ImageMethod)[target methodForSelector:selector]; + UIImage* image = nil; + + image = method(target, selector, UIControlStateNormal); + [button setImage:image forState:UIControlStateNormal]; + + image = method(target, selector, UIControlStateHighlighted); + [button setImage:image forState:UIControlStateHighlighted]; + + image = method(target, selector, UIControlStateDisabled); + [button setImage:image forState:UIControlStateDisabled]; + + image = method(target, selector, UIControlStateSelected); + [button setImage:image forState:UIControlStateSelected]; + + UIControlState selectedHighlightState = UIControlStateSelected | UIControlStateHighlighted; + image = method(target, selector, selectedHighlightState); + [button setImage:image forState:selectedHighlightState]; +} + +void NIApplyBackgroundImageSelectorToButton(SEL selector, id target, UIButton* button) { + typedef UIImage* (*ImageMethod)(id, SEL, UIControlState); + ImageMethod method = (ImageMethod)[target methodForSelector:selector]; + UIImage* image = nil; + + image = method(target, selector, UIControlStateNormal); + [button setBackgroundImage:image forState:UIControlStateNormal]; + + image = method(target, selector, UIControlStateHighlighted); + [button setBackgroundImage:image forState:UIControlStateHighlighted]; + + image = method(target, selector, UIControlStateDisabled); + [button setBackgroundImage:image forState:UIControlStateDisabled]; + + image = method(target, selector, UIControlStateSelected); + [button setBackgroundImage:image forState:UIControlStateSelected]; + + UIControlState selectedHighlightState = UIControlStateSelected | UIControlStateHighlighted; + image = method(target, selector, selectedHighlightState); + [button setBackgroundImage:image forState:selectedHighlightState]; +} + +void NIApplyTitleColorSelectorToButton(SEL selector, id target, UIButton* button) { + typedef UIColor* (*ColorMethod)(id, SEL, UIControlState); + ColorMethod method = (ColorMethod)[target methodForSelector:selector]; + UIColor* color = nil; + + color = method(target, selector, UIControlStateNormal); + [button setTitleColor:color forState:UIControlStateNormal]; + + color = method(target, selector, UIControlStateHighlighted); + [button setTitleColor:color forState:UIControlStateHighlighted]; + + color = method(target, selector, UIControlStateDisabled); + [button setTitleColor:color forState:UIControlStateDisabled]; + + color = method(target, selector, UIControlStateSelected); + [button setTitleColor:color forState:UIControlStateSelected]; + + UIControlState selectedHighlightState = UIControlStateSelected | UIControlStateHighlighted; + color = method(target, selector, selectedHighlightState); + [button setTitleColor:color forState:selectedHighlightState]; +} diff --git a/Pods/Nimbus/src/core/src/NICommonMetrics.h b/Pods/Nimbus/src/core/src/NICommonMetrics.h new file mode 100644 index 0000000..aa3c782 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NICommonMetrics.h @@ -0,0 +1,172 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +#import "NIPreprocessorMacros.h" + +#if defined __cplusplus +extern "C" { +#endif + +/** + * For common system metrics. + * + * If you work with system metrics in any way it can be a pain in the ass to figure out what the + * exact metrics are. Figuring out how long it takes the status bar to animate is not something you + * should be spending your time on. The metrics in this file are provided as a means of unifying a + * number of system metrics for use in your applications. + * + *

What Qualifies as a Common Metric

+ * + * Common metrics are system components, such as the dimensions of a toolbar in + * a particular orientation or the duration of a standard animation. This is + * not the place to put feature-specific metrics, such as the height of a photo scrubber + * view. + * + *

Examples

+ * + *

Positioning a Toolbar

+ * + * The following example updates the position and height of a toolbar when the device + * orientation is changing. This ensures that, in landscape mode on the iPhone, the toolbar + * is slightly shorter to accomodate the smaller height of the screen. + * + * @code + * - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation + * duration:(NSTimeInterval)duration { + * [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration]; + * + * CGRect toolbarFrame = self.toolbar.frame; + * toolbarFrame.size.height = NIToolbarHeightForOrientation(toInterfaceOrientation); + * toolbarFrame.origin.y = self.view.bounds.size.height - toolbarFrame.size.height; + * self.toolbar.frame = toolbarFrame; + * } + * @endcode + * + * @ingroup NimbusCore + * @defgroup Common-Metrics Common Metrics + * @{ + */ + +#ifndef UIViewAutoresizingFlexibleMargins +#define UIViewAutoresizingFlexibleMargins (UIViewAutoresizingFlexibleLeftMargin \ + | UIViewAutoresizingFlexibleTopMargin \ + | UIViewAutoresizingFlexibleRightMargin \ + | UIViewAutoresizingFlexibleBottomMargin) +#endif + +#ifndef UIViewAutoresizingFlexibleDimensions +#define UIViewAutoresizingFlexibleDimensions (UIViewAutoresizingFlexibleWidth \ + | UIViewAutoresizingFlexibleHeight) +#endif + +#ifndef UIViewAutoresizingNavigationBar +#define UIViewAutoresizingNavigationBar (UIViewAutoresizingFlexibleWidth \ + | UIViewAutoresizingFlexibleBottomMargin) +#endif + +#ifndef UIViewAutoresizingToolbar +#define UIViewAutoresizingToolbar (UIViewAutoresizingFlexibleWidth \ + | UIViewAutoresizingFlexibleTopMargin) +#endif + +/** + * The recommended number of points for a minimum tappable area. + * + * Value: 44 + */ +CGFloat NIMinimumTapDimension(void); + +/** + * Fetch the height of a toolbar in a given orientation. + * + * On the iPhone: + * - Portrait: 44 + * - Landscape: 33 + * + * On the iPad: always 44 + */ +CGFloat NIToolbarHeightForOrientation(UIInterfaceOrientation orientation); + +/** + * The animation curve used when changing the status bar's visibility. + * + * This is the curve of the animation used by + * -[[UIApplication sharedApplication] setStatusBarHidden:withAnimation:]. + * + * Value: UIViewAnimationCurveEaseIn + */ +UIViewAnimationCurve NIStatusBarAnimationCurve(void); + +/** + * The animation duration used when changing the status bar's visibility. + * + * This is the duration of the animation used by + * -[[UIApplication sharedApplication] setStatusBarHidden:withAnimation:]. + * + * Value: 0.3 seconds + */ +NSTimeInterval NIStatusBarAnimationDuration(void); + +/** + * The animation curve used when the status bar's bounds change (when a call is received, + * for example). + * + * Value: UIViewAnimationCurveEaseInOut + */ +UIViewAnimationCurve NIStatusBarBoundsChangeAnimationCurve(void); + +/** + * The animation duration used when the status bar's bounds change (when a call is received, + * for example). + * + * Value: 0.35 seconds + */ +NSTimeInterval NIStatusBarBoundsChangeAnimationDuration(void); + +/** + * Get the status bar's current height. + * + * If the status bar is hidden this will return 0. + * + * This is generally 20 when the status bar is its normal height. + */ +CGFloat NIStatusBarHeight(void) NI_EXTENSION_UNAVAILABLE_IOS(""); + +/** + * The animation duration when the device is rotating to a new orientation. + * + * Value: 0.4 seconds if the device is being rotated 90 degrees. + * 0.8 seconds if the device is being rotated 180 degrees. + * + * @param isFlippingUpsideDown YES if the device is being flipped upside down. + */ +NSTimeInterval NIDeviceRotationDuration(BOOL isFlippingUpsideDown); + +/** + * The padding around a standard cell in a table view. + * + * Value: 10 pixels on all sides. + */ +UIEdgeInsets NICellContentPadding(void); + +#if defined __cplusplus +}; +#endif + +/**@}*/// End of Common Metrics /////////////////////////////////////////////////////////////////// diff --git a/Pods/Nimbus/src/core/src/NICommonMetrics.m b/Pods/Nimbus/src/core/src/NICommonMetrics.m new file mode 100644 index 0000000..7aae538 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NICommonMetrics.m @@ -0,0 +1,69 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NICommonMetrics.h" + +#import "NISDKAvailability.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +CGFloat NIMinimumTapDimension(void) { + return 44; +} + +CGFloat NIToolbarHeightForOrientation(UIInterfaceOrientation orientation) { + return (NIIsPad() + ? 44 + : (UIInterfaceOrientationIsPortrait(orientation) + ? 44 + : 33)); +} + +UIViewAnimationCurve NIStatusBarAnimationCurve(void) { + return UIViewAnimationCurveEaseIn; +} + +NSTimeInterval NIStatusBarAnimationDuration(void) { + return 0.3; +} + +UIViewAnimationCurve NIStatusBarBoundsChangeAnimationCurve(void) { + return UIViewAnimationCurveEaseInOut; +} + +NSTimeInterval NIStatusBarBoundsChangeAnimationDuration(void) { + return 0.35; +} + +CGFloat NIStatusBarHeight(void) { + CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame]; + + // We take advantage of the fact that the status bar will always be wider than it is tall + // in order to avoid having to check the status bar orientation. + CGFloat statusBarHeight = MIN(statusBarFrame.size.width, statusBarFrame.size.height); + + return statusBarHeight; +} + +NSTimeInterval NIDeviceRotationDuration(BOOL isFlippingUpsideDown) { + return isFlippingUpsideDown ? 0.8 : 0.4; +} + +UIEdgeInsets NICellContentPadding(void) { + return UIEdgeInsetsMake(10, 10, 10, 10); +} diff --git a/Pods/Nimbus/src/core/src/NIDebuggingTools.h b/Pods/Nimbus/src/core/src/NIDebuggingTools.h new file mode 100644 index 0000000..1a2b84b --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIDebuggingTools.h @@ -0,0 +1,194 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Forked from Three20 June 10, 2011 - Copyright 2009-2011 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +/** + * For inspecting code and writing to logs in debug builds. + * + * Nearly all of the following macros will only do anything if the DEBUG macro is defined. + * The recommended way to enable the debug tools is to specify DEBUG in the "Preprocessor Macros" + * field in your application's Debug target settings. Be careful not to set this for your release + * or app store builds because this will enable code that may cause your app to be rejected. + * + * + *

Debug Assertions

+ * + * Debug assertions are a lightweight "sanity check". They won't crash the app, nor will they + * be included in release builds. They will halt the app's execution when debugging so + * that you can inspect the values that caused the failure. + * + * @code + * NIDASSERT(statement); + * @endcode + * + * If statement is false, the statement will be written to the log and if a debugger is + * attached, the app will break on the assertion line. + * + * + *

Debug Logging

+ * + * @code + * NIDPRINT(@"formatted log text %d", param1); + * @endcode + * + * Print the given formatted text to the log. + * + * @code + * NIDPRINTMETHODNAME(); + * @endcode + * + * Print the current method name to the log. + * + * @code + * NIDCONDITIONLOG(statement, @"formatted log text %d", param1); + * @endcode + * + * If statement is true, then the formatted text will be written to the log. + * + * @code + * NIDINFO/NIDWARNING/NIDERROR(@"formatted log text %d", param1); + * @endcode + * + * Will only write the formatted text to the log if NIMaxLogLevel is greater than the respective + * NID* method's log level. See below for log levels. + * + * The default maximum log level is NILOGLEVEL_WARNING. + * + *

Turning up the log level while the app is running

+ * + * NIMaxLogLevel is declared a non-const extern so that you can modify it at runtime. This can + * be helpful for turning logging on while the application is running. + * + * @code + * NIMaxLogLevel = NILOGLEVEL_INFO; + * @endcode + * + * @ingroup NimbusCore + * @defgroup Debugging-Tools Debugging Tools + * @{ + */ + +#if defined(DEBUG) || defined(NI_DEBUG) + +/** + * Assertions that only fire when DEBUG is defined. + * + * An assertion is like a programmatic breakpoint. Use it for sanity checks to save headache while + * writing your code. + */ +#import + +#if defined __cplusplus +extern "C" { +#endif + +int NIIsInDebugger(void); + +#if defined __cplusplus +} +#endif + +#if TARGET_IPHONE_SIMULATOR +// We leave the __asm__ in this macro so that when a break occurs, we don't have to step out of +// a "breakInDebugger" function. +#define NIDASSERT(xx) { if (!(xx)) { NIDPRINT(@"NIDASSERT failed: %s", #xx); \ +if (NIDebugAssertionsShouldBreak && NIIsInDebugger()) { __asm__("int $3\n" : : ); } } \ +} ((void)0) +#else +#define NIDASSERT(xx) { if (!(xx)) { NIDPRINT(@"NIDASSERT failed: %s", #xx); \ +if (NIDebugAssertionsShouldBreak && NIIsInDebugger()) { raise(SIGTRAP); } } \ +} ((void)0) +#endif // #if TARGET_IPHONE_SIMULATOR + +#else +#define NIDASSERT(xx) ((void)0) +#endif // #if defined(DEBUG) || defined(NI_DEBUG) + + +#define NILOGLEVEL_INFO 5 +#define NILOGLEVEL_WARNING 3 +#define NILOGLEVEL_ERROR 1 + +/** + * The maximum log level to output for Nimbus debug logs. + * + * This value may be changed at run-time. + * + * The default value is NILOGLEVEL_WARNING. + */ +extern NSInteger NIMaxLogLevel; + +/** + * Whether or not debug assertions should halt program execution like a breakpoint when they fail. + * + * An example of when this is used is in unit tests, when failure cases are tested that will + * fire debug assertions. + * + * The default value is YES. + */ +extern BOOL NIDebugAssertionsShouldBreak; + +/** + * Only writes to the log when DEBUG is defined. + * + * This log method will always write to the log, regardless of log levels. It is used by all + * of the other logging methods in Nimbus' debugging library. + */ +#if defined(DEBUG) || defined(NI_DEBUG) +#define NIDPRINT(xx, ...) NSLog(@"%s(%d): " xx, __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) +#else +#define NIDPRINT(xx, ...) ((void)0) +#endif // #if defined(DEBUG) || defined(NI_DEBUG) + +/** + * Write the containing method's name to the log using NIDPRINT. + */ +#define NIDPRINTMETHODNAME() NIDPRINT(@"%s", __PRETTY_FUNCTION__) + +#if defined(DEBUG) || defined(NI_DEBUG) +/** + * Only writes to the log if condition is satisified. + * + * This macro powers the level-based loggers. It can also be used for conditionally enabling + * families of logs. + */ +#define NIDCONDITIONLOG(condition, xx, ...) { if ((condition)) { NIDPRINT(xx, ##__VA_ARGS__); } \ +} ((void)0) +#else +#define NIDCONDITIONLOG(condition, xx, ...) ((void)0) +#endif // #if defined(DEBUG) || defined(NI_DEBUG) + + +/** + * Only writes to the log if NIMaxLogLevel >= NILOGLEVEL_ERROR. + */ +#define NIDERROR(xx, ...) NIDCONDITIONLOG((NILOGLEVEL_ERROR <= NIMaxLogLevel), xx, ##__VA_ARGS__) + +/** + * Only writes to the log if NIMaxLogLevel >= NILOGLEVEL_WARNING. + */ +#define NIDWARNING(xx, ...) NIDCONDITIONLOG((NILOGLEVEL_WARNING <= NIMaxLogLevel), \ +xx, ##__VA_ARGS__) + +/** + * Only writes to the log if NIMaxLogLevel >= NILOGLEVEL_INFO. + */ +#define NIDINFO(xx, ...) NIDCONDITIONLOG((NILOGLEVEL_INFO <= NIMaxLogLevel), xx, ##__VA_ARGS__) + +/**@}*/// End of Debugging Tools ////////////////////////////////////////////////////////////////// diff --git a/Pods/Nimbus/src/core/src/NIDebuggingTools.m b/Pods/Nimbus/src/core/src/NIDebuggingTools.m new file mode 100644 index 0000000..c81e569 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIDebuggingTools.m @@ -0,0 +1,61 @@ +// +// Copyright 2011-2014 NimbusKit +// Copyright 2009-2011 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIDebuggingTools.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +NSInteger NIMaxLogLevel = NILOGLEVEL_WARNING; +BOOL NIDebugAssertionsShouldBreak = YES; + +#if defined(DEBUG) || defined(NI_DEBUG) + +#import +#import + +// From: http://developer.apple.com/mac/library/qa/qa2004/qa1361.html +int NIIsInDebugger(void) { + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); + + // We're being debugged if the P_TRACED flag is set. + + return (info.kp_proc.p_flag & P_TRACED) != 0; +} + +#endif // #if defined(DEBUG) || defined(NI_DEBUG) diff --git a/Pods/Nimbus/src/core/src/NIDeviceOrientation.h b/Pods/Nimbus/src/core/src/NIDeviceOrientation.h new file mode 100644 index 0000000..f3b6b7a --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIDeviceOrientation.h @@ -0,0 +1,92 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Forked from Three20 June 10, 2011 - Copyright 2009-2011 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +#import "NIPreprocessorMacros.h" + +#if defined __cplusplus +extern "C" { +#endif + +/** + * For dealing with device orientations. + * + *

Examples

+ * + *

Use NIIsSupportedOrientation to Enable Autorotation

+ * + * @code + * - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation { + * return NIIsSupportedOrientation(toInterfaceOrientation); + * } + * @endcode + * + * @ingroup NimbusCore + * @defgroup Device-Orientation Device Orientation + * @{ + */ + +/** + * For use in shouldAutorotateToInterfaceOrientation: + * + * On iPhone/iPod touch: + * + * Returns YES if the orientation is portrait, landscape left, or landscape right. + * This helps to ignore upside down and flat orientations. + * + * On iPad: + * + * Always returns YES. + */ +BOOL NIIsSupportedOrientation(UIInterfaceOrientation orientation); + +/** + * Returns the application's current interface orientation. + * + * This is simply a convenience method for [UIApplication sharedApplication].statusBarOrientation. + * + * @returns The current interface orientation. + */ +UIInterfaceOrientation NIInterfaceOrientation(void) NI_EXTENSION_UNAVAILABLE_IOS(""); + +/** + * Returns YES if the device is a phone and the orientation is landscape. + * + * This is a useful check for phone landscape mode which often requires + * additional logic to handle the smaller vertical real estate. + * + * @returns YES if the device is a phone and orientation is landscape. + */ +BOOL NIIsLandscapePhoneOrientation(UIInterfaceOrientation orientation); + +/** + * Creates an affine transform for the given device orientation. + * + * This is useful for creating a transformation matrix for a view that has been added + * directly to the window and doesn't automatically have its transformation modified. + */ +CGAffineTransform NIRotateTransformForOrientation(UIInterfaceOrientation orientation); + +#if defined __cplusplus +}; +#endif + +/**@}*/// End of Device Orientation /////////////////////////////////////////////////////////////// + diff --git a/Pods/Nimbus/src/core/src/NIDeviceOrientation.m b/Pods/Nimbus/src/core/src/NIDeviceOrientation.m new file mode 100644 index 0000000..fefb006 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIDeviceOrientation.m @@ -0,0 +1,76 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Forked from Three20 June 10, 2011 - Copyright 2009-2011 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIDeviceOrientation.h" + +#import + +#import "NIDebuggingTools.h" +#import "NISDKAvailability.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +BOOL NIIsSupportedOrientation(UIInterfaceOrientation orientation) { + if (NIIsPad()) { + return YES; + + } else { + switch (orientation) { + case UIInterfaceOrientationPortrait: + case UIInterfaceOrientationLandscapeLeft: + case UIInterfaceOrientationLandscapeRight: + return YES; + default: + return NO; + } + } +} + +UIInterfaceOrientation NIInterfaceOrientation(void) { + UIInterfaceOrientation orient = [UIApplication sharedApplication].statusBarOrientation; + + // This code used to use the Three20 navigator to find the currently visible view controller and + // fall back to checking its orientation if we didn't know the status bar's orientation. + // It's unclear when this was actually necessary, though, so this assertion is here to try + // to find that case. If this assertion fails then the repro case needs to be analyzed and + // this method made more robust to handle that case. + NIDASSERT(UIDeviceOrientationUnknown != orient); + + return orient; +} + +BOOL NIIsLandscapePhoneOrientation(UIInterfaceOrientation orientation) { + return NIIsPhone() && UIInterfaceOrientationIsLandscape(orientation); +} + +CGAffineTransform NIRotateTransformForOrientation(UIInterfaceOrientation orientation) { + if (orientation == UIInterfaceOrientationLandscapeLeft) { + return CGAffineTransformMakeRotation((CGFloat)(M_PI * 1.5)); + + } else if (orientation == UIInterfaceOrientationLandscapeRight) { + return CGAffineTransformMakeRotation((CGFloat)(M_PI / 2.0)); + + } else if (orientation == UIInterfaceOrientationPortraitUpsideDown) { + return CGAffineTransformMakeRotation((CGFloat)(-M_PI)); + + } else { + return CGAffineTransformIdentity; + } +} diff --git a/Pods/Nimbus/src/core/src/NIError.h b/Pods/Nimbus/src/core/src/NIError.h new file mode 100644 index 0000000..89a14db --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIError.h @@ -0,0 +1,54 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +/** + * For defining various error types used throughout the Nimbus framework. + * + * @ingroup NimbusCore + * @defgroup Errors Errors + * @{ + */ + +/** The Nimbus error domain. */ +extern NSString* const NINimbusErrorDomain; + +/** The key used for images in the error's userInfo. */ +extern NSString* const NIImageErrorKey; + +/** NSError codes in NINimbusErrorDomain. */ +typedef enum { + /** The image is too small to be used. */ + NIImageTooSmall = 1, +} NINimbusErrorDomainCode; + + +/**@}*/// End of Errors /////////////////////////////////////////////////////////////////////////// + +/** + *

Example

+ * + * @code + * error = [NSError errorWithDomain: NINimbusErrorDomain + * code: NIImageTooSmall + * userInfo: [NSDictionary dictionaryWithObject: image + * forKey: NIImageErrorKey]]; + * @endcode + * + * @enum NINimbusErrorDomainCode + */ diff --git a/Pods/Nimbus/src/core/src/NIError.m b/Pods/Nimbus/src/core/src/NIError.m new file mode 100644 index 0000000..652829b --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIError.m @@ -0,0 +1,24 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIError.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +NSString* const NINimbusErrorDomain = @"com.nimbus.error"; +NSString* const NIImageErrorKey = @"image"; diff --git a/Pods/Nimbus/src/core/src/NIFoundationMethods.h b/Pods/Nimbus/src/core/src/NIFoundationMethods.h new file mode 100644 index 0000000..cc8be31 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIFoundationMethods.h @@ -0,0 +1,421 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +#import "NIPreprocessorMacros.h" + +#if defined __cplusplus +extern "C" { +#endif + +/** + * For filling in gaps in Apple's Foundation framework. + * + * @ingroup NimbusCore + * @defgroup Foundation-Methods Foundation Methods + * @{ + * + * Utility methods save time and headache. You've probably written dozens of your own. Nimbus + * hopes to provide an ever-growing set of convenience methods that compliment the Foundation + * framework's functionality. + */ + +#pragma mark - NSInvocation Methods + +/** + * Construct an NSInvocation with an instance of an object and a selector + * + * @return an NSInvocation that will call the given selector on the given target + */ +NSInvocation* NIInvocationWithInstanceTarget(NSObject* target, SEL selector); + +/** + * This method is deprecated. Please use NIInvocationWithInstanceTarget([object class], selector) + * instead. + */ +NSInvocation* NIInvocationWithClassTarget(Class targetClass, SEL selector) __NI_DEPRECATED_METHOD; + +#pragma mark - CGRect Methods + +/** + * For manipulating CGRects. + * + * @defgroup CGRect-Methods CGRect Methods + * @{ + * + * These methods provide additional means of modifying the edges of CGRects beyond the basics + * included in CoreGraphics. + */ + +/** + * Modifies only the right and bottom edges of a CGRect. + * + * @return a CGRect with dx and dy subtracted from the width and height. + * + * Example result: CGRectMake(x, y, w - dx, h - dy) + */ +CGRect NIRectContract(CGRect rect, CGFloat dx, CGFloat dy); + +/** + * Modifies only the right and bottom edges of a CGRect. + * + * @return a CGRect with dx and dy added to the width and height. + * + * Example result: CGRectMake(x, y, w + dx, h + dy) + */ +CGRect NIRectExpand(CGRect rect, CGFloat dx, CGFloat dy); + +/** + * Modifies only the top and left edges of a CGRect. + * + * @return a CGRect whose origin has been offset by dx, dy, and whose size has been + * contracted by dx, dy. + * + * Example result: CGRectMake(x + dx, y + dy, w - dx, h - dy) + */ +CGRect NIRectShift(CGRect rect, CGFloat dx, CGFloat dy); + +/** + * Inverse of UIEdgeInsetsInsetRect. + * + * Example result: CGRectMake(x - left, y - top, + * w + left + right, h + top + bottom) + */ +CGRect NIEdgeInsetsOutsetRect(CGRect rect, UIEdgeInsets outsets); + +/** + * Returns the x position that will center size within containerSize. + * + * Example result: floorf((containerSize.width - size.width) / 2.f) + */ +CGFloat NICenterX(CGSize containerSize, CGSize size); + +/** + * Returns the y position that will center size within containerSize. + * + * Example result: floorf((containerSize.height - size.height) / 2.f) + */ +CGFloat NICenterY(CGSize containerSize, CGSize size); + +/** + * Returns a rect that will center viewToCenter within containerView. + * + * @return a CGPoint that will center viewToCenter within containerView. + */ +CGRect NIFrameOfCenteredViewWithinView(UIView* viewToCenter, UIView* containerView); + +/** + * Returns the size of the string with given UILabel properties. + */ +CGSize NISizeOfStringWithLabelProperties(NSString *string, CGSize constrainedToSize, UIFont *font, NSLineBreakMode lineBreakMode, NSInteger numberOfLines); + +/**@}*/ + + +#pragma mark - NSRange Methods + +/** + * For manipulating NSRange. + * + * @defgroup NSRange-Methods NSRange Methods + * @{ + */ + +/** + * Create an NSRange object from a CFRange object. + * + * @return an NSRange object with the same values as the CFRange object. + * + * @attention This has the potential to behave unexpectedly because it converts the + * CFRange's long values to unsigned integers. Nimbus will fire off a debug + * assertion at runtime if the value will be chopped or the sign will change. + * Even though the assertion will fire, the method will still chop or change + * the sign of the values so you should take care to fix this. + */ +NSRange NIMakeNSRangeFromCFRange(CFRange range); + +/**@}*/ + + +#pragma mark - NSData Methods + +/** + * For manipulating NSData. + * + * @defgroup NSData-Methods NSData Methods + * @{ + */ + +/** + * Calculates an md5 hash of the data using CC_MD5. + */ +NSString* NIMD5HashFromData(NSData* data); + +/** + * Calculates a sha1 hash of the data using CC_SHA1. + */ +NSString* NISHA1HashFromData(NSData* data); + +/**@}*/ + + +#pragma mark - NSString Methods + +/** + * For manipulating NSStrings. + * + * @defgroup NSString-Methods NSString Methods + * @{ + */ + +/** + * Calculates an md5 hash of the string using CC_MD5. + * + * Treats the string as UTF8. + */ +NSString* NIMD5HashFromString(NSString* string); + +/** + * Calculates a sha1 hash of the string using CC_SHA1. + * + * Treats the string as UTF8. + */ +NSString* NISHA1HashFromString(NSString* string); + +/** + * Returns a Boolean value indicating whether the string is a NSString object that contains only + * whitespace and newlines. + */ +BOOL NIIsStringWithWhitespaceAndNewlines(NSString* string); + +/** + * Compares two strings expressing software versions. + * + * The comparison is (except for the development version provisions noted below) lexicographic + * string comparison. So as long as the strings being compared use consistent version formats, + * a variety of schemes are supported. For example "3.02" < "3.03" and "3.0.2" < "3.0.3". If you + * mix such schemes, like trying to compare "3.02" and "3.0.3", the result may not be what you + * expect. + * + * Development versions are also supported by adding an "a" character and more version info after + * it. For example "3.0a1" or "3.01a4". The way these are handled is as follows: if the parts + * before the "a" are different, the parts after the "a" are ignored. If the parts before the "a" + * are identical, the result of the comparison is the result of NUMERICALLY comparing the parts + * after the "a". If the part after the "a" is empty, it is treated as if it were "0". If one + * string has an "a" and the other does not (e.g. "3.0" and "3.0a1") the one without the "a" + * is newer. + * + * Examples (?? means undefined): + * @htmlonly + *
+ *   "3.0" = "3.0"
+ *   "3.0a2" = "3.0a2"
+ *   "3.0" > "2.5"
+ *   "3.1" > "3.0"
+ *   "3.0a1" < "3.0"
+ *   "3.0a1" < "3.0a4"
+ *   "3.0a2" < "3.0a19"  <-- numeric, not lexicographic
+ *   "3.0a" < "3.0a1"
+ *   "3.02" < "3.03"
+ *   "3.0.2" < "3.0.3"
+ *   "3.00" ?? "3.0"
+ *   "3.02" ?? "3.0.3"
+ *   "3.02" ?? "3.0.2"
+ * 
+ * @endhtmlonly + */ +NSComparisonResult NICompareVersionStrings(NSString* string1, NSString* string2); + +/** + * Parses a URL query string into a dictionary where the values are arrays. + * + * A query string is one that looks like ¶m1=value1¶m2=value2... + * + * The resulting NSDictionary will contain keys for each parameter name present in the query. + * The value for each key will be an NSArray which may be empty if the key is simply present + * in the query. Otherwise each object in the array with be an NSString corresponding to a value + * in the query for that parameter. + */ +NSDictionary* NIQueryDictionaryFromStringUsingEncoding(NSString* string, NSStringEncoding encoding); + +/** + * Returns a string that has been escaped for use as a URL parameter. + */ +NSString* NIStringByAddingPercentEscapesForURLParameterString(NSString* parameter); + +/** + * Appends a dictionary of query parameters to a string, adding the ? character if necessary. + */ +NSString* NIStringByAddingQueryDictionaryToString(NSString* string, NSDictionary* query); + +/**@}*/ + + +#pragma mark - CGFloat Methods + +/** + * For manipulating CGFloat. + * + * @defgroup CGFloat-Methods CGFloat Methods + * @{ + * + * These methods provide math functions on CGFloats. They could easily be replaced with + * but that is currently (Xcode 5.0) incompatible with CLANG_ENABLE_MODULES (on by default for + * many projects/targets). We'll use CG_INLINE because this really should be completely inline. + */ + +#if CGFLOAT_IS_DOUBLE + #define NI_CGFLOAT_EPSILON DBL_EPSILON +#else + #define NI_CGFLOAT_EPSILON FLT_EPSILON +#endif + +/** + * fabs()/fabsf() sized for CGFloat + */ +CG_INLINE CGFloat NICGFloatAbs(CGFloat x) { +#if CGFLOAT_IS_DOUBLE + return (CGFloat)fabs(x); +#else + return (CGFloat)fabsf(x); +#endif +} + +/** + * floor()/floorf() sized for CGFloat + */ +CG_INLINE CGFloat NICGFloatFloor(CGFloat x) { +#if CGFLOAT_IS_DOUBLE + return (CGFloat)floor(x); +#else + return (CGFloat)floorf(x); +#endif +} + +/** + * ceil()/ceilf() sized for CGFloat + */ +CG_INLINE CGFloat NICGFloatCeil(CGFloat x) { +#if CGFLOAT_IS_DOUBLE + return (CGFloat)ceil(x); +#else + return (CGFloat)ceilf(x); +#endif +} + +/** + * round()/roundf() sized for CGFloat + */ +CG_INLINE CGFloat NICGFloatRound(CGFloat x) { +#if CGFLOAT_IS_DOUBLE + return (CGFloat)round(x); +#else + return (CGFloat)roundf(x); +#endif +} + +/** + * sqrt()/sqrtf() sized for CGFloat + */ +CG_INLINE CGFloat NICGFloatSqRt(CGFloat x) { +#if CGFLOAT_IS_DOUBLE + return (CGFloat)sqrt(x); +#else + return (CGFloat)sqrtf(x); +#endif +} + +/** + * copysign()/copysignf() sized for CGFloat + */ +CG_INLINE CGFloat NICGFloatCopySign(CGFloat x, CGFloat y) { +#if CGFLOAT_IS_DOUBLE + return (CGFloat)copysign(x, y); +#else + return (CGFloat)copysignf(x, y); +#endif +} + +/** + * pow()/powf() sized for CGFloat + */ +CG_INLINE CGFloat NICGFloatPow(CGFloat x, CGFloat y) { +#if CGFLOAT_IS_DOUBLE + return (CGFloat)pow(x, y); +#else + return (CGFloat)powf(x, y); +#endif +} + +/** + * cos()/cosf() sized for CGFloat + */ +CG_INLINE CGFloat NICGFloatCos(CGFloat x) { +#if CGFLOAT_IS_DOUBLE + return (CGFloat)cos(x); +#else + return (CGFloat)cosf(x); +#endif +} + +/**@}*/ + +#pragma mark - General Purpose Methods + +/** + * For general purpose foundation type manipulation. + * + * @defgroup General-Purpose-Methods General Purpose Methods + * @{ + */ + +/** + * Deprecated method. Use NIBoundf instead. + */ +CGFloat boundf(CGFloat value, CGFloat min, CGFloat max) __NI_DEPRECATED_METHOD; // Use NIBoundf instead. MAINTENANCE: Remove by Feb 28, 2014. + +/** + * Deprecated method. Use NIBoundi instead. + */ +NSInteger boundi(NSInteger value, NSInteger min, NSInteger max) __NI_DEPRECATED_METHOD; // Use NIBoundi instead. MAINTENANCE: Remove by Feb 28, 2014. + +/** + * Bounds a given value within the min and max values. + * + * If max < min then value will be min. + * + * @returns min <= result <= max + */ +CGFloat NIBoundf(CGFloat value, CGFloat min, CGFloat max); + +/** + * Bounds a given value within the min and max values. + * + * If max < min then value will be min. + * + * @returns min <= result <= max + */ +NSInteger NIBoundi(NSInteger value, NSInteger min, NSInteger max); + +/**@}*/ + +#if defined __cplusplus +}; +#endif + +/**@}*/// End of Foundation Methods /////////////////////////////////////////////////////////////// diff --git a/Pods/Nimbus/src/core/src/NIFoundationMethods.m b/Pods/Nimbus/src/core/src/NIFoundationMethods.m new file mode 100644 index 0000000..16c9601 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIFoundationMethods.m @@ -0,0 +1,314 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIFoundationMethods.h" + +#import "NIDebuggingTools.h" +#import +#import + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +#pragma mark - NSInvocation + +NSInvocation* NIInvocationWithInstanceTarget(NSObject *targetObject, SEL selector) { + NSMethodSignature* sig = [targetObject methodSignatureForSelector:selector]; + NSInvocation* inv = [NSInvocation invocationWithMethodSignature:sig]; + [inv setTarget:targetObject]; + [inv setSelector:selector]; + return inv; +} + +// Deprecated. Please delete on the next minor version upgrade. +NSInvocation* NIInvocationWithClassTarget(Class targetClass, SEL selector) { + return NIInvocationWithInstanceTarget((NSObject *)targetClass, selector); +} + +#pragma mark - CGRect + +CGRect NIRectContract(CGRect rect, CGFloat dx, CGFloat dy) { + return CGRectMake(rect.origin.x, rect.origin.y, rect.size.width - dx, rect.size.height - dy); +} + +CGRect NIRectExpand(CGRect rect, CGFloat dx, CGFloat dy) { + return CGRectMake(rect.origin.x, rect.origin.y, rect.size.width + dx, rect.size.height + dy); +} + +CGRect NIRectShift(CGRect rect, CGFloat dx, CGFloat dy) { + return CGRectOffset(NIRectContract(rect, dx, dy), dx, dy); +} + +CGRect NIEdgeInsetsOutsetRect(CGRect rect, UIEdgeInsets outsets) { + return CGRectMake(rect.origin.x - outsets.left, + rect.origin.y - outsets.top, + rect.size.width + outsets.left + outsets.right, + rect.size.height + outsets.top + outsets.bottom); +} + +CGFloat NICenterX(CGSize containerSize, CGSize size) { + return NICGFloatFloor((containerSize.width - size.width) / 2.f); +} + +CGFloat NICenterY(CGSize containerSize, CGSize size) { + return NICGFloatFloor((containerSize.height - size.height) / 2.f); +} + +CGRect NIFrameOfCenteredViewWithinView(UIView* viewToCenter, UIView* containerView) { + CGPoint origin; + CGSize containerViewSize = containerView.bounds.size; + CGSize viewSize = viewToCenter.frame.size; + origin.x = NICenterX(containerViewSize, viewSize); + origin.y = NICenterY(containerViewSize, viewSize); + return CGRectMake(origin.x, origin.y, viewSize.width, viewSize.height); +} + +CGSize NISizeOfStringWithLabelProperties(NSString *string, CGSize constrainedToSize, UIFont *font, NSLineBreakMode lineBreakMode, NSInteger numberOfLines) { + if (string.length == 0) { + return CGSizeZero; + } + + CGFloat lineHeight = font.lineHeight; + CGSize size = CGSizeZero; + + if (numberOfLines == 1) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + size = [string sizeWithFont:font forWidth:constrainedToSize.width lineBreakMode:lineBreakMode]; +#pragma clang diagnostic pop + } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + size = [string sizeWithFont:font constrainedToSize:constrainedToSize lineBreakMode:lineBreakMode]; +#pragma clang diagnostic pop + if (numberOfLines > 0) { + size.height = MIN(size.height, numberOfLines * lineHeight); + } + } + + return size; +} + +#pragma mark - NSRange + +NSRange NIMakeNSRangeFromCFRange(CFRange range) { + // CFRange stores its values in signed longs, but we're about to copy the values into + // unsigned integers, let's check whether we're about to lose any information. + NIDASSERT(range.location >= 0 && range.location <= NSIntegerMax); + NIDASSERT(range.length >= 0 && range.length <= NSIntegerMax); + return NSMakeRange(range.location, range.length); +} + +#pragma mark - NSData + +NSString* NIMD5HashFromData(NSData* data) { + unsigned char result[CC_MD5_DIGEST_LENGTH]; + bzero(result, sizeof(result)); + CC_MD5_CTX md5Context; + CC_MD5_Init(&md5Context); + size_t bytesHashed = 0; + while (bytesHashed < [data length]) { + CC_LONG updateSize = 1024 * 1024; + if (([data length] - bytesHashed) < (size_t)updateSize) { + updateSize = (CC_LONG)([data length] - bytesHashed); + } + CC_MD5_Update(&md5Context, (char *)[data bytes] + bytesHashed, updateSize); + bytesHashed += updateSize; + } + CC_MD5_Final(result, &md5Context); + + return [NSString stringWithFormat: + @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7], + result[8], result[9], result[10], result[11], result[12], result[13], result[14], + result[15] + ]; +} + +NSString* NISHA1HashFromData(NSData* data) { + unsigned char result[CC_SHA1_DIGEST_LENGTH]; + bzero(result, sizeof(result)); + CC_SHA1_CTX sha1Context; + CC_SHA1_Init(&sha1Context); + size_t bytesHashed = 0; + while (bytesHashed < [data length]) { + CC_LONG updateSize = 1024 * 1024; + if (([data length] - bytesHashed) < (size_t)updateSize) { + updateSize = (CC_LONG)([data length] - bytesHashed); + } + CC_SHA1_Update(&sha1Context, (char *)[data bytes] + bytesHashed, updateSize); + bytesHashed += updateSize; + } + CC_SHA1_Final(result, &sha1Context); + + return [NSString stringWithFormat: + @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7], + result[8], result[9], result[10], result[11], result[12], result[13], result[14], + result[15], result[16], result[17], result[18], result[19] + ]; +} + +#pragma mark - NSString + +NSString* NIMD5HashFromString(NSString* string) { + return NIMD5HashFromData([string dataUsingEncoding:NSUTF8StringEncoding]); +} + +NSString* NISHA1HashFromString(NSString* string) { + return NISHA1HashFromData([string dataUsingEncoding:NSUTF8StringEncoding]); +} + +BOOL NIIsStringWithWhitespaceAndNewlines(NSString* string) { + NSCharacterSet* notWhitespaceAndNewlines = [[NSCharacterSet whitespaceAndNewlineCharacterSet] invertedSet]; + return [string isKindOfClass:[NSString class]] && [string rangeOfCharacterFromSet:notWhitespaceAndNewlines].length == 0; +} + +NSComparisonResult NICompareVersionStrings(NSString* string1, NSString* string2) { + NSArray *oneComponents = [string1 componentsSeparatedByString:@"a"]; + NSArray *twoComponents = [string2 componentsSeparatedByString:@"a"]; + + // The parts before the "a" + NSString *oneMain = [oneComponents objectAtIndex:0]; + NSString *twoMain = [twoComponents objectAtIndex:0]; + + // If main parts are different, return that result, regardless of alpha part + NSComparisonResult mainDiff; + if ((mainDiff = [oneMain compare:twoMain]) != NSOrderedSame) { + return mainDiff; + } + + // At this point the main parts are the same; just deal with alpha stuff + // If one has an alpha part and the other doesn't, the one without is newer + if ([oneComponents count] < [twoComponents count]) { + return NSOrderedDescending; + + } else if ([oneComponents count] > [twoComponents count]) { + return NSOrderedAscending; + + } else if ([oneComponents count] == 1) { + // Neither has an alpha part, and we know the main parts are the same + return NSOrderedSame; + } + + // At this point the main parts are the same and both have alpha parts. Compare the alpha parts + // numerically. If it's not a valid number (including empty string) it's treated as zero. + NSNumber *oneAlpha = [NSNumber numberWithInt:[[oneComponents objectAtIndex:1] intValue]]; + NSNumber *twoAlpha = [NSNumber numberWithInt:[[twoComponents objectAtIndex:1] intValue]]; + return [oneAlpha compare:twoAlpha]; +} + +NSDictionary* NIQueryDictionaryFromStringUsingEncoding(NSString* string, NSStringEncoding encoding) { + NSCharacterSet* delimiterSet = [NSCharacterSet characterSetWithCharactersInString:@"&;"]; + NSMutableDictionary* pairs = [NSMutableDictionary dictionary]; + NSScanner* scanner = [[NSScanner alloc] initWithString:string]; + + while (![scanner isAtEnd]) { + NSString* pairString = nil; + [scanner scanUpToCharactersFromSet:delimiterSet intoString:&pairString]; + [scanner scanCharactersFromSet:delimiterSet intoString:NULL]; + + NSArray* kvPair = [pairString componentsSeparatedByString:@"="]; + if (kvPair.count == 1 || kvPair.count == 2) { + NSString* key = [kvPair[0] stringByReplacingPercentEscapesUsingEncoding:encoding]; + + NSMutableArray* values = pairs[key]; + if (nil == values) { + values = [NSMutableArray array]; + pairs[key] = values; + } + + if (kvPair.count == 1) { + [values addObject:[NSNull null]]; + + } else if (kvPair.count == 2) { + NSString* value = [kvPair[1] stringByReplacingPercentEscapesUsingEncoding:encoding]; + [values addObject:value]; + } + } + } + return [pairs copy]; +} + +NSString* NIStringByAddingPercentEscapesForURLParameterString(NSString* parameter) { + CFStringRef buffer = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, + (__bridge CFStringRef)parameter, + NULL, + (__bridge CFStringRef)@"!*'();:@&=+$,/?%#[]", + kCFStringEncodingUTF8); + + NSString* result = [NSString stringWithString:(__bridge NSString *)buffer]; + CFRelease(buffer); + return result; +} + +NSString* NIStringByAddingQueryDictionaryToString(NSString* string, NSDictionary* query) { + NSMutableArray* pairs = [NSMutableArray array]; + for (NSString* key in [query keyEnumerator]) { + NSString* value = NIStringByAddingPercentEscapesForURLParameterString([query objectForKey:key]); + NSString* pair = [NSString stringWithFormat:@"%@=%@", key, value]; + [pairs addObject:pair]; + } + + NSString* params = [pairs componentsJoinedByString:@"&"]; + if ([string rangeOfString:@"?"].location == NSNotFound) { + return [string stringByAppendingFormat:@"?%@", params]; + + } else { + return [string stringByAppendingFormat:@"&%@", params]; + } +} + +#pragma mark - General Purpose + +// Deprecated. +CGFloat boundf(CGFloat value, CGFloat min, CGFloat max) { + return NIBoundf(value, min, max); +} + +// Deprecated. +NSInteger boundi(NSInteger value, NSInteger min, NSInteger max) { + return NIBoundi(value, min, max); +} + +CGFloat NIBoundf(CGFloat value, CGFloat min, CGFloat max) { + if (max < min) { + max = min; + } + CGFloat bounded = value; + if (bounded > max) { + bounded = max; + } + if (bounded < min) { + bounded = min; + } + return bounded; +} + +NSInteger NIBoundi(NSInteger value, NSInteger min, NSInteger max) { + if (max < min) { + max = min; + } + NSInteger bounded = value; + if (bounded > max) { + bounded = max; + } + if (bounded < min) { + bounded = min; + } + return bounded; +} diff --git a/Pods/Nimbus/src/core/src/NIImageUtilities.h b/Pods/Nimbus/src/core/src/NIImageUtilities.h new file mode 100644 index 0000000..e0dc4dd --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIImageUtilities.h @@ -0,0 +1,48 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +#if defined __cplusplus +extern "C" { +#endif + +/** + * For manipulating UIImage objects. + * + * @ingroup NimbusCore + * @defgroup Image-Utilities Image Utilities + * @{ + */ + +/** + * Returns an image that is stretchable from the center. + * + * A common use of this method is to create an image that has rounded corners for a button + * and then assign a stretchable version of that image to a UIButton. + * + * This stretches the middle vertical and horizontal line of pixels, so use care when + * stretching images that have gradients. For example, an image with a vertical gradient + * can be stretched horizontally, but will look odd if stretched vertically. + */ +UIImage* NIStretchableImageFromImage(UIImage* image); + +/**@}*/// End of Image Utilities ////////////////////////////////////////////////////////////////// + +#if defined __cplusplus +}; +#endif diff --git a/Pods/Nimbus/src/core/src/NIImageUtilities.m b/Pods/Nimbus/src/core/src/NIImageUtilities.m new file mode 100644 index 0000000..f7b866a --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIImageUtilities.m @@ -0,0 +1,24 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIImageUtilities.h" + +UIImage* NIStretchableImageFromImage(UIImage* image) { + const CGSize size = image.size; + NSInteger midX = (NSInteger)(size.width / 2.f); + NSInteger midY = (NSInteger)(size.height / 2.f); + return [image stretchableImageWithLeftCapWidth:midX topCapHeight:midY]; +} diff --git a/Pods/Nimbus/src/core/src/NIInMemoryCache.h b/Pods/Nimbus/src/core/src/NIInMemoryCache.h new file mode 100644 index 0000000..dc5dbb7 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIInMemoryCache.h @@ -0,0 +1,316 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +#import "NIPreprocessorMacros.h" + +/** + * For storing and accessing objects in memory. + * + * The base class, NIMemoryCache, is a generic object store that may be used for anything that + * requires support for expiration. + * + * @ingroup NimbusCore + * @defgroup In-Memory-Caches In-Memory Caches + * @{ + */ + +/** + * An in-memory cache for storing objects with expiration support. + * + * The Nimbus in-memory object cache allows you to store objects in memory with an expiration + * date attached. Objects with expiration dates drop out of the cache when they have expired. + */ +@interface NIMemoryCache : NSObject + +// Designated initializer. +- (id)initWithCapacity:(NSUInteger)capacity; + +- (NSUInteger)count; + +- (void)storeObject:(id)object withName:(NSString *)name; +- (void)storeObject:(id)object withName:(NSString *)name expiresAfter:(NSDate *)expirationDate; + +- (void)removeObjectWithName:(NSString *)name; +- (void)removeAllObjectsWithPrefix:(NSString *)prefix; +- (void)removeAllObjects; + +- (id)objectWithName:(NSString *)name; +- (BOOL)containsObjectWithName:(NSString *)name; +- (NSDate *)dateOfLastAccessWithName:(NSString *)name; + +- (NSString *)nameOfLeastRecentlyUsedObject; +- (NSString *)nameOfMostRecentlyUsedObject; + +- (void)reduceMemoryUsage; + +// Subclassing + +- (BOOL)shouldSetObject:(id)object withName:(NSString *)name previousObject:(id)previousObject; +- (void)didSetObject:(id)object withName:(NSString *)name; +- (void)willRemoveObject:(id)object withName:(NSString *)name; + +// Deprecated method. Use shouldSetObject:withName:previousObject: instead. +- (BOOL)willSetObject:(id)object withName:(NSString *)name previousObject:(id)previousObject __NI_DEPRECATED_METHOD; + +@end + +/** + * An in-memory cache for storing images with caps on the total number of pixels. + * + * When reduceMemoryUsage is called, the least recently used images are removed from the cache + * until the numberOfPixels is below maxNumberOfPixelsUnderStress. + * + * When an image is added to the cache that causes the memory usage to pass the max, the + * least recently used images are removed from the cache until the numberOfPixels is below + * maxNumberOfPixels. + * + * By default the image memory cache has no limit to its pixel count. You must explicitly + * set this value in your application. + * + * @attention If the cache is too small to fit the newly added image, then all images + * will end up being removed including the one being added. + * + * @see Nimbus::imageMemoryCache + * @see Nimbus::setImageMemoryCache: + */ +@interface NIImageMemoryCache : NIMemoryCache + +@property (nonatomic, readonly) unsigned long long numberOfPixels; + +@property (nonatomic) unsigned long long maxNumberOfPixels; // Default: 0 (unlimited) +@property (nonatomic) unsigned long long maxNumberOfPixelsUnderStress; // Default: 0 (unlimited) + +@end + +/**@}*/// End of In-Memory Cache ////////////////////////////////////////////////////////////////// + +/** @name Creating an In-Memory Cache */ + +/** + * Initializes a newly allocated cache with the given capacity. + * + * @returns An in-memory cache initialized with the given capacity. + * @fn NIMemoryCache::initWithCapacity: + */ + +/** @name Storing Objects in the Cache */ + +/** + * Stores an object in the cache. + * + * The object will be stored without an expiration date. The object will stay in the cache until + * it's bumped out due to the cache's memory limit. + * + * @param object The object being stored in the cache. + * @param name The name used as a key to store this object. + * @fn NIMemoryCache::storeObject:withName: + */ + +/** + * Stores an object in the cache with an expiration date. + * + * If an object is stored with an expiration date that has already passed then the object will + * not be stored in the cache and any existing object will be removed. The rationale behind this + * is that the object would be removed from the cache the next time it was accessed anyway. + * + * @param object The object being stored in the cache. + * @param name The name used as a key to store this object. + * @param expirationDate A date after which this object is no longer valid in the cache. + * @fn NIMemoryCache::storeObject:withName:expiresAfter: + */ + +/** @name Removing Objects from the Cache */ + +/** + * Removes an object from the cache with the given name. + * + * @param name The name used as a key to store this object. + * @fn NIMemoryCache::removeObjectWithName: + */ + +/** + * Removes all objects from the cache with a given prefix. + * + * This method requires a scan of the cache entries. + * + * @param prefix Any object name that has this prefix will be removed from the cache. + * @fn NIMemoryCache::removeAllObjectsWithPrefix: + */ + +/** + * Removes all objects from the cache, regardless of expiration dates. + * + * This will completely clear out the cache and all objects in the cache will be released. + * + * @fn NIMemoryCache::removeAllObjects + */ + +/** @name Accessing Objects in the Cache */ + +/** + * Retrieves an object from the cache. + * + * If the object has expired then the object will be removed from the cache and nil will be + * returned. + * + * @returns The object stored in the cache. The object is retained and autoreleased to + * ensure that it survives this run loop if you then remove it from the cache. + * @fn NIMemoryCache::objectWithName: + */ + +/** + * Returns a Boolean value that indicates whether an object with the given name is present + * in the cache. + * + * Does not update the access time of the object. + * + * If the object has expired then the object will be removed from the cache and NO will be + * returned. + * + * @returns YES if an object with the given name is present in the cache and has not expired, + * otherwise NO. + * @fn NIMemoryCache::containsObjectWithName: + */ + +/** + * Returns the date that the object with the given name was last accessed. + * + * Does not update the access time of the object. + * + * If the object has expired then the object will be removed from the cache and nil will be + * returned. + * + * @returns The last access date of the object if it exists and has not expired, nil + * otherwise. + * @fn NIMemoryCache::dateOfLastAccessWithName: + */ + +/** + * Retrieve the name of the object that was least recently used. + * + * This will not update the access time of the object. + * + * If the cache is empty, returns nil. + * + * @fn NIMemoryCache::nameOfLeastRecentlyUsedObject + */ + +/** + * Retrieve the key with the most fresh access. + * + * This will not update the access time of the object. + * + * If the cache is empty, returns nil. + * + * @fn NIMemoryCache::nameOfMostRecentlyUsedObject + */ + +/** @name Reducing Memory Usage Explicitly */ + +/** + * Removes all expired objects from the cache. + * + * Subclasses may add additional functionality to this implementation. + * Subclasses should call super in order to prune expired objects. + * + * This will be called when UIApplicationDidReceiveMemoryWarningNotification + * is posted. + * + * @fn NIMemoryCache::reduceMemoryUsage + */ + +/** @name Querying an In-Memory Cache */ + +/** + * Returns the number of objects currently in the cache. + * + * @returns The number of objects currently in the cache. + * @fn NIMemoryCache::count + */ + +/** + * @name Subclassing + * + * The following methods are provided to aid in subclassing and are not meant to be + * used externally. + */ + +/** + * An object is about to be stored in the cache. + * + * @param object The object that is about to be stored in the cache. + * @param name The cache name for the object. + * @param previousObject The object previously stored in the cache. This may be the + * same as object. + * @returns YES If object is allowed to be stored in the cache. + * @fn NIMemoryCache::shouldSetObject:withName:previousObject: + */ + +/** + * This method is deprecated. Please use shouldSetObject:withName:previousObject: instead. + * + * @fn NIMemoryCache::willSetObject:withName:previousObject: + */ + +/** + * An object has been stored in the cache. + * + * @param object The object that was stored in the cache. + * @param name The cache name for the object. + * @fn NIMemoryCache::didSetObject:withName: + */ + +/** + * An object is about to be removed from the cache. + * + * @param object The object about to removed from the cache. + * @param name The cache name for the object about to be removed. + * @fn NIMemoryCache::willRemoveObject:withName: + */ + +// NIImageMemoryCache + +/** @name Querying an In-Memory Image Cache */ + +/** + * Returns the total number of pixels being stored in the cache. + * + * @returns The total number of pixels being stored in the cache. + * @fn NIImageMemoryCache::numberOfPixels + */ + +/** @name Setting the Maximum Number of Pixels */ + +/** + * The maximum number of pixels this cache may ever store. + * + * Defaults to 0, which is special cased to represent an unlimited number of pixels. + * + * @returns The maximum number of pixels this cache may ever store. + * @fn NIImageMemoryCache::maxNumberOfPixels + */ + +/** + * The maximum number of pixels this cache may store after a call to reduceMemoryUsage. + * + * Defaults to 0, which is special cased to represent an unlimited number of pixels. + * + * @returns The maximum number of pixels this cache may store after a call + * to reduceMemoryUsage. + * @fn NIImageMemoryCache::maxNumberOfPixelsUnderStress + */ diff --git a/Pods/Nimbus/src/core/src/NIInMemoryCache.m b/Pods/Nimbus/src/core/src/NIInMemoryCache.m new file mode 100644 index 0000000..da895d0 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIInMemoryCache.m @@ -0,0 +1,453 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIInMemoryCache.h" + +#import "NIDebuggingTools.h" +#import "NIPreprocessorMacros.h" + +#import + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +@interface NIMemoryCache() +// Mapping from a name (usually a URL) to an internal object. +@property (nonatomic, strong) NSMutableDictionary* cacheMap; +// A linked list of least recently used cache objects. Most recently used is the tail. +@property (nonatomic, strong) NSMutableOrderedSet* lruCacheObjects; +@end + +/** + * @brief A single cache item's information. + * + * Used in expiration calculations and for storing the actual cache object. + */ +@interface NIMemoryCacheInfo : NSObject + +/** + * @brief The name used to store this object in the cache. + */ +@property (nonatomic, copy) NSString* name; + +/** + * @brief The object stored in the cache. + */ +@property (nonatomic, strong) id object; + +/** + * @brief The date after which the image is no longer valid and should be removed from the cache. + */ +@property (nonatomic, strong) NSDate* expirationDate; + +/** + * @brief The last time this image was accessed. + * + * This property is updated every time the image is fetched from or stored into the cache. It + * is used when the memory peak has been reached as a fast means of removing least-recently-used + * images. When the memory limit is reached, we sort the cache based on the last access times and + * then prune images until we're under the memory limit again. + */ +@property (nonatomic, strong) NSDate* lastAccessTime; + +/** + * @brief Determine whether this cache entry has past its expiration date. + * + * @returns YES if an expiration date has been specified and the expiration date has been passed. + * NO in all other cases. Notably if there is no expiration date then this object will + * never expire. + */ +- (BOOL)hasExpired; + +@end + +@implementation NIMemoryCache + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (id)init { + return [self initWithCapacity:0]; +} + +- (id)initWithCapacity:(NSUInteger)capacity { + if ((self = [super init])) { + _cacheMap = [[NSMutableDictionary alloc] initWithCapacity:capacity]; + _lruCacheObjects = [NSMutableOrderedSet orderedSet]; + + // Automatically reduce memory usage when we get a memory warning. + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(reduceMemoryUsage) + name:UIApplicationDidReceiveMemoryWarningNotification + object:nil]; + } + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat: + @"<%@" + @" lruObjects: %@" + @" cache map: %@" + @">", + [super description], + self.lruCacheObjects, + self.cacheMap]; +} + +#pragma mark - Internal + +- (void)updateAccessTimeForInfo:(NIMemoryCacheInfo *)info { + @synchronized(self) { + NIDASSERT(nil != info); + if (nil == info) { + return; // COV_NF_LINE + } + info.lastAccessTime = [NSDate date]; + + [self.lruCacheObjects removeObject:info]; + [self.lruCacheObjects addObject:info]; + } +} + +- (NIMemoryCacheInfo *)cacheInfoForName:(NSString *)name { + NIMemoryCacheInfo* info; + @synchronized(self) { + info = self.cacheMap[name]; + } + return info; +} + +- (void)setCacheInfo:(NIMemoryCacheInfo *)info forName:(NSString *)name { + @synchronized(self) { + NIDASSERT(nil != name); + if (nil == name) { + return; + } + + // Storing in the cache counts as an access of the object, so we update the access time. + [self updateAccessTimeForInfo:info]; + + id previousObject = [self cacheInfoForName:name].object; + if ([self shouldSetObject:info.object withName:name previousObject:previousObject]) { + self.cacheMap[name] = info; + [self didSetObject:info.object withName:name]; + } + } +} + +- (void)removeCacheInfoForName:(NSString *)name { + @synchronized(self) { + NIDASSERT(nil != name); + if (nil == name) { + return; + } + + NIMemoryCacheInfo* cacheInfo = [self cacheInfoForName:name]; + [self willRemoveObject:cacheInfo.object withName:name]; + + [self.lruCacheObjects removeObject:cacheInfo]; + [self.cacheMap removeObjectForKey:name]; + } +} + +#pragma mark - Subclassing + +// Deprecated method. +- (BOOL)willSetObject:(id)object withName:(NSString *)name previousObject:(id)previousObject { + return [self shouldSetObject:object withName:name previousObject:previousObject]; +} + +- (BOOL)shouldSetObject:(id)object withName:(NSString *)name previousObject:(id)previousObject { + // Allow anything to be stored. + return YES; +} + +- (void)didSetObject:(id)object withName:(NSString *)name { + // No-op +} + +- (void)willRemoveObject:(id)object withName:(NSString *)name { + // No-op +} + +#pragma mark - Public + +- (void)storeObject:(id)object withName:(NSString *)name { + @synchronized(self) { + [self storeObject:object withName:name expiresAfter:nil]; + } +} + +- (void)storeObject:(id)object withName:(NSString *)name expiresAfter:(NSDate *)expirationDate { + @synchronized(self) { + // Don't store nil objects in the cache. + if (nil == object) { + return; + } + + if (nil != expirationDate && [[NSDate date] timeIntervalSinceDate:expirationDate] >= 0) { + // The object being stored is already expired so remove the object from the cache altogether. + [self removeObjectWithName:name]; + + // We're done here. + return; + } + + NIMemoryCacheInfo* info = [self cacheInfoForName:name]; + + // Create a new cache entry. + if (nil == info) { + info = [[NIMemoryCacheInfo alloc] init]; + info.name = name; + } + + // Store the object in the cache item. + info.object = object; + + // Override any existing expiration date. + info.expirationDate = expirationDate; + + // Commit the changes to the cache. + [self setCacheInfo:info forName:name]; + } +} + +- (id)objectWithName:(NSString *)name { + @synchronized(self) { + NIMemoryCacheInfo* info = [self cacheInfoForName:name]; + + id object = nil; + + if (nil != info) { + if ([info hasExpired]) { + [self removeObjectWithName:name]; + + } else { + // Update the access time whenever we fetch an object from the cache. + [self updateAccessTimeForInfo:info]; + + object = info.object; + } + } + + return object; + } +} + +- (BOOL)containsObjectWithName:(NSString *)name { + @synchronized(self) { + NIMemoryCacheInfo* info = [self cacheInfoForName:name]; + + if ([info hasExpired]) { + [self removeObjectWithName:name]; + return NO; + } + + return (nil != info); + } +} + +- (NSDate *)dateOfLastAccessWithName:(NSString *)name { + @synchronized(self) { + NIMemoryCacheInfo* info = [self cacheInfoForName:name]; + + if ([info hasExpired]) { + [self removeObjectWithName:name]; + return nil; + } + + return [info lastAccessTime]; + } +} + +- (NSString *)nameOfLeastRecentlyUsedObject { + @synchronized(self) { + NIMemoryCacheInfo* info = [self.lruCacheObjects firstObject]; + + if ([info hasExpired]) { + [self removeObjectWithName:info.name]; + return nil; + } + + return info.name; + } +} + +- (NSString *)nameOfMostRecentlyUsedObject { + @synchronized(self) { + NIMemoryCacheInfo* info = [self.lruCacheObjects lastObject]; + + if ([info hasExpired]) { + [self removeObjectWithName:info.name]; + return nil; + } + + return info.name; + } +} + +- (void)removeObjectWithName:(NSString *)name { + @synchronized(self) { + [self removeCacheInfoForName:name]; + } +} + +- (void)removeAllObjectsWithPrefix:(NSString *)prefix { + @synchronized(self) { + // Assertions fire if you try to modify the object you're iterating over, so we make a copy. + for (NSString* name in [self.cacheMap copy]) { + if ([name hasPrefix:prefix]) { + [self removeObjectWithName:name]; + } + } + } +} + +- (void)removeAllObjects { + @synchronized(self) { + [self.cacheMap removeAllObjects]; + [self.lruCacheObjects removeAllObjects]; + } +} + +- (void)reduceMemoryUsage { + @synchronized(self) { + // Assertions fire if you try to modify the object you're iterating over, so we make a copy. + for (id name in [self.cacheMap copy]) { + NIMemoryCacheInfo* info = [self cacheInfoForName:name]; + + if ([info hasExpired]) { + [self removeCacheInfoForName:name]; + } + } + } +} + +- (NSUInteger)count { + @synchronized(self) { + return self.cacheMap.count; + } +} + +@end + +@implementation NIMemoryCacheInfo + +- (BOOL)hasExpired { + return (nil != _expirationDate + && [[NSDate date] timeIntervalSinceDate:_expirationDate] >= 0); +} + +- (NSString *)description { + return [NSString stringWithFormat: + @"<%@" + @" name: %@" + @" object: %@" + @" expiration date: %@" + @" last access time: %@" + @">", + [super description], + self.name, + self.object, + self.expirationDate, + self.lastAccessTime]; +} + +@end + +@interface NIImageMemoryCache() +@property (nonatomic, assign) unsigned long long numberOfPixels; +@end + +@implementation NIImageMemoryCache + +- (unsigned long long)numberOfPixelsUsedByImage:(UIImage *)image { + @synchronized(self) { + if (nil == image) { + return 0; + } + + return (unsigned long long)(image.size.width * image.size.height * [image scale] * [image scale]); + } +} + +- (void)removeAllObjects { + @synchronized(self) { + [super removeAllObjects]; + + self.numberOfPixels = 0; + } +} + +- (void)reduceMemoryUsage { + @synchronized(self) { + // Remove all expired images first. + [super reduceMemoryUsage]; + + if (self.maxNumberOfPixelsUnderStress > 0) { + // Remove the least recently used images by iterating over the linked list. + while (self.numberOfPixels > self.maxNumberOfPixelsUnderStress) { + NIMemoryCacheInfo* info = [self.lruCacheObjects firstObject]; + [self removeCacheInfoForName:info.name]; + } + } + } +} + +- (BOOL)shouldSetObject:(id)object withName:(NSString *)name previousObject:(id)previousObject { + @synchronized(self) { + NIDASSERT(nil == object || [object isKindOfClass:[UIImage class]]); + if (![object isKindOfClass:[UIImage class]]) { + return NO; + } + + _numberOfPixels -= [self numberOfPixelsUsedByImage:previousObject]; + _numberOfPixels += [self numberOfPixelsUsedByImage:object]; + + return YES; + } +} + +- (void)didSetObject:(id)object withName:(NSString *)name { + @synchronized(self) { + // Reduce the cache size after the object has been set in case the cache size is smaller + // than the object that's being added and we need to remove this object right away. + if (self.maxNumberOfPixels > 0) { + // Remove least recently used images until we satisfy our memory constraints. + while (self.numberOfPixels > self.maxNumberOfPixels) { + NIMemoryCacheInfo* info = [self.lruCacheObjects firstObject]; + [self removeCacheInfoForName:info.name]; + } + } + } +} + +- (void)willRemoveObject:(id)object withName:(NSString *)name { + @synchronized(self) { + NIDASSERT(nil == object || [object isKindOfClass:[UIImage class]]); + if (nil == object || ![object isKindOfClass:[UIImage class]]) { + return; + } + + self.numberOfPixels -= [self numberOfPixelsUsedByImage:object]; + } +} + +@end + diff --git a/Pods/Nimbus/src/core/src/NINetworkActivity.h b/Pods/Nimbus/src/core/src/NINetworkActivity.h new file mode 100644 index 0000000..aeacbf8 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NINetworkActivity.h @@ -0,0 +1,101 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Forked from Three20 July 2, 2011 - Copyright 2009-2011 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +#import "NIPreprocessorMacros.h" + +#if defined __cplusplus +extern "C" { +#endif + +/** + * For showing network activity in the device's status bar. + * + * @ingroup NimbusCore + * @defgroup Network-Activity Network Activity + * @{ + * + * Two methods for keeping track of all active network tasks. These methods are threadsafe + * and act as a simple counter. When the counter is positive, the network activity indicator + * is displayed. + */ + +/** + * Increment the number of active network tasks. + * + * The status bar activity indicator will be spinning while there are active tasks. + * + * This method is threadsafe. + */ +void NINetworkActivityTaskDidStart(void) NI_EXTENSION_UNAVAILABLE_IOS(""); + +/** + * Decrement the number of active network tasks. + * + * The status bar activity indicator will be spinning while there are active tasks. + * + * This method is threadsafe. + */ +void NINetworkActivityTaskDidFinish(void); + +/** + * @name For Debugging Only + * @{ + * + * Methods that will only do anything interesting if the DEBUG preprocessor macro is defined. + */ + +/** + * Enable network activity debugging. + * + * @attention This won't do anything unless the DEBUG preprocessor macro is defined. + * + * The Nimbus network activity methods will only work correctly if they are the only methods to + * touch networkActivityIndicatorVisible. If you are using another library that touches + * networkActivityIndicatorVisible then the network activity indicator might not accurately + * represent its state. + * + * When enabled, the networkActivityIndicatorVisible method on UIApplication will be swizzled + * with a debugging method that checks the global network task count and verifies that state + * is maintained correctly. If it is found that networkActivityIndicatorVisible is being accessed + * directly, then an assertion will be fired. + * + * If debugging was previously enabled, this does nothing. + */ +void NIEnableNetworkActivityDebugging(void); + +/** + * Disable network activity debugging. + * + * @attention This won't do anything unless the DEBUG preprocessor macro is defined. + * + * When disabled, the networkActivityIndicatorVisible will be restored if this was previously + * enabled, otherwise this method does nothing. + * + * If debugging wasn't previously enabled, this does nothing. + */ +void NIDisableNetworkActivityDebugging(void); + +/**@}*/// End of For Debugging Only + +#if defined __cplusplus +}; +#endif + +/**@}*/// End of Network Activity ///////////////////////////////////////////////////////////////// diff --git a/Pods/Nimbus/src/core/src/NINetworkActivity.m b/Pods/Nimbus/src/core/src/NINetworkActivity.m new file mode 100644 index 0000000..eaaf064 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NINetworkActivity.m @@ -0,0 +1,171 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NINetworkActivity.h" + +#import "NIDebuggingTools.h" +#import "NIPreprocessorMacros.h" + +#if defined(DEBUG) || defined(NI_DEBUG) +#import "NIRuntimeClassModifications.h" +#endif + +#import +#import + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +static int gNetworkTaskCount = 0; +static pthread_mutex_t gMutex = PTHREAD_MUTEX_INITIALIZER; +static const NSTimeInterval kDelayBeforeDisablingActivity = 0.1; +static NSTimer* gScheduledDelayTimer = nil; + +@interface NINetworkActivity : NSObject +@end + + +@implementation NINetworkActivity + + +// Called after a certain amount of time has passed since all network activity has stopped. +// By delaying the turnoff of the network activity we avoid "flickering" effects when network +// activity is starting and stopping rapidly. ++ (void)disableNetworkActivity NI_EXTENSION_UNAVAILABLE_IOS("") { + pthread_mutex_lock(&gMutex); + if (nil != gScheduledDelayTimer) { + [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; + gScheduledDelayTimer = nil; + } + pthread_mutex_unlock(&gMutex); +} + +@end + + +void NINetworkActivityTaskDidStart(void) { + pthread_mutex_lock(&gMutex); + + BOOL enableNetworkActivityIndicator = (0 == gNetworkTaskCount); + + ++gNetworkTaskCount; + [gScheduledDelayTimer invalidate]; + gScheduledDelayTimer = nil; + + if (enableNetworkActivityIndicator) { + [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; + } + + pthread_mutex_unlock(&gMutex); +} + +void NINetworkActivityTaskDidFinish(void) { + pthread_mutex_lock(&gMutex); + + --gNetworkTaskCount; + // If this asserts, you don't have enough stop requests to match your start requests. + NIDASSERT(gNetworkTaskCount >= 0); + gNetworkTaskCount = MAX(0, gNetworkTaskCount); + + if (gNetworkTaskCount == 0) { + [gScheduledDelayTimer invalidate]; + gScheduledDelayTimer = nil; + + // Ensure that the timer is scheduled on the main loop, otherwise it will die when the thread + // dies. + dispatch_async(dispatch_get_main_queue(), ^{ + pthread_mutex_lock(&gMutex); + gScheduledDelayTimer = [NSTimer scheduledTimerWithTimeInterval:kDelayBeforeDisablingActivity + target:[NINetworkActivity class] + selector:@selector(disableNetworkActivity) + userInfo:nil + repeats:NO]; + pthread_mutex_unlock(&gMutex); + }); + } + + pthread_mutex_unlock(&gMutex); +} + +#pragma mark - Network Activity Debugging + +#if defined(DEBUG) || defined(NI_DEBUG) + +static BOOL gNetworkActivityDebuggingEnabled = NO; + +void NISwizzleMethodsForNetworkActivityDebugging(void); + +@implementation UIApplication (NimbusNetworkActivityDebugging) + + +- (void)nimbusDebugSetNetworkActivityIndicatorVisible:(BOOL)visible { + // This method will only be used when swizzled, so this will actually call + // setNetworkActivityIndicatorVisible: + [self nimbusDebugSetNetworkActivityIndicatorVisible:visible]; + + // Sanity check that this method isn't being called directly when debugging isn't enabled. + NIDASSERT(gNetworkActivityDebuggingEnabled); + + // If either of the following assertions fail then you should look at the call stack to + // determine what code is erroneously calling setNetworkActivityIndicatorVisible: directly. + if (visible) { + // The only time we should be enabling the network activity indicator is when the task + // count is one. + NIDASSERT(1 == gNetworkTaskCount); + + } else { + // The only time we should be disabling the network activity indicator is when the task + // count is zero. + NIDASSERT(0 == gNetworkTaskCount); + } +} + +@end + + +void NISwizzleMethodsForNetworkActivityDebugging(void) { + NISwapInstanceMethods([UIApplication class], + @selector(setNetworkActivityIndicatorVisible:), + @selector(nimbusDebugSetNetworkActivityIndicatorVisible:)); +} + +void NIEnableNetworkActivityDebugging(void) { + if (!gNetworkActivityDebuggingEnabled) { + gNetworkActivityDebuggingEnabled = YES; + NISwizzleMethodsForNetworkActivityDebugging(); + } +} + +void NIDisableNetworkActivityDebugging(void) { + if (gNetworkActivityDebuggingEnabled) { + gNetworkActivityDebuggingEnabled = NO; + NISwizzleMethodsForNetworkActivityDebugging(); + } +} + +#else // #ifndef DEBUG + + +void NIEnableNetworkActivityDebugging(void) { + // No-op +} + +void NIDisableNetworkActivityDebugging(void) { + // No-op +} + +#endif // #if defined(DEBUG) || defined(NI_DEBUG) diff --git a/Pods/Nimbus/src/core/src/NINonEmptyCollectionTesting.h b/Pods/Nimbus/src/core/src/NINonEmptyCollectionTesting.h new file mode 100644 index 0000000..f1eb699 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NINonEmptyCollectionTesting.h @@ -0,0 +1,58 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Forked from Three20 June 10, 2011 - Copyright 2009-2011 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +#if defined __cplusplus +extern "C" { +#endif + +/** + * For testing whether a collection is of a certain type and is non-empty. + * + * @ingroup NimbusCore + * @defgroup Non-Empty-Collection-Testing Non-Empty Collection Testing + * @{ + * + * Simply calling -count on an object may not yield the expected results when enumerating it if + * certain assumptions are also made about the object's type. For example, if a JSON response + * returns a dictionary when you expected an array, casting the result to an NSArray and + * calling count will yield a positive value, but objectAtIndex: will crash the application. + * These methods provide a safer check for non-emptiness of collections. + */ + +/** + * Tests if an object is a non-nil array which is not empty. + */ +BOOL NIIsArrayWithObjects(id object); + +/** + * Tests if an object is a non-nil set which is not empty. + */ +BOOL NIIsSetWithObjects(id object); + +/** + * Tests if an object is a non-nil string which is not empty. + */ +BOOL NIIsStringWithAnyText(id object); + +#if defined __cplusplus +}; +#endif + +/**@}*/// End of Non-Empty Collection Testing ///////////////////////////////////////////////////// diff --git a/Pods/Nimbus/src/core/src/NINonEmptyCollectionTesting.m b/Pods/Nimbus/src/core/src/NINonEmptyCollectionTesting.m new file mode 100644 index 0000000..6c1b9c9 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NINonEmptyCollectionTesting.m @@ -0,0 +1,35 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Forked from Three20 June 10, 2011 - Copyright 2009-2011 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NINonEmptyCollectionTesting.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +BOOL NIIsArrayWithObjects(id object) { + return [object isKindOfClass:[NSArray class]] && [(NSArray*)object count] > 0; +} + +BOOL NIIsSetWithObjects(id object) { + return [object isKindOfClass:[NSSet class]] && [(NSSet*)object count] > 0; +} + +BOOL NIIsStringWithAnyText(id object) { + return [object isKindOfClass:[NSString class]] && [(NSString*)object length] > 0; +} diff --git a/Pods/Nimbus/src/core/src/NINonRetainingCollections.h b/Pods/Nimbus/src/core/src/NINonRetainingCollections.h new file mode 100644 index 0000000..5c39e4d --- /dev/null +++ b/Pods/Nimbus/src/core/src/NINonRetainingCollections.h @@ -0,0 +1,65 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Forked from Three20 June 10, 2011 - Copyright 2009-2011 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +#if defined __cplusplus +extern "C" { +#endif + +/** + * For collections that don't retain their objects. + * + * @ingroup NimbusCore + * @defgroup Non-Retaining-Collections Non-Retaining Collections + * @{ + * + * Non-retaining collections have historically been used when we needed more than one delegate + * in an object. However, NSNotificationCenter is a much better solution for n > 1 delegates. + * Using a non-retaining collection is dangerous, so if you must use one, use it with extreme care. + * The danger primarily lies in the fact that by all appearances the collection should still + * operate like a regular collection, so this might lead to a lot of developer error if the + * developer assumes that the collection does, in fact, retain the object. + */ + +/** + * Creates a mutable array which does not retain references to the objects it contains. + * + * Typically used with arrays of delegates. + */ +NSMutableArray* NICreateNonRetainingMutableArray(void); + +/** + * Creates a mutable dictionary which does not retain references to the values it contains. + * + * Typically used with dictionaries of delegates. + */ +NSMutableDictionary* NICreateNonRetainingMutableDictionary(void); + +/** + * Creates a mutable set which does not retain references to the values it contains. + * + * Typically used with sets of delegates. + */ +NSMutableSet* NICreateNonRetainingMutableSet(void); + +#if defined __cplusplus +}; +#endif + +/**@}*/// End of Non-Retaining Collections //////////////////////////////////////////////////////// diff --git a/Pods/Nimbus/src/core/src/NINonRetainingCollections.m b/Pods/Nimbus/src/core/src/NINonRetainingCollections.m new file mode 100644 index 0000000..1fa3612 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NINonRetainingCollections.m @@ -0,0 +1,36 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Forked from Three20 June 9, 2011 - Copyright 2009-2011 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NINonRetainingCollections.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +NSMutableArray* NICreateNonRetainingMutableArray(void) { + return (__bridge_transfer NSMutableArray *)CFArrayCreateMutable(nil, 0, nil); +} + +NSMutableDictionary* NICreateNonRetainingMutableDictionary(void) { + return (__bridge_transfer NSMutableDictionary *)CFDictionaryCreateMutable(nil, 0, nil, nil); +} + +NSMutableSet* NICreateNonRetainingMutableSet(void) { + return (__bridge_transfer NSMutableSet *)CFSetCreateMutable(nil, 0, nil); +} + diff --git a/Pods/Nimbus/src/core/src/NIOperations+Subclassing.h b/Pods/Nimbus/src/core/src/NIOperations+Subclassing.h new file mode 100644 index 0000000..b08592a --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIOperations+Subclassing.h @@ -0,0 +1,20 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#import "NIOperations.h" + +@interface NIOperation() +@property (strong) NSError* lastError; +@end diff --git a/Pods/Nimbus/src/core/src/NIOperations.h b/Pods/Nimbus/src/core/src/NIOperations.h new file mode 100644 index 0000000..2841dae --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIOperations.h @@ -0,0 +1,209 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +#import "NIPreprocessorMacros.h" /* for weak */ + +@class NIOperation; + +typedef void (^NIOperationBlock)(NIOperation* operation); +typedef void (^NIOperationDidFailBlock)(NIOperation* operation, NSError* error); + +/** + * For writing code that runs concurrently. + * + * @ingroup NimbusCore + * @defgroup Operations Operations + * + * This collection of NSOperation implementations is meant to provide a set of common + * operations that might be used in an application to offload complex processing to a separate + * thread. + */ + +@protocol NIOperationDelegate; + +/** + * A base implementation of an NSOperation that supports traditional delegation and blocks. + * + *

Subclassing

+ * + * A subclass should call the operationDid* methods to notify the delegate on the main thread + * of changes in the operation's state. Calling these methods will notify the delegate and the + * blocks if provided. + * + * @ingroup Operations + */ +@interface NIOperation : NSOperation + +@property (weak) id delegate; +@property (readonly, strong) NSError* lastError; +@property (assign) NSInteger tag; + +@property (copy) NIOperationBlock didStartBlock; +@property (copy) NIOperationBlock didFinishBlock; +@property (copy) NIOperationDidFailBlock didFailWithErrorBlock; +@property (copy) NIOperationBlock willFinishBlock; + +- (void)didStart; +- (void)didFinish; +- (void)didFailWithError:(NSError *)error; +- (void)willFinish; + +@end + +/** + * The delegate protocol for an NIOperation. + * + * @ingroup Operations + */ +@protocol NIOperationDelegate +@optional + +/** @name [NIOperationDelegate] State Changes */ + +/** The operation has started executing. */ +- (void)nimbusOperationDidStart:(NIOperation *)operation; + +/** + * The operation is about to complete successfully. + * + * This will not be called if the operation fails. + * + * This will be called from within the operation's runloop and must be thread safe. + */ +- (void)nimbusOperationWillFinish:(NIOperation *)operation; + +/** + * The operation has completed successfully. + * + * This will not be called if the operation fails. + */ +- (void)nimbusOperationDidFinish:(NIOperation *)operation; + +/** + * The operation failed in some way and has completed. + * + * operationDidFinish: will not be called. + */ +- (void)nimbusOperationDidFail:(NIOperation *)operation withError:(NSError *)error; + +@end + + +// NIOperation + +/** @name Delegation */ + +/** + * The delegate through which changes are notified for this operation. + * + * All delegate methods are performed on the main thread. + * + * @fn NIOperation::delegate + */ + + +/** @name Post-Operation Properties */ + +/** + * The error last passed to the didFailWithError notification. + * + * @fn NIOperation::lastError + */ + + +/** @name Identification */ + +/** + * A simple tagging mechanism for identifying operations. + * + * @fn NIOperation::tag + */ + + +/** @name Blocks */ + +/** + * The operation has started executing. + * + * Performed on the main thread. + * + * @fn NIOperation::didStartBlock + */ + +/** + * The operation has completed successfully. + * + * This will not be called if the operation fails. + * + * Performed on the main thread. + * + * @fn NIOperation::didFinishBlock + */ + +/** + * The operation failed in some way and has completed. + * + * didFinishBlock will not be executed. + * + * Performed on the main thread. + * + * @fn NIOperation::didFailWithErrorBlock + */ + +/** + * The operation is about to complete successfully. + * + * This will not be called if the operation fails. + * + * Performed in the operation's thread. + * + * @fn NIOperation::willFinishBlock + */ + + +/** + * @name Subclassing + * + * The following methods are provided to aid in subclassing and are not meant to be + * used externally. + */ + +/** + * On the main thread, notify the delegate that the operation has begun. + * + * @fn NIOperation::didStart + */ + +/** + * On the main thread, notify the delegate that the operation has finished. + * + * @fn NIOperation::didFinish + */ + +/** + * On the main thread, notify the delegate that the operation has failed. + * + * @fn NIOperation::didFailWithError: + */ + +/** + * In the operation's thread, notify the delegate that the operation will finish successfully. + * + * @fn NIOperation::willFinish + */ diff --git a/Pods/Nimbus/src/core/src/NIOperations.m b/Pods/Nimbus/src/core/src/NIOperations.m new file mode 100644 index 0000000..e865417 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIOperations.m @@ -0,0 +1,111 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIOperations.h" + +#import "NIDebuggingTools.h" +#import "NIPreprocessorMacros.h" +#import "NIOperations+Subclassing.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +@implementation NIOperation + +- (void)dealloc { + // For an unknown reason these block objects are not released when the NIOperation is deallocated + // with ARC enabled. + _didStartBlock = nil; + _didFinishBlock = nil; + _didFailWithErrorBlock = nil; + _willFinishBlock = nil; +} + +#pragma mark - Initiate delegate notification from the NSOperation + +- (void)didStart { + [self performSelectorOnMainThread:@selector(onMainThreadOperationDidStart) + withObject:nil + waitUntilDone:[NSThread isMainThread]]; +} + +- (void)didFinish { + [self performSelectorOnMainThread:@selector(onMainThreadOperationDidFinish) + withObject:nil + waitUntilDone:[NSThread isMainThread]]; +} + +- (void)didFailWithError:(NSError *)error { + self.lastError = error; + + [self performSelectorOnMainThread:@selector(onMainThreadOperationDidFailWithError:) + withObject:error + waitUntilDone:[NSThread isMainThread]]; +} + +- (void)willFinish { + if ([self.delegate respondsToSelector:@selector(nimbusOperationWillFinish:)]) { + [self.delegate nimbusOperationWillFinish:self]; + } + + if (nil != self.willFinishBlock) { + self.willFinishBlock(self); + } +} + +#pragma mark - Main Thread + +- (void)onMainThreadOperationDidStart { + // This method should only be called on the main thread. + NIDASSERT([NSThread isMainThread]); + + if ([self.delegate respondsToSelector:@selector(nimbusOperationDidStart:)]) { + [self.delegate nimbusOperationDidStart:self]; + } + + if (nil != self.didStartBlock) { + self.didStartBlock(self); + } +} + +- (void)onMainThreadOperationDidFinish { + // This method should only be called on the main thread. + NIDASSERT([NSThread isMainThread]); + + if ([self.delegate respondsToSelector:@selector(nimbusOperationDidFinish:)]) { + [self.delegate nimbusOperationDidFinish:self]; + } + + if (nil != self.didFinishBlock) { + self.didFinishBlock(self); + } +} + +- (void)onMainThreadOperationDidFailWithError:(NSError *)error { + // This method should only be called on the main thread. + NIDASSERT([NSThread isMainThread]); + + if ([self.delegate respondsToSelector:@selector(nimbusOperationDidFail:withError:)]) { + [self.delegate nimbusOperationDidFail:self withError:error]; + } + + if (nil != self.didFailWithErrorBlock) { + self.didFailWithErrorBlock(self, error); + } +} + +@end diff --git a/Pods/Nimbus/src/core/src/NIPaths.h b/Pods/Nimbus/src/core/src/NIPaths.h new file mode 100644 index 0000000..64e41e1 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIPaths.h @@ -0,0 +1,69 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Forked from Three20 June 10, 2011 - Copyright 2009-2011 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +#if defined __cplusplus +extern "C" { +#endif + +/** + * For creating standard system paths. + * + * @ingroup NimbusCore + * @defgroup Paths Paths + * @{ + */ + +/** + * Create a path with the given bundle and the relative path appended. + * + * @param bundle The bundle to append relativePath to. If nil, [NSBundle mainBundle] + * will be used. + * @param relativePath The relative path to append to the bundle's path. + * + * @returns The bundle path concatenated with the given relative path. + */ +NSString* NIPathForBundleResource(NSBundle* bundle, NSString* relativePath); + +/** + * Create a path with the documents directory and the relative path appended. + * + * @returns The documents path concatenated with the given relative path. + */ +NSString* NIPathForDocumentsResource(NSString* relativePath); + +/** + * Create a path with the Library directory and the relative path appended. + * + * @returns The Library path concatenated with the given relative path. + */ +NSString* NIPathForLibraryResource(NSString* relativePath); + +/** + * Create a path with the caches directory and the relative path appended. + * + * @returns The caches path concatenated with the given relative path. + */ +NSString* NIPathForCachesResource(NSString* relativePath); + +#if defined __cplusplus +}; +#endif + +/**@}*/// End of Paths //////////////////////////////////////////////////////////////////////////// diff --git a/Pods/Nimbus/src/core/src/NIPaths.m b/Pods/Nimbus/src/core/src/NIPaths.m new file mode 100644 index 0000000..32dd2af --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIPaths.m @@ -0,0 +1,61 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Forked from Three20 June 10, 2011 - Copyright 2009-2011 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIPaths.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +NSString* NIPathForBundleResource(NSBundle* bundle, NSString* relativePath) { + NSString* resourcePath = [(nil == bundle ? [NSBundle mainBundle] : bundle) resourcePath]; + return [resourcePath stringByAppendingPathComponent:relativePath]; +} + +NSString* NIPathForDocumentsResource(NSString* relativePath) { + static NSString* documentsPath = nil; + if (nil == documentsPath) { + NSArray* dirs = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, + NSUserDomainMask, + YES); + documentsPath = [dirs objectAtIndex:0]; + } + return [documentsPath stringByAppendingPathComponent:relativePath]; +} + +NSString* NIPathForLibraryResource(NSString* relativePath) { + static NSString* libraryPath = nil; + if (nil == libraryPath) { + NSArray* dirs = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, + NSUserDomainMask, + YES); + libraryPath = [dirs objectAtIndex:0]; + } + return [libraryPath stringByAppendingPathComponent:relativePath]; +} + +NSString* NIPathForCachesResource(NSString* relativePath) { + static NSString* cachesPath = nil; + if (nil == cachesPath) { + NSArray* dirs = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, + NSUserDomainMask, + YES); + cachesPath = [dirs objectAtIndex:0]; + } + return [cachesPath stringByAppendingPathComponent:relativePath]; +} diff --git a/Pods/Nimbus/src/core/src/NIPreprocessorMacros.h b/Pods/Nimbus/src/core/src/NIPreprocessorMacros.h new file mode 100644 index 0000000..8f33e13 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIPreprocessorMacros.h @@ -0,0 +1,141 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Forked from Three20 June 10, 2011 - Copyright 2009-2011 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + + +#pragma mark - Preprocessor Macros + +/** + * Preprocessor macros are added to Nimbus with care. Macros hide functionality and are difficult + * to debug, so most macros found in Nimbus are one-liners or compiler utilities. + * + *

Creating Byte- and Hex-based Colors

+ * + * Nimbus provides the RGBCOLOR and RGBACOLOR macros for easily creating UIColor objects + * with byte and hex values. + * + *

Examples

+ * +@code +UIColor* color = RGBCOLOR(255, 128, 64); // Fully opaque orange +UIColor* color = RGBACOLOR(255, 128, 64, 0.5); // Orange with 50% transparency +UIColor* color = RGBCOLOR(0xFF, 0x7A, 0x64); // Hexadecimal color +@endcode + * + *

Why it exists

+ * + * There is no easy way to create UIColor objects using 0 - 255 range values or hexadecimal. This + * leads to code like this being written: + * +@code +UIColor* color = [UIColor colorWithRed:128.f/255.0f green:64.f/255.0f blue:32.f/255.0f alpha:1] +@endcode + * + *

Avoid requiring the -all_load and -force_load flags

+ * + * Categories can introduce the need for the -all_load and -force_load because of the fact that + * the application will not load these categories on startup without them. This is due to the way + * Xcode deals with .m files that only contain categories: it doesn't load them without the + * -all_load or -force_load flag specified. + * + * There is, however, a way to force Xcode into loading the category .m file. If you provide an + * empty class implementation in the .m file then your app will pick up the category + * implementation. + * + * Example in plain UIKit: + * +@code +@interface BogusClass +@end +@implementation BogusClass +@end + +@implementation UIViewController (MyCustomCategory) +... +@end +@endcode + * + * NI_FIX_CATEGORY_BUG is a Nimbus macro that you include in your category `.m` file to save you + * the trouble of having to write a bogus class for every category. Just be sure that the name you + * provide to the macro is unique across your project or you will encounter duplicate symbol errors + * when linking. + * +@code +NI_FIX_CATEGORY_BUG(UIViewController_MyCustomCategory); + +@implementation UIViewController (MyCustomCategory) +... +@end +@endcode + * + * @ingroup NimbusCore + * @defgroup Preprocessor-Macros Preprocessor Macros + * @{ + */ + +/** + * Mark a method or property as deprecated to the compiler. + * + * Any use of a deprecated method or property will flag a warning when compiling. + * + * Borrowed from Apple's AvailabiltyInternal.h header. + * + * @htmlonly + *
+ *   __AVAILABILITY_INTERNAL_DEPRECATED         __attribute__((deprecated))
+ * 
+ * @endhtmlonly + */ +#define __NI_DEPRECATED_METHOD __attribute__((deprecated)) + +/** + * Mark APIs as unavailable in app extensions. + * + * Use of unavailable methods, classes, or functions produces a compile error when built as part + * of an app extension target. If the method, class or function using the unavailable API has also + * been marked as unavailable in app extensions, the error will be suppressed. + */ +#ifdef NS_EXTENSION_UNAVAILABLE_IOS +#define NI_EXTENSION_UNAVAILABLE_IOS(msg) NS_EXTENSION_UNAVAILABLE_IOS(msg) +#else +#define NI_EXTENSION_UNAVAILABLE_IOS(msg) +#endif + +/** + * Force a category to be loaded when an app starts up. + * + * Add this macro before each category implementation, so we don't have to use + * -all_load or -force_load to load object files from static libraries that only contain + * categories and no classes. + * See http://developer.apple.com/library/mac/#qa/qa2006/qa1490.html for more info. + */ +#define NI_FIX_CATEGORY_BUG(name) @interface NI_FIX_CATEGORY_BUG_##name : NSObject @end \ +@implementation NI_FIX_CATEGORY_BUG_##name @end + +/** + * Creates an opaque UIColor object from a byte-value color definition. + */ +#define RGBCOLOR(r,g,b) [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:1] + +/** + * Creates a UIColor object from a byte-value color definition and alpha transparency. + */ +#define RGBACOLOR(r,g,b,a) [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:(a)] + +/**@}*/// End of Preprocessor Macros ////////////////////////////////////////////////////////////// diff --git a/Pods/Nimbus/src/core/src/NIRuntimeClassModifications.h b/Pods/Nimbus/src/core/src/NIRuntimeClassModifications.h new file mode 100644 index 0000000..e3d76ae --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIRuntimeClassModifications.h @@ -0,0 +1,74 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Forked from Three20 June 10, 2011 - Copyright 2009-2011 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +#if defined __cplusplus +extern "C" { +#endif + +/** + * For modifying class implementations at runtime. + * + * @ingroup NimbusCore + * @defgroup Runtime-Class-Modifications Runtime Class Modifications + * @{ + * + * @attention Please use caution when modifying class implementations at runtime. + * Apple is prone to rejecting apps for gratuitous use of method swapping. + * In particular, avoid swapping any NSObject methods such as dealloc, init, + * and retain/release on UIKit classes. + * + * See example: @link ExampleRuntimeDebugging.m Runtime Debugging with Method Swizzling@endlink + */ + +/** + * Swap two class instance method implementations. + * + * Use this method when you would like to replace an existing method implementation in a class + * with your own implementation at runtime. In practice this is often used to replace the + * implementations of UIKit classes where subclassing isn't an adequate solution. + * + * This will only work for methods declared with a -. + * + * After calling this method, any calls to originalSel will actually call newSel and vice versa. + * + * Uses method_exchangeImplementations to accomplish this. + */ +void NISwapInstanceMethods(Class cls, SEL originalSel, SEL newSel); + +/** + * Swap two class method implementations. + * + * Use this method when you would like to replace an existing method implementation in a class + * with your own implementation at runtime. In practice this is often used to replace the + * implementations of UIKit classes where subclassing isn't an adequate solution. + * + * This will only work for methods declared with a +. + * + * After calling this method, any calls to originalSel will actually call newSel and vice versa. + * + * Uses method_exchangeImplementations to accomplish this. + */ +void NISwapClassMethods(Class cls, SEL originalSel, SEL newSel); + +#if defined __cplusplus +}; +#endif + +/**@}*/// End of Runtime Class Modifications ////////////////////////////////////////////////////// diff --git a/Pods/Nimbus/src/core/src/NIRuntimeClassModifications.m b/Pods/Nimbus/src/core/src/NIRuntimeClassModifications.m new file mode 100644 index 0000000..41e32bb --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIRuntimeClassModifications.m @@ -0,0 +1,37 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Forked from Three20 June 10, 2011 - Copyright 2009-2011 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIRuntimeClassModifications.h" + +#import + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +void NISwapInstanceMethods(Class cls, SEL originalSel, SEL newSel) { + Method originalMethod = class_getInstanceMethod(cls, originalSel); + Method newMethod = class_getInstanceMethod(cls, newSel); + method_exchangeImplementations(originalMethod, newMethod); +} + +void NISwapClassMethods(Class cls, SEL originalSel, SEL newSel) { + Method originalMethod = class_getClassMethod(cls, originalSel); + Method newMethod = class_getClassMethod(cls, newSel); + method_exchangeImplementations(originalMethod, newMethod); +} diff --git a/Pods/Nimbus/src/core/src/NISDKAvailability.h b/Pods/Nimbus/src/core/src/NISDKAvailability.h new file mode 100644 index 0000000..a0ce759 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NISDKAvailability.h @@ -0,0 +1,257 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Forked from Three20 June 10, 2011 - Copyright 2009-2011 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +#import "NIPreprocessorMacros.h" + +/** + * For checking SDK feature availibility. + * + * @ingroup NimbusCore + * @defgroup SDK-Availability SDK Availability + * @{ + * + * NIIOS macros are defined in parallel to their __IPHONE_ counterparts as a consistently-defined + * means of checking __IPHONE_OS_VERSION_MAX_ALLOWED. + * + * For example: + * + * @htmlonly + *
+ *     #if __IPHONE_OS_VERSION_MAX_ALLOWED >= NIIOS_3_2
+ *       // This code will only compile on versions >= iOS 3.2
+ *     #endif
+ * 
+ * @endhtmlonly + */ + +/** + * Released on July 11, 2008 + */ +#define NIIOS_2_0 20000 + +/** + * Released on September 9, 2008 + */ +#define NIIOS_2_1 20100 + +/** + * Released on November 21, 2008 + */ +#define NIIOS_2_2 20200 + +/** + * Released on June 17, 2009 + */ +#define NIIOS_3_0 30000 + +/** + * Released on September 9, 2009 + */ +#define NIIOS_3_1 30100 + +/** + * Released on April 3, 2010 + */ +#define NIIOS_3_2 30200 + +/** + * Released on June 21, 2010 + */ +#define NIIOS_4_0 40000 + +/** + * Released on September 8, 2010 + */ +#define NIIOS_4_1 40100 + +/** + * Released on November 22, 2010 + */ +#define NIIOS_4_2 40200 + +/** + * Released on March 9, 2011 + */ +#define NIIOS_4_3 40300 + +/** + * Released on October 12, 2011. + */ +#define NIIOS_5_0 50000 + +/** + * Released on March 7, 2012. + */ +#define NIIOS_5_1 50100 + +/** + * Released on September 19, 2012. + */ +#define NIIOS_6_0 60000 + +/** + * Released on January 28, 2013. + */ +#define NIIOS_6_1 60100 + +/** + * Released on September 18, 2013 + */ +#define NIIOS_7_0 70000 + +#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_2_0 +#define kCFCoreFoundationVersionNumber_iPhoneOS_2_0 478.23 +#endif + +#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_2_1 +#define kCFCoreFoundationVersionNumber_iPhoneOS_2_1 478.26 +#endif + +#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_2_2 +#define kCFCoreFoundationVersionNumber_iPhoneOS_2_2 478.29 +#endif + +#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_0 +#define kCFCoreFoundationVersionNumber_iPhoneOS_3_0 478.47 +#endif + +#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_1 +#define kCFCoreFoundationVersionNumber_iPhoneOS_3_1 478.52 +#endif + +#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_2 +#define kCFCoreFoundationVersionNumber_iPhoneOS_3_2 478.61 +#endif + +#ifndef kCFCoreFoundationVersionNumber_iOS_4_0 +#define kCFCoreFoundationVersionNumber_iOS_4_0 550.32 +#endif + +#ifndef kCFCoreFoundationVersionNumber_iOS_4_1 +#define kCFCoreFoundationVersionNumber_iOS_4_1 550.38 +#endif + +#ifndef kCFCoreFoundationVersionNumber_iOS_4_2 +#define kCFCoreFoundationVersionNumber_iOS_4_2 550.52 +#endif + +#ifndef kCFCoreFoundationVersionNumber_iOS_4_3 +#define kCFCoreFoundationVersionNumber_iOS_4_3 550.52 +#endif + +#ifndef kCFCoreFoundationVersionNumber_iOS_5_0 +#define kCFCoreFoundationVersionNumber_iOS_5_0 675.00 +#endif + +#ifndef kCFCoreFoundationVersionNumber_iOS_5_1 +#define kCFCoreFoundationVersionNumber_iOS_5_1 690.10 +#endif + +#ifndef kCFCoreFoundationVersionNumber_iOS_6_0 +#define kCFCoreFoundationVersionNumber_iOS_6_0 793.00 +#endif + +#ifndef kCFCoreFoundationVersionNumber_iOS_6_1 +#define kCFCoreFoundationVersionNumber_iOS_6_1 793.00 +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +/** + * Checks whether the device the app is currently running on is an iPad or not. + * + * @returns YES if the device is an iPad. + */ +BOOL NIIsPad(void); + +/** + * Checks whether the device the app is currently running on is an + * iPhone/iPod touch or not. + * + * @returns YES if the device is an iPhone or iPod touch. + */ +BOOL NIIsPhone(void); + +/** + * Checks whether the device supports tint colors on all UIViews. + * + * @returns YES if all UIView instances on the device respond to tintColor. + */ +BOOL NIIsTintColorGloballySupported(void); + +/** + * Checks whether the device's OS version is at least the given version number. + * + * Useful for runtime checks of the device's version number. + * + * @param versionNumber Any value of kCFCoreFoundationVersionNumber. + * + * @attention Apple recommends using respondsToSelector where possible to check for + * feature support. Use this method as a last resort. + */ +BOOL NIDeviceOSVersionIsAtLeast(double versionNumber); + +/** + * Fetch the screen's scale. + */ +CGFloat NIScreenScale(void); + +/** + * Returns YES if the screen is a retina display, NO otherwise. + */ +BOOL NIIsRetina(void); + +/** + * This method is now deprecated. Use [UIPopoverController class] instead. + * + * MAINTENANCE: Remove by Feb 28, 2014. + */ +Class NIUIPopoverControllerClass(void) __NI_DEPRECATED_METHOD; + +/** + * This method is now deprecated. Use [UITapGestureRecognizer class] instead. + * + * MAINTENANCE: Remove by Feb 28, 2014. + */ +Class NIUITapGestureRecognizerClass(void) __NI_DEPRECATED_METHOD; + +#if defined(__cplusplus) +} // extern "C" +#endif + +#pragma mark Building with Old SDKs + +// Define methods that were introduced in iOS 7.0. +#if __IPHONE_OS_VERSION_MAX_ALLOWED < NIIOS_7_0 + +@interface UIViewController (Nimbus7SDKAvailability) + +@property (nonatomic, assign) UIRectEdge edgesForExtendedLayout; +- (void)setNeedsStatusBarAppearanceUpdate; + +@end + +#endif + + +/**@}*/// End of SDK Availability ///////////////////////////////////////////////////////////////// diff --git a/Pods/Nimbus/src/core/src/NISDKAvailability.m b/Pods/Nimbus/src/core/src/NISDKAvailability.m new file mode 100644 index 0000000..19703c7 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NISDKAvailability.m @@ -0,0 +1,72 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Forked from Three20 June 10, 2011 - Copyright 2009-2011 Facebook +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NimbusCore.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +#if __IPHONE_OS_VERSION_MAX_ALLOWED < NIIOS_6_0 +const UIImageResizingMode UIImageResizingModeStretch = -1; +#endif + +BOOL NIIsPad(void) { + static NSInteger isPad = -1; + if (isPad < 0) { + isPad = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) ? 1 : 0; + } + return isPad > 0; +} + +BOOL NIIsPhone(void) { + static NSInteger isPhone = -1; + if (isPhone < 0) { + isPhone = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) ? 1 : 0; + } + return isPhone > 0; +} + +BOOL NIIsTintColorGloballySupported(void) { + static NSInteger isTintColorGloballySupported = -1; + if (isTintColorGloballySupported < 0) { + UIView* view = [[UIView alloc] init]; + isTintColorGloballySupported = [view respondsToSelector:@selector(tintColor)]; + } + return isTintColorGloballySupported > 0; +} + +BOOL NIDeviceOSVersionIsAtLeast(double versionNumber) { + return kCFCoreFoundationVersionNumber >= versionNumber; +} + +CGFloat NIScreenScale(void) { + return [[UIScreen mainScreen] scale]; +} + +BOOL NIIsRetina(void) { + return NIScreenScale() > 1.f; +} + +Class NIUIPopoverControllerClass(void) { + return [UIPopoverController class]; +} + +Class NIUITapGestureRecognizerClass(void) { + return [UITapGestureRecognizer class]; +} diff --git a/Pods/Nimbus/src/core/src/NISnapshotRotation.h b/Pods/Nimbus/src/core/src/NISnapshotRotation.h new file mode 100644 index 0000000..219eedb --- /dev/null +++ b/Pods/Nimbus/src/core/src/NISnapshotRotation.h @@ -0,0 +1,224 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +/** + * An object designed to easily implement snapshot rotation. + * + * Snapshot rotation involves taking two screenshots of a UIView: the "before" and the "after" + * state of the rotation. These two images are then cross-faded during the rotation, creating an + * animation that minimizes visual artifacts that would otherwise be noticed when rotation occurs. + * + * This feature will only function on iOS 6.0 and higher. On older iOS versions the view will rotate + * just as it always has. + * + * This functionality has been adopted from WWDC 2012 session 240 "Polishing Your Interface + * Rotations". + * + * @ingroup NimbusCore + * @defgroup Snapshot-Rotation Snapshot Rotation + * @{ + */ + +@protocol NISnapshotRotationDelegate; + +/** + * The NISnapshotRotation class provides support for implementing snapshot-based rotations on views. + * + * You must call this object's rotation methods from your controller in order for the rotation + * object to implement the rotation animations correctly. + */ +@interface NISnapshotRotation : NSObject + +// Designated initializer. +- (id)initWithDelegate:(id)delegate; + +@property (nonatomic, weak) id delegate; + +@property (nonatomic, readonly, assign) CGRect frameBeforeRotation; +@property (nonatomic, readonly, assign) CGRect frameAfterRotation; + +- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration; +- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration; +- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation; + +@end + +/** + * The NITableViewSnapshotRotation class implements the fixedInsetsForSnapshotRotation: delegate + * method and forwards all other delegate methods along. + * + * If you are rotating a UITableView you can instantiate a NITableViewSnapshotRotation object and + * use it just like you would a snapshot rotation object. The NITableViewSnapshotRotation class + * intercepts the fixedInsetsForSnapshotRotation: method and returns insets that map to the + * dimensions of the content view for the first visible cell in the table view. + * + * The assigned delegate only needs to implement containerViewForSnapshotRotation: and + * rotatingViewForSnapshotRotation:. + */ +@interface NITableViewSnapshotRotation : NISnapshotRotation +@end + +/** + * The methods declared by the NISnapshotRotation protocol allow the adopting delegate to respond to + * messages from the NISnapshotRotation class and thus implement snapshot rotations. + */ +@protocol NISnapshotRotationDelegate +@required + +/** @name Accessing Rotation Views */ + +/** + * Tells the delegate to return the container view of the rotating view. + * + * This is often the controller's self.view. This view must not be the same as the rotatingView and + * rotatingView must be in the subview tree of containerView. + * + * @sa NISnapshotRotation::rotatingViewForSnapshotRotation: + */ +- (UIView *)containerViewForSnapshotRotation:(NISnapshotRotation *)snapshotRotation; + +/** + * Tells the delegate to return the rotating view. + * + * The rotating view is the view that will be snapshotted during the rotation. + * + * This view must not be the same as the containerView and must be in the subview tree of + * containerView. + * + * @sa NISnapshotRotation::containerViewForSnapshotRotation: + */ +- (UIView *)rotatingViewForSnapshotRotation:(NISnapshotRotation *)snapshotRotation; + +@optional + +/** @name Configuring Fixed Insets */ + +/** + * Asks the delegate to return the insets of the rotating view that should be fixed during rotation. + * + * This method will only be called on iOS 6.0 and higher. + * + * The returned insets will denote which parts of the snapshotted images will not stretch during + * the rotation animation. + */ +- (UIEdgeInsets)fixedInsetsForSnapshotRotation:(NISnapshotRotation *)snapshotRotation; + +@end + +#if defined __cplusplus +extern "C" { +#endif + +/** + * Returns an opaque UIImage snapshot of the given view. + * + * This method takes into account the offset of scrollable views and captures whatever is currently + * in the frame of the view. + * + * @param view A snapshot will be taken of this view. + * @returns A UIImage with the snapshot of @c view. + */ +UIImage* NISnapshotOfView(UIView* view); + +/** + * Returns a UIImageView with an image snapshot of the given view. + * + * The frame of the returned view is set to match the frame of @c view. + * + * @param view A snapshot will be taken of this view. + * @returns A UIImageView with the snapshot of @c view and matching frame. + */ +UIImageView* NISnapshotViewOfView(UIView* view); + +/** + * Returns a UIImage snapshot of the given view with transparency. + * + * This method takes into account the offset of scrollable views and captures whatever is currently + * in the frame of the view. + * + * @param view A snapshot will be taken of this view. + * @returns A UIImage with the snapshot of @c view. + */ +UIImage* NISnapshotOfViewWithTransparency(UIView* view); + +/** + * Returns a UIImageView with an image snapshot with transparency of the given view. + * + * The frame of the returned view is set to match the frame of @c view. + * + * @param view A snapshot will be taken of this view. + * @returns A UIImageView with the snapshot of @c view and matching frame. + */ +UIImageView* NISnapshotViewOfViewWithTransparency(UIView* view); + +#if defined __cplusplus +} +#endif + +/** + * @} + */ + +/** @name Creating a Snapshot Rotation Object */ + +/** + * Initializes a newly allocated rotation object with a given delegate. + * + * @param delegate A delegate that implements the NISnapshotRotation protocol. + * @returns A NISnapshotRotation object initialized with @c delegate. + * @fn NISnapshotRotation::initWithDelegate: + */ + +/** @name Accessing the Delegate */ + +/** + * The delegate of the snapshot rotation object. + * + * The delegate must adopt the NISnapshotRotation protocol. The NISnapshotRotation class, which does + * not retain the delegate, invokes each protocol method the delegate implements. + * + * @fn NISnapshotRotation::delegate + */ + +/** @name Implementing UIViewController Autorotation */ + +/** + * Prepares the animation for a rotation by taking a snapshot of the rotatingView in its current + * state. + * + * This method must be called from your UIViewController implementation. + * + * @fn NISnapshotRotation::willRotateToInterfaceOrientation:duration: + */ + +/** + * Crossfades between the initial and final snapshots. + * + * This method must be called from your UIViewController implementation. + * + * @fn NISnapshotRotation::willAnimateRotationToInterfaceOrientation:duration: + */ + +/** + * Finalizes the rotation animation by removing the snapshot views from the container view. + * + * This method must be called from your UIViewController implementation. + * + * @fn NISnapshotRotation::didRotateFromInterfaceOrientation: + */ diff --git a/Pods/Nimbus/src/core/src/NISnapshotRotation.m b/Pods/Nimbus/src/core/src/NISnapshotRotation.m new file mode 100644 index 0000000..c337e6a --- /dev/null +++ b/Pods/Nimbus/src/core/src/NISnapshotRotation.m @@ -0,0 +1,299 @@ +// +// Copyright 2011-2014 NimbusKit +// +// This code was originally found in Apple's WWDC Session 240 on +// "Polishing Your Interface Rotations" and has been repurposed into a reusable class. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NISnapshotRotation.h" + +#import "NIDebuggingTools.h" +#import "NISDKAvailability.h" +#import +#import + +#if __IPHONE_OS_VERSION_MIN_REQUIRED < NIIOS_6_0 +#error "Nimbus Snapshot Rotation requires iOS 6 or higher." +#endif + +UIImage* NISnapshotOfViewWithTransparencyOption(UIView* view, BOOL transparency); + +UIImage* NISnapshotOfViewWithTransparencyOption(UIView* view, BOOL transparency) { + // Passing 0 as the last argument ensures that the image context will match the current device's + // scaling mode. + UIGraphicsBeginImageContextWithOptions(view.bounds.size, !transparency, 0); + + CGContextRef cx = UIGraphicsGetCurrentContext(); + + // Views that can scroll do so by modifying their bounds. We want to capture the part of the view + // that is currently in the frame, so we offset by the bounds of the view accordingly. + CGContextTranslateCTM(cx, -view.bounds.origin.x, -view.bounds.origin.y); + + BOOL didDraw = NO; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= NIIOS_7_0 + if ([view respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { + didDraw = [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:YES]; + } +#endif + if (!didDraw) { + [view.layer renderInContext:cx]; + } + + UIImage* image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return image; +} + +UIImage* NISnapshotOfView(UIView* view) { + return NISnapshotOfViewWithTransparencyOption(view, NO); +} + +UIImageView* NISnapshotViewOfView(UIView* view) { + UIImage* image = NISnapshotOfView(view); + + UIImageView* snapshotView = [[UIImageView alloc] initWithImage:image]; + snapshotView.frame = view.frame; + + return snapshotView; +} + +UIImage* NISnapshotOfViewWithTransparency(UIView* view) { + return NISnapshotOfViewWithTransparencyOption(view, YES); +} + +UIImageView* NISnapshotViewOfViewWithTransparency(UIView* view) { + UIImage* image = NISnapshotOfViewWithTransparency(view); + + UIImageView* snapshotView = [[UIImageView alloc] initWithImage:image]; + snapshotView.frame = view.frame; + + return snapshotView; +} + +@interface NISnapshotRotation() +@property (nonatomic, assign) BOOL isSupportedOS; +@property (nonatomic, assign) CGRect frameBeforeRotation; +@property (nonatomic, assign) CGRect frameAfterRotation; + +@property (nonatomic, strong) UIImageView* snapshotViewBeforeRotation; +@property (nonatomic, strong) UIImageView* snapshotViewAfterRotation; +@end + +@implementation NISnapshotRotation + +- (id)initWithDelegate:(id)delegate { + if ((self = [super init])) { + _delegate = delegate; + + // Check whether this feature is supported or not. + UIImage* image = [[UIImage alloc] init]; + _isSupportedOS = [image respondsToSelector:@selector(resizableImageWithCapInsets:resizingMode:)]; + } + return self; +} + +- (id)init { + return [self initWithDelegate:nil]; +} + +- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { + if (!self.isSupportedOS) { + return; + } + + UIView* containerView = [self.delegate containerViewForSnapshotRotation:self]; + UIView* rotationView = [self.delegate rotatingViewForSnapshotRotation:self]; + + // The container view must not be the same as the rotation view. + NIDASSERT(containerView != rotationView); + if (containerView == rotationView) { + return; + } + + self.frameBeforeRotation = rotationView.frame; + self.snapshotViewBeforeRotation = NISnapshotViewOfViewWithTransparency(rotationView); + [containerView insertSubview:self.snapshotViewBeforeRotation aboveSubview:rotationView]; +} + +- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { + if (!self.isSupportedOS) { + return; + } + + UIView* containerView = [self.delegate containerViewForSnapshotRotation:self]; + UIView* rotationView = [self.delegate rotatingViewForSnapshotRotation:self]; + + // The container view must not be the same as the rotation view. + NIDASSERT(containerView != rotationView); + if (containerView == rotationView) { + return; + } + + self.frameAfterRotation = rotationView.frame; + + [UIView setAnimationsEnabled:NO]; + + self.snapshotViewAfterRotation = NISnapshotViewOfViewWithTransparency(rotationView); + // Set the new frame while maintaining the old frame's height. + self.snapshotViewAfterRotation.frame = CGRectMake(self.frameBeforeRotation.origin.x, + self.frameBeforeRotation.origin.y, + self.frameBeforeRotation.size.width, + self.snapshotViewAfterRotation.frame.size.height); + + UIImage* imageBeforeRotation = self.snapshotViewBeforeRotation.image; + UIImage* imageAfterRotation = self.snapshotViewAfterRotation.image; + + if ([self.delegate respondsToSelector:@selector(fixedInsetsForSnapshotRotation:)]) { + UIEdgeInsets fixedInsets = [self.delegate fixedInsetsForSnapshotRotation:self]; + + imageBeforeRotation = [imageBeforeRotation resizableImageWithCapInsets:fixedInsets resizingMode:UIImageResizingModeStretch]; + imageAfterRotation = [imageAfterRotation resizableImageWithCapInsets:fixedInsets resizingMode:UIImageResizingModeStretch]; + } + + self.snapshotViewBeforeRotation.image = imageBeforeRotation; + self.snapshotViewAfterRotation.image = imageAfterRotation; + + [UIView setAnimationsEnabled:YES]; + + if (imageAfterRotation.size.height < imageBeforeRotation.size.height) { + self.snapshotViewAfterRotation.alpha = 0; + + [containerView insertSubview:self.snapshotViewAfterRotation aboveSubview:self.snapshotViewBeforeRotation]; + + self.snapshotViewAfterRotation.alpha = 1; + + } else { + [containerView insertSubview:self.snapshotViewAfterRotation belowSubview:self.snapshotViewBeforeRotation]; + self.snapshotViewBeforeRotation.alpha = 0; + } + + self.snapshotViewAfterRotation.frame = self.frameAfterRotation; + self.snapshotViewBeforeRotation.frame = CGRectMake(self.frameAfterRotation.origin.x, + self.frameAfterRotation.origin.y, + self.frameAfterRotation.size.width, + self.snapshotViewBeforeRotation.frame.size.height); + + rotationView.hidden = YES; +} + +- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { + if (!self.isSupportedOS) { + return; + } + + UIView* containerView = [self.delegate containerViewForSnapshotRotation:self]; + UIView* rotationView = [self.delegate rotatingViewForSnapshotRotation:self]; + + // The container view must not be the same as the rotation view. + NIDASSERT(containerView != rotationView); + if (containerView == rotationView) { + return; + } + + [self.snapshotViewBeforeRotation removeFromSuperview]; + [self.snapshotViewAfterRotation removeFromSuperview]; + self.snapshotViewBeforeRotation = nil; + self.snapshotViewAfterRotation = nil; + + rotationView.hidden = NO; +} + +@end + +@interface NITableViewSnapshotRotation() +@property (nonatomic, weak) id forwardingDelegate; +@end + +@implementation NITableViewSnapshotRotation + +- (void)setDelegate:(id)delegate { + if (delegate == self) { + [super setDelegate:delegate]; + + } else { + self.forwardingDelegate = delegate; + } +} + +- (id)init { + if ((self = [super init])) { + self.delegate = self; + } + return self; +} + +#pragma mark - Forward Invocations + +- (BOOL)shouldForwardSelectorToDelegate:(SEL)selector { + struct objc_method_description description; + // Only forward the selector if it's part of the protocol. + description = protocol_getMethodDescription(@protocol(NISnapshotRotationDelegate), selector, NO, YES); + + BOOL isSelectorInProtocol = (description.name != NULL && description.types != NULL); + return (isSelectorInProtocol && [self.forwardingDelegate respondsToSelector:selector]); +} + +- (BOOL)respondsToSelector:(SEL)selector { + if ([super respondsToSelector:selector] == YES) { + return YES; + + } else { + return [self shouldForwardSelectorToDelegate:selector]; + } +} + +- (id)forwardingTargetForSelector:(SEL)selector { + if ([self shouldForwardSelectorToDelegate:selector]) { + return self.forwardingDelegate; + + } else { + return nil; + } +} + +#pragma mark - NISnapshotRotation + +- (UIView *)containerViewForSnapshotRotation:(NISnapshotRotation *)snapshotRotation { + return [self.forwardingDelegate containerViewForSnapshotRotation:snapshotRotation]; +} + +- (UIView *)rotatingViewForSnapshotRotation:(NISnapshotRotation *)snapshotRotation { + return [self.forwardingDelegate rotatingViewForSnapshotRotation:snapshotRotation]; +} + +- (UIEdgeInsets)fixedInsetsForSnapshotRotation:(NISnapshotRotation *)snapshotRotation { + UIEdgeInsets insets = UIEdgeInsetsZero; + + // Find the right edge of the content view in the coordinate space of the UITableView. + UIView* rotatingView = [self.forwardingDelegate rotatingViewForSnapshotRotation:snapshotRotation]; + NIDASSERT([rotatingView isKindOfClass:[UITableView class]]); + if ([rotatingView isKindOfClass:[UITableView class]]) { + UITableView* tableView = (UITableView *)rotatingView; + + NSArray* visibleCells = tableView.visibleCells; + if (visibleCells.count > 0) { + UIView* contentView = [[visibleCells objectAtIndex:0] contentView]; + CGFloat contentViewRightEdge = [tableView convertPoint:CGPointMake(contentView.bounds.size.width, 0) fromView:contentView].x; + + CGFloat fixedRightWidth = tableView.bounds.size.width - contentViewRightEdge; + CGFloat fixedLeftWidth = MIN(snapshotRotation.frameAfterRotation.size.width, snapshotRotation.frameBeforeRotation.size.width) - fixedRightWidth - 1; + insets = UIEdgeInsetsMake(0, fixedLeftWidth, 0, fixedRightWidth); + } + } + return insets; +} + +@end diff --git a/Pods/Nimbus/src/core/src/NIState.h b/Pods/Nimbus/src/core/src/NIState.h new file mode 100644 index 0000000..7d53e9c --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIState.h @@ -0,0 +1,84 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@class NIImageMemoryCache; + +/** + * For modifying Nimbus state information. + * + * @ingroup NimbusCore + * @defgroup Core-State State + * @{ + * + * The Nimbus core provides a common layer of features used by nearly all of the libraries in + * the Nimbus ecosystem. Here you will find methods for accessing and setting the global image + * cache amongst other things. + */ + +/** + * The Nimbus state interface. + * + * @ingroup Core-State + */ +@interface Nimbus : NSObject + +#pragma mark Accessing Global State /** @name Accessing Global State */ + +/** + * Access the global image memory cache. + * + * If a cache hasn't been assigned via Nimbus::setGlobalImageMemoryCache: then one will be created + * automatically. + * + * @remarks The default image cache has no upper limit on its memory consumption. It is + * up to you to specify an upper limit in your application. + */ ++ (NIImageMemoryCache *)imageMemoryCache; + +/** + * Access the global network operation queue. + * + * The global network operation queue exists to be used for asynchronous network requests if + * you choose. By defining a global operation queue in the core of Nimbus, we can ensure that + * all libraries that depend on core will use the same network operation queue unless configured + * otherwise. + * + * If an operation queue hasn't been assigned via Nimbus::setGlobalNetworkOperationQueue: then + * one will be created automatically with the default iOS settings. + */ ++ (NSOperationQueue *)networkOperationQueue; + +#pragma mark Modifying Global State /** @name Modifying Global State */ + +/** + * Set the global image memory cache. + * + * The cache will be retained and the old cache released. + */ ++ (void)setImageMemoryCache:(NIImageMemoryCache *)imageMemoryCache; + +/** + * Set the global network operation queue. + * + * The queue will be retained and the old queue released. + */ ++ (void)setNetworkOperationQueue:(NSOperationQueue *)queue; + +@end + +/**@}*/// End of State //////////////////////////////////////////////////////////////////////////// diff --git a/Pods/Nimbus/src/core/src/NIState.m b/Pods/Nimbus/src/core/src/NIState.m new file mode 100644 index 0000000..064e20f --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIState.m @@ -0,0 +1,58 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIState.h" + +#import "NIInMemoryCache.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +static NIImageMemoryCache* sNimbusGlobalMemoryCache = nil; +static NSOperationQueue* sNimbusGlobalOperationQueue = nil; + +@implementation Nimbus + ++ (void)setImageMemoryCache:(NIImageMemoryCache *)imageMemoryCache { + if (sNimbusGlobalMemoryCache != imageMemoryCache) { + sNimbusGlobalMemoryCache = nil; + sNimbusGlobalMemoryCache = imageMemoryCache; + } +} + ++ (NIImageMemoryCache *)imageMemoryCache { + if (nil == sNimbusGlobalMemoryCache) { + sNimbusGlobalMemoryCache = [[NIImageMemoryCache alloc] init]; + } + return sNimbusGlobalMemoryCache; +} + ++ (void)setNetworkOperationQueue:(NSOperationQueue *)queue { + if (sNimbusGlobalOperationQueue != queue) { + sNimbusGlobalOperationQueue = nil; + sNimbusGlobalOperationQueue = queue; + } +} + ++ (NSOperationQueue *)networkOperationQueue { + if (nil == sNimbusGlobalOperationQueue) { + sNimbusGlobalOperationQueue = [[NSOperationQueue alloc] init]; + } + return sNimbusGlobalOperationQueue; +} + +@end diff --git a/Pods/Nimbus/src/core/src/NIViewRecycler.h b/Pods/Nimbus/src/core/src/NIViewRecycler.h new file mode 100644 index 0000000..1925ce1 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIViewRecycler.h @@ -0,0 +1,164 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +/** + * For recycling views in scroll views. + * + * @ingroup NimbusCore + * @defgroup Core-View-Recycling View Recyling + * @{ + * + * View recycling is an important aspect of iOS memory management and performance when building + * scroll views. UITableView uses view recycling via the table cell dequeue mechanism. + * NIViewRecycler implements this recycling functionality, allowing you to implement recycling + * mechanisms in your own views and controllers. + * + * + *

Example Use

+ * + * Imagine building a UITableView. We'll assume that a viewRecycler object exists in the view. + * + * Views are usually recycled once they are no longer on screen, so within a did scroll event + * we might have code like the following: + * +@code +for (UIView* view in visibleViews) { + if (![self isVisible:view]) { + [viewRecycler recycleView:view]; + [view removeFromSuperview]; + } +} +@endcode + * + * This will take the views that are no longer visible and add them to the recycler. At a later + * point in that same didScroll code we will check if there are any new views that are visible. + * This is when we try to dequeue a recycled view from the recycler. + * +@code +UIView* view = [viewRecycler dequeueReusableViewWithIdentifier:reuseIdentifier]; +if (nil == view) { + // Allocate a new view that conforms to the NIRecyclableView protocol. + view = [[[...]] autorelease]; +} +[self addSubview:view]; +@endcode + * + */ + +@protocol NIRecyclableView; + +/** + * An object for efficiently reusing views by recycling and dequeuing them from a pool of views. + * + * This sort of object is likely what UITableView and NIPagingScrollView use to recycle their views. + */ +@interface NIViewRecycler : NSObject + +- (UIView *)dequeueReusableViewWithIdentifier:(NSString *)reuseIdentifier; + +- (void)recycleView:(UIView *)view; + +- (void)removeAllViews; + +@end + +/** + * The NIRecyclableView protocol defines a set of optional methods that a view may implement to + * handle being added to a NIViewRecycler. + */ +@protocol NIRecyclableView + +@optional + +/** + * The identifier used to categorize views into buckets for reuse. + * + * Views will be reused when a new view is requested with a matching identifier. + * + * If the reuseIdentifier is nil then the class name will be used. + */ +@property (nonatomic, copy) NSString* reuseIdentifier; + +/** + * Called immediately after the view has been dequeued from the recycled view pool. + */ +- (void)prepareForReuse; + +@end + +/** + * A simple implementation of the NIRecyclableView protocol as a UIView. + * + * This class can be used as a base class for building recyclable views if specific reuse + * identifiers are necessary, e.g. when the same class might have different implementations + * depending on the reuse identifier. + * + * Assuming functionality is consistent for a given class it is simpler not to have a + * reuseIdentifier, making the view recycler use the class name as the reuseIdentifier. In this case + * subclassing this class is overkill. + */ +@interface NIRecyclableView : UIView + +// Designated initializer. +- (id)initWithReuseIdentifier:(NSString *)reuseIdentifier; + +@property (nonatomic, copy) NSString* reuseIdentifier; + +@end + +/**@}*/ // End of View Recyling + +/** + * Dequeues a reusable view from the recycled views pool if one exists, otherwise returns nil. + * + * @fn NIViewRecycler::dequeueReusableViewWithIdentifier: + * @param reuseIdentifier Often the name of the class of view you wish to fetch. + */ + +/** + * Adds a given view to the recycled views pool. + * + * @fn NIViewRecycler::recycleView: + * @param view The view to recycle. The reuse identifier will be retrieved from the view + * via the NIRecyclableView protocol. + */ + +/** + * Removes all of the views from the recycled views pool. + * + * @fn NIViewRecycler::removeAllViews + */ + +/** + * Initializes a newly allocated view with the given reuse identifier. + * + * This is the designated initializer. + * + * @fn NIRecyclableView::initWithReuseIdentifier: + * @param reuseIdentifier The identifier that will be used to group this view in the view + * recycler. + */ + +/** + * This view's reuse identifier. + * + * Used by NIViewRecycler to pool this view into a group of similar recycled views. + * + * @fn NIRecyclableView::reuseIdentifier + */ diff --git a/Pods/Nimbus/src/core/src/NIViewRecycler.m b/Pods/Nimbus/src/core/src/NIViewRecycler.m new file mode 100644 index 0000000..d5bcf50 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NIViewRecycler.m @@ -0,0 +1,111 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIViewRecycler.h" + +#import "NimbusCore.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +@interface NIViewRecycler() +@property (nonatomic, strong) NSMutableDictionary* reuseIdentifiersToRecycledViews; +@end + +@implementation NIViewRecycler + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (id)init { + if ((self = [super init])) { + _reuseIdentifiersToRecycledViews = [[NSMutableDictionary alloc] init]; + + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self + selector:@selector(reduceMemoryUsage) + name:UIApplicationDidReceiveMemoryWarningNotification + object:nil]; + } + return self; +} + +#pragma mark - Memory Warnings + +- (void)reduceMemoryUsage { + [self removeAllViews]; +} + +#pragma mark - Public + +- (UIView *)dequeueReusableViewWithIdentifier:(NSString *)reuseIdentifier { + NSMutableArray* views = [_reuseIdentifiersToRecycledViews objectForKey:reuseIdentifier]; + UIView* view = [views lastObject]; + if (nil != view) { + [views removeLastObject]; + if ([view respondsToSelector:@selector(prepareForReuse)]) { + [view prepareForReuse]; + } + } + return view; +} + +- (void)recycleView:(UIView *)view { + NIDASSERT([view isKindOfClass:[UIView class]]); + + NSString* reuseIdentifier = nil; + if ([view respondsToSelector:@selector(reuseIdentifier)]) { + reuseIdentifier = [view reuseIdentifier];; + } + if (nil == reuseIdentifier) { + reuseIdentifier = NSStringFromClass([view class]); + } + + NIDASSERT(nil != reuseIdentifier); + if (nil == reuseIdentifier) { + return; + } + + NSMutableArray* views = [_reuseIdentifiersToRecycledViews objectForKey:reuseIdentifier]; + if (nil == views) { + views = [[NSMutableArray alloc] init]; + [_reuseIdentifiersToRecycledViews setObject:views forKey:reuseIdentifier]; + } + [views addObject:view]; +} + +- (void)removeAllViews { + [_reuseIdentifiersToRecycledViews removeAllObjects]; +} + +@end + +@implementation NIRecyclableView + +- (id)initWithReuseIdentifier:(NSString *)reuseIdentifier { + if ((self = [super initWithFrame:CGRectZero])) { + _reuseIdentifier = reuseIdentifier; + } + return self; +} + +- (id)initWithFrame:(CGRect)frame { + return [self initWithReuseIdentifier:nil]; +} + +@end diff --git a/Pods/Nimbus/src/core/src/NimbusCore+Additions.h b/Pods/Nimbus/src/core/src/NimbusCore+Additions.h new file mode 100644 index 0000000..aebaa2a --- /dev/null +++ b/Pods/Nimbus/src/core/src/NimbusCore+Additions.h @@ -0,0 +1,26 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// All category documentation is found in the source files due to limitations of Doxygen. +// Look for the documentation in the Classes tab of the documentation. + +#import +#import + +#import "NimbusCore.h" + +// Additions +#import "UIResponder+NimbusCore.h" diff --git a/Pods/Nimbus/src/core/src/NimbusCore.h b/Pods/Nimbus/src/core/src/NimbusCore.h new file mode 100644 index 0000000..71c2aa9 --- /dev/null +++ b/Pods/Nimbus/src/core/src/NimbusCore.h @@ -0,0 +1,120 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +/** + * @defgroup NimbusCore Nimbus Core + * + *
+ * + * Nimbus' Core defines the foundation upon which all other Nimbus features are built. + * Within the core you will find common elements used to build iOS applications + * including in-memory caches, path manipulation, and SDK availability. These features form + * the foundation upon which all other Nimbus libraries are built. + * + *

How Features are Added to the Core

+ * + * As a general rule of thumb, if something is used between multiple independent libraries or + * applications with little variation, it likely qualifies to be added to the Core. + * + *

Exceptions

+ * + * Standalone user interface components are rarely acceptable features to add to the Core. + * For example: photo viewers, pull to refresh, launchers, attributed labels. + * + * Nimbus is not UIKit: we don't have the privilege of being an assumed cost on every iOS + * device. Developers must carefully weigh whether it is worth adding a Nimbus feature - along + * with its dependencies - over building the feature themselves or using another library. This + * means that an incredible amount of care must be placed into deciding what gets added to the + * Core. + * + *

How Features are Removed from the Core

+ * + * It is inevitable that certain aspects of the Core will grow and develop over time. If a + * feature gets to the point where the value of being a separate library is greater than the + * overhead of managing such a library, then the feature should be considered for removal + * from the Core. + * + * Great care must be taken to ensure that Nimbus doesn't become a framework composed of + * hundreds of miniscule libraries. + * + *

Common autoresizing masks

+ * + * Nimbus provides the following macros: UIViewAutoresizingFlexibleMargins, + * UIViewAutoresizingFlexibleDimensions, UIViewAutoresizingNavigationBar, and + * UIViewAutoresizingToolbarBar. + * +@code +// Create a view that fills its superview's bounds. +UIView* contentView = [[UIView alloc] initWithFrame:self.view.bounds]; +contentView.autoresizingMask = UIViewAutoresizingFlexibleDimensions; +[self.view addSubview:contentView]; + +// Create a view that is always centered in the superview's bounds. +UIView* centeredView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)]; +centeredView.autoresizingMask = UIViewAutoresizingFlexibleMargins; +// Center the view within the superview however you choose. +[self.view addSubview:centeredView]; + +// Create a navigation bar that stays fixed to the top. +UINavigationBar* navBar = [[UINavigationBar alloc] initWithFrame:CGRectZero]; +[navBar sizeToFit]; +navBar.autoresizingMask = UIViewAutoresizingNavigationBar; +[self.view addSubview:navBar]; + +// Create a toolbar that stays fixed to the bottom. +UIToolbar* toolBar = [[UIToolbar alloc] initWithFrame:CGRectZero]; +[toolBar sizeToFit]; +toolBar.autoresizingMask = UIViewAutoresizingToolbarBar; +[self.view addSubview:toolBar]; +@endcode + * + *

Why they exist

+ * + * Using the existing UIViewAutoresizing flags can be tedious for common flags. + * + * For example, to make a view have flexible margins you would need to write four flags: + * +@code +view.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin + | UIViewAutoresizingFlexibleTopMargin + | UIViewAutoresizingFlexibleRightMargin + | UIViewAutoresizingFlexibleBottomMargin); +@endcode + */ + +#import +#import + +#import "NIActions.h" +#import "NIButtonUtilities.h" +#import "NICommonMetrics.h" +#import "NIDebuggingTools.h" +#import "NIDeviceOrientation.h" +#import "NIError.h" +#import "NIFoundationMethods.h" +#import "NIImageUtilities.h" +#import "NIInMemoryCache.h" +#import "NINetworkActivity.h" +#import "NINonEmptyCollectionTesting.h" +#import "NINonRetainingCollections.h" +#import "NIOperations.h" +#import "NIPaths.h" +#import "NIPreprocessorMacros.h" +#import "NIRuntimeClassModifications.h" +#import "NISDKAvailability.h" +#import "NISnapshotRotation.h" +#import "NIState.h" +#import "NIViewRecycler.h" diff --git a/Pods/Nimbus/src/core/src/UIResponder+NimbusCore.h b/Pods/Nimbus/src/core/src/UIResponder+NimbusCore.h new file mode 100644 index 0000000..1d400a6 --- /dev/null +++ b/Pods/Nimbus/src/core/src/UIResponder+NimbusCore.h @@ -0,0 +1,26 @@ +// +// Copyright 2011-2014 NimbusKit +// +// +currentFirstResponder originally written by Jakob Egger, adapted by Jeff Verkoeyen. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +// Documentation for these additions is found in the .m file. +@interface UIResponder (NimbusCore) + ++ (instancetype)nimbus_currentFirstResponder; + +@end diff --git a/Pods/Nimbus/src/core/src/UIResponder+NimbusCore.m b/Pods/Nimbus/src/core/src/UIResponder+NimbusCore.m new file mode 100644 index 0000000..328e16f --- /dev/null +++ b/Pods/Nimbus/src/core/src/UIResponder+NimbusCore.m @@ -0,0 +1,49 @@ +// +// Copyright 2011-2014 NimbusKit +// +// +currentFirstResponder originally written by Jakob Egger, adapted by Jeff Verkoeyen. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "UIResponder+NimbusCore.h" + +#import "NIPreprocessorMacros.h" + +// Adapted from http://stackoverflow.com/questions/5029267/is-there-any-way-of-asking-an-ios-view-which-of-its-children-has-first-responder/14135456#14135456 + +static __weak id sCurrentFirstResponder = nil; + +NI_FIX_CATEGORY_BUG(UIResponderNimbusCore) +/** + * For working with UIResponders. + */ +@implementation UIResponder (NimbusCore) + +/** + * Returns the current first responder by sending an action from the UIApplication. + * + * The implementation was adapted from http://stackoverflow.com/questions/5029267/is-there-any-way-of-asking-an-ios-view-which-of-its-children-has-first-responder/14135456#14135456 + */ ++ (instancetype)nimbus_currentFirstResponder { + sCurrentFirstResponder = nil; + [[UIApplication sharedApplication] sendAction:@selector(nimbus_findFirstResponder:) + to:nil from:nil forEvent:nil]; + return sCurrentFirstResponder; +} + +- (void)nimbus_findFirstResponder:(id)sender { + sCurrentFirstResponder = self; +} + +@end diff --git a/Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollView+Subclassing.h b/Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollView+Subclassing.h new file mode 100644 index 0000000..8776d06 --- /dev/null +++ b/Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollView+Subclassing.h @@ -0,0 +1,123 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIPagingScrollView.h" + +// Methods that are meant to be subclassed. +@interface NIPagingScrollView (Subclassing) + +// Meant to be subclassed. Default implementations are stubs. +- (void)willDisplayPage:(UIView *)pageView; +- (void)didRecyclePage:(UIView *)pageView; +- (void)didReloadNumberOfPages; +- (void)didChangeCenterPageIndexFrom:(NSInteger)from to:(NSInteger)to; + +// Meant to be subclassed. +- (UIView *)loadPageAtIndex:(NSInteger)pageIndex; + +#pragma mark Accessing Child Views + +- (UIScrollView *)scrollView; +- (NSMutableSet *)visiblePages; // Set of UIView* + +@end + +// Methods that are not meant to be subclassed. +@interface NIPagingScrollView (ProtectedMethods) + +- (void)setCenterPageIndexIvar:(NSInteger)centerPageIndex; +- (void)recyclePageAtIndex:(NSInteger)pageIndex; +- (void)displayPageAtIndex:(NSInteger)pageIndex; +- (CGFloat)pageScrollableDimension; +- (void)layoutVisiblePages; + +@end + +/** + * Called before the page is about to be shown and after its frame has been set. + * + * Meant to be subclassed. By default this method does nothing. + * + * @fn NIPagingScrollView::willDisplayPage: + */ + +/** + * Called immediately after the page is removed from the paging scroll view. + * + * Meant to be subclassed. By default this method does nothing. + * + * @fn NIPagingScrollView::didRecyclePage: + */ + +/** + * Called immediately after the data source has been queried for its number of + * pages. + * + * Meant to be subclassed. By default this method does nothing. + * + * @fn NIPagingScrollView::didReloadNumberOfPages + */ + +/** + * Called when the visible page has changed. + * + * Meant to be subclassed. By default this method does nothing. + * + * @fn NIPagingScrollView::didChangeCenterPageIndexFrom:to: + */ + +/** + * Called when a page needs to be loaded before it is displayed. + * + * By default this method asks the data source for the page at the given index. + * A subclass may chose to modify the page index using a transformation method + * before calling super. + * + * @fn NIPagingScrollView::loadPageAtIndex: + */ + +/** + * Sets the centerPageIndex ivar without side effects. + * + * @fn NIPagingScrollView::setCenterPageIndexIvar: + */ + +/** + * Recycles the page at the given index. + * + * @fn NIPagingScrollView::recyclePageAtIndex: + */ + +/** + * Displays the page at the given index. + * + * @fn NIPagingScrollView::displayPageAtIndex: + */ + +/** + * Returns the page's scrollable dimension. + * + * This is the width of the paging scroll view for horizontal scroll views, or + * the height of the paging scroll view for vertical scroll views. + * + * @fn NIPagingScrollView::pageScrollableDimension + */ + +/** + * Updates the frames of all visible pages based on their page indices. + * + * @fn NIPagingScrollView::layoutVisiblePages + */ diff --git a/Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollView.h b/Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollView.h new file mode 100644 index 0000000..a155dab --- /dev/null +++ b/Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollView.h @@ -0,0 +1,386 @@ +// +// Copyright 2011-2014 NimbusKit +// Copyright 2012 Manu Cornet (vertical layouts) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +#import "NimbusCore.h" + +/** + * numberOfPages will be this value until reloadData is called. + */ +extern const NSInteger NIPagingScrollViewUnknownNumberOfPages; + +/** + * The default number of pixels on the side of each page. + * + * Value: 10 + */ +extern const CGFloat NIPagingScrollViewDefaultPageMargin; + +typedef enum { + NIPagingScrollViewHorizontal = 0, + NIPagingScrollViewVertical, +} NIPagingScrollViewType; + +@protocol NIPagingScrollViewDataSource; +@protocol NIPagingScrollViewDelegate; +@protocol NIPagingScrollViewPage; +@class NIViewRecycler; + +/** + * The NIPagingScrollView class provides a UITableView-like interface for loading pages via a data + * source. + * + * @ingroup NimbusPagingScrollView + */ +@interface NIPagingScrollView : UIView + +#pragma mark Data Source + +- (void)reloadData; +@property (nonatomic, weak) id dataSource; +@property (nonatomic, weak) id delegate; + +// It is highly recommended that you use this method to manage view recycling. +- (UIView *)dequeueReusablePageWithIdentifier:(NSString *)identifier; + +#pragma mark State + +- (UIView *)centerPageView; +@property (nonatomic) NSInteger centerPageIndex; // Use moveToPageAtIndex:animated: to animate to a given page. + +@property (nonatomic, readonly) NSInteger numberOfPages; + +#pragma mark Configuring Presentation + +// Controls the border between pages. +@property (nonatomic) CGFloat pageMargin; +// Used to make the view smaller than the frame of the paging scroll view, thus showing +// neighboring pages, either horizontally or vertically depending on the configuration +// of the view. +@property (nonatomic) CGFloat pageInset; +@property (nonatomic) NIPagingScrollViewType type; // Default: NIPagingScrollViewHorizontal + +#pragma mark Visible Pages + +- (BOOL)hasNext; +- (BOOL)hasPrevious; +- (void)moveToNextAnimated:(BOOL)animated; +- (void)moveToPreviousAnimated:(BOOL)animated; +- (BOOL)moveToPageAtIndex:(NSInteger)pageIndex animated:(BOOL)animated updateVisiblePagesWhileScrolling:(BOOL)updateVisiblePagesWhileScrolling; + +// Short form for moveToPageAtIndex:pageIndex animated:animated updateVisiblePagesWhileScrolling:NO +- (BOOL)moveToPageAtIndex:(NSInteger)pageIndex animated:(BOOL)animated; + +#pragma mark Rotating the Scroll View + +- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration; +- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration; + +@end + +/** + * The delegate for NIPagingScrollView. + * + * @ingroup NimbusPagingScrollView + */ +@protocol NIPagingScrollViewDelegate +@optional + +#pragma mark Scrolling and Zooming /** @name [NIPhotoAlbumScrollViewDelegate] Scrolling and Zooming */ + +/** + * The user is scrolling between two photos. + */ +- (void)pagingScrollViewDidScroll:(NIPagingScrollView *)pagingScrollView; + +#pragma mark Changing Pages /** @name [NIPagingScrollViewDelegate] Changing Pages */ + +/** + * The current page will change. + * + * pagingScrollView.centerPageIndex will reflect the old page index, not the new + * page index. + */ +- (void)pagingScrollViewWillChangePages:(NIPagingScrollView *)pagingScrollView; + +/** + * The current page has changed. + * + * pagingScrollView.centerPageIndex will reflect the changed page index. + */ +- (void)pagingScrollViewDidChangePages:(NIPagingScrollView *)pagingScrollView; + +@end + +/** + * The data source for NIPagingScrollView. + * + * @ingroup NimbusPagingScrollView + */ +@protocol NIPagingScrollViewDataSource +@required + +#pragma mark Fetching Required Album Information /** @name [NIPagingScrollViewDataSource] Fetching Required Album Information */ + +/** + * Fetches the total number of pages in the scroll view. + * + * The value returned in this method will be cached by the scroll view until reloadData + * is called again. + */ +- (NSInteger)numberOfPagesInPagingScrollView:(NIPagingScrollView *)pagingScrollView; + +/** + * Fetches a page that will be displayed at the given page index. + * + * You should always try to reuse pages by calling dequeueReusablePageWithIdentifier: on the + * paging scroll view before allocating a new page. + */ +- (UIView *)pagingScrollView:(NIPagingScrollView *)pagingScrollView pageViewForIndex:(NSInteger)pageIndex; + +@end + +/** + * The protocol that a paging scroll view page should implement. + * + * By providing a protocol instead of a UIView base class we allow more flexibility when + * building pages. + * + * @ingroup NimbusPagingScrollView + */ +@protocol NIPagingScrollViewPage +@required + +/** + * The index of this page view. + */ +@property (nonatomic, assign) NSInteger pageIndex; + +@optional + +/** + * Called after the page has gone off-screen. + * + * This method should be used to reset any state information after a page goes off-screen. + * For example, in the Nimbus photo viewer we reset the zoom scale so that if the photo + * was zoomed in it will fit on the screen again when the user flips back and forth between + * two pages. + */ +- (void)pageDidDisappear; + +/** + * Called when the frame of the page is going to change. + * + * Use this method to maintain any state that may be affected by the frame changing. + * The Nimbus photo viewer uses this method to save and restore the zoom and center + * point. This makes the photo always appear to rotate around the center point of the screen + * rather than the center of the photo. + */ +- (void)setFrameAndMaintainState:(CGRect)frame; + +@end + +/** @name Data Source */ + +/** + * The data source for this page album view. + * + * This is the only means by which this paging view acquires any information about the + * album to be displayed. + * + * @fn NIPagingScrollView::dataSource + */ + +/** + * Force the view to reload its data by asking the data source for information. + * + * This must be called at least once after dataSource has been set in order for the view + * to gather any presentable information. + * + * This method is cheap because we only fetch new information about the currently displayed + * pages. If the number of pages shrinks then the current center page index will be decreased + * accordingly. + * + * @fn NIPagingScrollView::reloadData + */ + +/** + * Dequeues a reusable page from the set of recycled pages. + * + * If no pages have been recycled for the given identifier then this will return nil. + * In this case it is your responsibility to create a new page. + * + * @fn NIPagingScrollView::dequeueReusablePageWithIdentifier: + */ + +/** + * The delegate for this paging view. + * + * Any user interactions or state changes are sent to the delegate through this property. + * + * @fn NIPagingScrollView::delegate + */ + +/** @name Configuring Presentation */ + +/** + * The number of pixels on either side of each page. + * + * The space between each page will be 2x this value. + * + * By default this is NIPagingScrollViewDefaultPageMargin. + * + * @fn NIPagingScrollView::pageMargin + */ + +/** + * The type of paging scroll view to display. + * + * This property allows you to configure whether you want a horizontal or vertical paging scroll + * view. You should set this property before you present the scroll view and not modify it after. + * + * By default this is NIPagingScrollViewHorizontal. + * + * @fn NIPagingScrollView::type + */ + +/** @name State */ + +/** + * The current center page view. + * + * If no pages exist then this will return nil. + * + * @fn NIPagingScrollView::centerPageView + */ + +/** + * The current center page index. + * + * This is a zero-based value. If you intend to use this in a label such as "page ## of n" be + * sure to add one to this value. + * + * Setting this value directly will center the new page without any animation. + * + * @fn NIPagingScrollView::centerPageIndex + */ + +/** + * Change the center page index with optional animation. + * + * This method is deprecated in favor of + * @link NIPagingScrollView::moveToPageAtIndex:animated: moveToPageAtIndex:animated:@endlink + * + * @fn NIPagingScrollView::setCenterPageIndex:animated: + */ + +/** + * The total number of pages in this paging view, as gathered from the data source. + * + * This value is cached after reloadData has been called. + * + * Until reloadData is called the first time, numberOfPages will be + * NIPagingScrollViewUnknownNumberOfPages. + * + * @fn NIPagingScrollView::numberOfPages + */ + +/** @name Changing the Visible Page */ + +/** + * Returns YES if there is a next page. + * + * @fn NIPagingScrollView::hasNext + */ + +/** + * Returns YES if there is a previous page. + * + * @fn NIPagingScrollView::hasPrevious + */ + +/** + * Move to the next page if there is one. + * + * @fn NIPagingScrollView::moveToNextAnimated: + */ + +/** + * Move to the previous page if there is one. + * + * @fn NIPagingScrollView::moveToPreviousAnimated: + */ + +/** + * Move to the given page index with optional animation. + * + * @returns NO if a page change animation is already in effect and we couldn't change the page + * again. + * @fn NIPagingScrollView::moveToPageAtIndex:animated: + */ + +/** + * Move to the given page index with optional animation and option to enable page updates while + * scrolling. + * + * NOTE: Passing YES for moveToPageAtIndex:animated:updateVisiblePagesWhileScrolling will cause + * every page from the present page to the destination page to be loaded. This has the potential to + * cause choppy animations. + * + * @param updateVisiblePagesWhileScrolling If YES, will query the data source for any pages + * that become visible while the animation occurs. + * @returns NO if a page change animation is already in effect and we couldn't change the page + * again. + * @fn NIPagingScrollView::moveToPageAtIndex:animated:updateVisiblePagesWhileScrolling: + */ + +/** @name Rotating the Scroll View */ + +/** + * Stores the current state of the scroll view in preparation for rotation. + * + * This must be called in conjunction with willAnimateRotationToInterfaceOrientation:duration: + * in the methods by the same name from the view controller containing this view. + * + * @fn NIPagingScrollView::willRotateToInterfaceOrientation:duration: + */ + +/** + * Updates the frame of the scroll view while maintaining the current visible page's state. + * + * @fn NIPagingScrollView::willAnimateRotationToInterfaceOrientation:duration: + */ + +/** @name Subclassing */ + +/** + * The internal scroll view. + * + * Meant to be used by subclasses only. + * + * @fn NIPagingScrollView::pagingScrollView + */ + +/** + * The set of currently visible pages. + * + * Meant to be used by subclasses only. + * + * @fn NIPagingScrollView::visiblePages + */ diff --git a/Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollView.m b/Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollView.m new file mode 100644 index 0000000..9cfcc79 --- /dev/null +++ b/Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollView.m @@ -0,0 +1,760 @@ +// +// Copyright 2011-2014 NimbusKit +// Copyright 2012 Manu Cornet (vertical layouts) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIPagingScrollView.h" +#import "NIPagingScrollView+Subclassing.h" + +#import "NimbusCore.h" + +#import + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +const NSInteger NIPagingScrollViewUnknownNumberOfPages = -1; +const CGFloat NIPagingScrollViewDefaultPageMargin = 10; +const CGFloat NIPagingScrollViewDefaultPageInset = 0; + +@implementation NIPagingScrollView { + NIViewRecycler* _viewRecycler; + UIScrollView* _scrollView; + + NSMutableSet* _visiblePages; + + // Animating to Pages + NSInteger _animatingToPageIndex; + BOOL _isKillingAnimation; + NSInteger _queuedAnimationPageIndex; + BOOL _shouldUpdateVisiblePagesWhileScrolling; + + // Rotation State + NSInteger _firstVisiblePageIndexBeforeRotation; + CGFloat _percentScrolledIntoFirstVisiblePage; +} + +- (void)commonInit { + // Default state. + self.pageMargin = NIPagingScrollViewDefaultPageMargin; + self.pageInset = NIPagingScrollViewDefaultPageInset; + self.type = NIPagingScrollViewHorizontal; + + // Internal state + _animatingToPageIndex = -1; + _firstVisiblePageIndexBeforeRotation = -1; + _percentScrolledIntoFirstVisiblePage = -1; + _centerPageIndex = -1; + _numberOfPages = NIPagingScrollViewUnknownNumberOfPages; + + _viewRecycler = [[NIViewRecycler alloc] init]; + + // The internal scroll view that powers this paging scroll view. + _scrollView = [[UIScrollView alloc] initWithFrame:self.bounds]; + _scrollView.pagingEnabled = YES; + _scrollView.scrollsToTop = NO; + + // Allows the scroll view to show adjacent pages... + _scrollView.clipsToBounds = NO; + // ...while still clipping contents to the bounds of the paging scroll view. + self.clipsToBounds = YES; + + _scrollView.autoresizingMask = UIViewAutoresizingFlexibleDimensions; + + _scrollView.delegate = self; + + _scrollView.showsVerticalScrollIndicator = NO; + _scrollView.showsHorizontalScrollIndicator = NO; + + [self addSubview:_scrollView]; +} + +- (id)initWithFrame:(CGRect)frame { + if ((self = [super initWithFrame:frame])) { + [self commonInit]; + } + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder { + if ((self = [super initWithCoder:aDecoder])) { + [self commonInit]; + } + return self; +} + +#pragma mark - Page Layout + +- (void)layoutSubviews { + [super layoutSubviews]; + _scrollView.frame = [self frameForPagingScrollView]; + + // Retain the current position. + CGPoint offset = [self frameForPageAtIndex:_centerPageIndex].origin; + _scrollView.contentOffset = [self contentOffsetFromPageOffset:offset]; + + _scrollView.contentSize = [self contentSizeForPagingScrollView]; + [self layoutVisiblePages]; +} + +// The following three methods are from Apple's ImageScrollView example application and have +// been used here because they are well-documented and concise. + +- (CGRect)frameForPagingScrollView { + CGRect frame = self.bounds; + + if (NIPagingScrollViewHorizontal == self.type) { + // We make the paging scroll view a little bit wider on the side edges so that there + // there is space between the pages when flipping through them. + frame = CGRectInset(frame, self.pageInset - self.pageMargin, 0); + + } else if (NIPagingScrollViewVertical == self.type) { + frame = CGRectInset(frame, 0, self.pageInset - self.pageMargin); + } + + return frame; +} + +- (CGRect)frameForPageAtIndex:(NSInteger)pageIndex { + // We have to use our paging scroll view's bounds, not frame, to calculate the page + // placement. When the device is in landscape orientation, the frame will still be in + // portrait because the pagingScrollView is the root view controller's view, so its + // frame is in window coordinate space, which is never rotated. Its bounds, however, + // will be in landscape because it has a rotation transform applied. + CGRect bounds = _scrollView.bounds; + CGRect pageFrame = bounds; + + if (NIPagingScrollViewHorizontal == self.type) { + pageFrame.origin.x = (bounds.size.width * pageIndex); + // We need to counter the extra spacing added to the paging scroll view in + // frameForPagingScrollView. + pageFrame = CGRectInset(pageFrame, self.pageMargin, 0); + + } else if (NIPagingScrollViewVertical == self.type) { + pageFrame.origin.y = (bounds.size.height * pageIndex); + pageFrame = CGRectInset(pageFrame, 0, self.pageMargin); + } + + return pageFrame; +} + +- (CGSize)contentSizeForPagingScrollView { + // We use the paging scroll view's bounds to calculate the contentSize, for the same reason + // outlined above. + CGRect bounds = _scrollView.bounds; + if (NIPagingScrollViewHorizontal == self.type) { + return CGSizeMake(bounds.size.width * self.numberOfPages, bounds.size.height); + + } else if (NIPagingScrollViewVertical == self.type) { + return CGSizeMake(bounds.size.width, bounds.size.height * self.numberOfPages); + } + + return CGSizeZero; +} + +- (CGPoint)contentOffsetFromPageOffset:(CGPoint)offset { + if (NIPagingScrollViewHorizontal == self.type) { + offset.x -= self.pageMargin; + + } else if (NIPagingScrollViewVertical == self.type) { + offset.y -= self.pageMargin; + } + + return offset; +} + +- (CGFloat)pageScrollableDimension { + if (NIPagingScrollViewHorizontal == self.type) { + return _scrollView.bounds.size.width; + + } else if (NIPagingScrollViewVertical == self.type) { + return _scrollView.bounds.size.height; + } + + return 0; +} + +- (CGPoint)contentOffsetFromOffset:(CGFloat)offset { + if (NIPagingScrollViewHorizontal == self.type) { + return CGPointMake(offset, 0); + + } else if (NIPagingScrollViewVertical == self.type) { + return CGPointMake(0, offset); + } + + return CGPointMake(0, 0); +} + +- (CGFloat)scrolledPageOffset { + if (NIPagingScrollViewHorizontal == self.type) { + return _scrollView.contentOffset.x; + + } else if (NIPagingScrollViewVertical == self.type) { + return _scrollView.contentOffset.y; + } + + return 0; +} + +#pragma mark - Visible Page Management + +- (BOOL)isDisplayingPageForIndex:(NSInteger)pageIndex { + BOOL foundPage = NO; + + // There will never be more than a handful (3 without insets) of visible pages in this array, so this lookup is + // effectively O(C) constant time. + for (UIView * page in _visiblePages) { + if (page.pageIndex == pageIndex) { + foundPage = YES; + break; + } + } + + return foundPage; +} + +- (NSInteger)currentVisiblePageIndex { + CGPoint contentOffset = _scrollView.contentOffset; + CGSize boundsSize = _scrollView.bounds.size; + + if (NIPagingScrollViewHorizontal == self.type) { + // Whatever image is currently displayed in the center of the screen is the currently + // visible image. + return NIBoundi((NSInteger)(NICGFloatFloor((contentOffset.x + boundsSize.width / 2) / boundsSize.width) + + 0.5f), + 0, self.numberOfPages - 1); + + } else if (NIPagingScrollViewVertical == self.type) { + return NIBoundi((NSInteger)(NICGFloatFloor((contentOffset.y + boundsSize.height / 2) / boundsSize.height) + + 0.5f), + 0, self.numberOfPages - 1); + } + + return 0; +} + +- (NSRange)rangeOfVisiblePages { + if (0 >= self.numberOfPages) { + return NSMakeRange(0, 0); + } + + NSInteger visibleRange = 1; + if (_pageInset != 0) { + CGSize boundsSize = _scrollView.bounds.size; + CGSize frameSize = self.frame.size; + visibleRange = (NSInteger)ceil(frameSize.width / (boundsSize.width + _pageMargin)); + } + + NSInteger currentVisiblePageIndex = [self currentVisiblePageIndex]; + + NSInteger firstVisiblePageIndex = NIBoundi(currentVisiblePageIndex - visibleRange, 0, self.numberOfPages - 1); + NSInteger lastVisiblePageIndex = NIBoundi(currentVisiblePageIndex + visibleRange, 0, self.numberOfPages - 1); + + return NSMakeRange(firstVisiblePageIndex, lastVisiblePageIndex - firstVisiblePageIndex + 1); +} + +- (void)willDisplayPage:(UIView *)pageView atIndex:(NSInteger)pageIndex { + pageView.pageIndex = pageIndex; + pageView.frame = [self frameForPageAtIndex:pageIndex]; + + [self willDisplayPage:pageView]; +} + +- (void)resetPage:(id)page { + if ([page respondsToSelector:@selector(pageDidDisappear)]) { + [page pageDidDisappear]; + } +} + +- (void)resetSurroundingPages { + for (id page in _visiblePages) { + if (page.pageIndex != self.centerPageIndex) { + [self resetPage:page]; + } + } +} + +- (UIView *)dequeueReusablePageWithIdentifier:(NSString *)identifier { + NIDASSERT(nil != identifier); + if (nil == identifier) { + return nil; + } + + return (UIView *)[_viewRecycler dequeueReusableViewWithIdentifier:identifier]; +} + +- (UIView *)loadPageAtIndex:(NSInteger)pageIndex { + UIView* page = [self.dataSource pagingScrollView:self pageViewForIndex:pageIndex]; + + NIDASSERT([page isKindOfClass:[UIView class]]); + NIDASSERT([page conformsToProtocol:@protocol(NIPagingScrollViewPage)]); + + if (nil == page || ![page isKindOfClass:[UIView class]] + || ![page conformsToProtocol:@protocol(NIPagingScrollViewPage)]) { + // Bail out! This page is malformed. + return nil; + } + + return page; +} + +- (void)displayPageAtIndex:(NSInteger)pageIndex { + UIView* page = [self loadPageAtIndex:pageIndex]; + if (nil == page) { + return; + } + + // This will only be called once, before the page is shown. + [self willDisplayPage:page atIndex:pageIndex]; + + [_scrollView addSubview:page]; + [_visiblePages addObject:page]; +} + +- (void)recyclePageAtIndex:(NSInteger)pageIndex { + for (UIView* page in [_visiblePages copy]) { + if (page.pageIndex == pageIndex) { + [_viewRecycler recycleView:page]; + [page removeFromSuperview]; + + [self didRecyclePage:page]; + + [_visiblePages removeObject:page]; + } + } +} + +- (void)preloadOffscreenPages { + NSRange rangeOfVisiblePages = [self rangeOfVisiblePages]; + for (NSUInteger pageIndex = rangeOfVisiblePages.location; + pageIndex < NSMaxRange(rangeOfVisiblePages); ++pageIndex) { + if (![self isDisplayingPageForIndex:pageIndex]) { + [self displayPageAtIndex:pageIndex]; + } + } +} + +- (void)updateVisiblePagesShouldNotifyDelegate:(BOOL)shouldNotifyDelegate { + // Before updating _centerPageIndex, notify delegate + if (shouldNotifyDelegate && (self.numberOfPages > 0) && + ([self currentVisiblePageIndex] != self.centerPageIndex) && + [self.delegate respondsToSelector:@selector(pagingScrollViewWillChangePages:)]) { + [self.delegate pagingScrollViewWillChangePages:self]; + } + + NSRange rangeOfVisiblePages = [self rangeOfVisiblePages]; + // Recycle no-longer-visible pages. We copy _visiblePages because we may modify it while we're + // iterating over it. + for (UIView* page in [_visiblePages copy]) { + if (!NSLocationInRange(page.pageIndex, rangeOfVisiblePages)) { + [_viewRecycler recycleView:page]; + [page removeFromSuperview]; + + [self didRecyclePage:page]; + + [_visiblePages removeObject:page]; + } + } + + NSInteger oldCenterPageIndex = self.centerPageIndex; + + if (self.numberOfPages > 0) { + _centerPageIndex = [self currentVisiblePageIndex]; + + [self didChangeCenterPageIndexFrom:oldCenterPageIndex to:_centerPageIndex]; + + if (_pageInset != 0) { + // Load all visible insetted pages immediately. + [self preloadOffscreenPages]; + } else { + // Prioritize displaying the currently visible page. + if (![self isDisplayingPageForIndex:_centerPageIndex]) { + [self displayPageAtIndex:_centerPageIndex]; + } + + // Add missing pages after displaying the current page. + [self performSelector:@selector(preloadOffscreenPages) + withObject:nil + afterDelay:0]; + } + } else { + _centerPageIndex = -1; + } + + if (shouldNotifyDelegate && oldCenterPageIndex != _centerPageIndex + && [self.delegate respondsToSelector:@selector(pagingScrollViewDidChangePages:)]) { + [self.delegate pagingScrollViewDidChangePages:self]; + } +} + +- (void)layoutVisiblePages { + for (UIView* page in _visiblePages) { + CGRect pageFrame = [self frameForPageAtIndex:page.pageIndex]; + if ([page respondsToSelector:@selector(setFrameAndMaintainState:)]) { + [page setFrameAndMaintainState:pageFrame]; + + } else { + [page setFrame:pageFrame]; + } + } +} + +#pragma mark - UIView + +- (void)setFrame:(CGRect)frame { + // We have to modify this method because it eventually leads to changing the content offset + // programmatically. When this happens we end up getting a scrollViewDidScroll: message + // during which we do not want to modify the visible pages because this is handled elsewhere. + [super setFrame:frame]; + + _scrollView.contentSize = [self contentSizeForPagingScrollView]; + [self layoutVisiblePages]; +} + +- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { + UIView *view = [super hitTest:point withEvent:event]; + // We must forward hits for the scrollView or else the smaller frame when + // it is inset will prevent touches outside the scrollView bounds. + if (view == self) { + return _scrollView; + } + return view; +} + +#pragma mark - UIScrollViewDelegate + +- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { + [self updateVisiblePagesShouldNotifyDelegate:YES]; + _isKillingAnimation = NO; + + if ([self.delegate respondsToSelector:_cmd]) { + [self.delegate scrollViewWillBeginDragging:scrollView]; + } +} + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { + if ([scrollView isTracking] && [scrollView isDragging]) { + if ([self.delegate respondsToSelector:@selector(pagingScrollViewDidScroll:)]) { + [self.delegate pagingScrollViewDidScroll:self]; + } + } + if (_shouldUpdateVisiblePagesWhileScrolling + && ![scrollView isTracking] && ![scrollView isDragging]) { + [self updateVisiblePagesShouldNotifyDelegate:YES]; + } + + if ([self.delegate respondsToSelector:_cmd]) { + [self.delegate scrollViewDidScroll:scrollView]; + } + + if (_isKillingAnimation) { + // The content size is calculated based on the number of pages and the scroll view frame. + CGPoint offset = [self frameForPageAtIndex:_centerPageIndex].origin; + offset = [self contentOffsetFromPageOffset:offset]; + _scrollView.contentOffset = offset; + } +} + +- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { + _isKillingAnimation = NO; + + if (!decelerate) { + [self updateVisiblePagesShouldNotifyDelegate:YES]; + [self resetSurroundingPages]; + } + + if ([self.delegate respondsToSelector:_cmd]) { + [self.delegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; + } +} + +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { + [self updateVisiblePagesShouldNotifyDelegate:YES]; + [self resetSurroundingPages]; + + if ([self.delegate respondsToSelector:_cmd]) { + [self.delegate scrollViewDidEndDecelerating:scrollView]; + } +} + +- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { + if (_animatingToPageIndex >= 0) { + [self didAnimateToPage:_animatingToPageIndex]; + + if ([self.delegate respondsToSelector:_cmd]) { + [self.delegate scrollViewDidEndScrollingAnimation:scrollView]; + } + } +} + +#pragma mark - Forward UIScrollViewDelegate Methods + + +- (BOOL)shouldForwardSelectorToDelegate:(SEL)aSelector { + struct objc_method_description description; + // Only forward the selector if it's part of the UIScrollViewDelegate protocol. + description = protocol_getMethodDescription(@protocol(UIScrollViewDelegate), + aSelector, + NO, + YES); + + BOOL isSelectorInScrollViewDelegate = (description.name != NULL && description.types != NULL); + return (isSelectorInScrollViewDelegate + && [self.delegate respondsToSelector:aSelector]); +} + +- (BOOL)respondsToSelector:(SEL)aSelector { + if ([super respondsToSelector:aSelector] == YES) { + return YES; + + } else { + return [self shouldForwardSelectorToDelegate:aSelector]; + } +} + +- (id)forwardingTargetForSelector:(SEL)aSelector { + if ([self shouldForwardSelectorToDelegate:aSelector]) { + return self.delegate; + + } else { + return nil; + } +} + +#pragma mark - Subclassing + + +- (void)willDisplayPage:(UIView *)pageView { + // No-op. +} + +- (void)didRecyclePage:(UIView *)pageView { + // No-op +} + +- (void)didReloadNumberOfPages { + // No-op +} + +- (void)didChangeCenterPageIndexFrom:(NSInteger)from to:(NSInteger)to { + // No-op +} + +- (void)setCenterPageIndexIvar:(NSInteger)centerPageIndex { + _centerPageIndex = centerPageIndex; +} + +#pragma mark - Public + + +- (void)reloadData { + _animatingToPageIndex = -1; + NIDASSERT(nil != _dataSource); + + // Remove any visible pages from the view before we release the sets. + for (UIView* page in _visiblePages) { + [_viewRecycler recycleView:page]; + [(UIView *)page removeFromSuperview]; + + [self didRecyclePage:page]; + } + + _visiblePages = nil; + + // If there is no data source then we can't do anything particularly interesting. + if (nil == _dataSource) { + _scrollView.contentSize = self.bounds.size; + _scrollView.contentOffset = CGPointZero; + + // May as well just get rid of all the views then. + [_viewRecycler removeAllViews]; + + return; + } + + _visiblePages = [[NSMutableSet alloc] init]; + + // Cache the number of pages. + _numberOfPages = [_dataSource numberOfPagesInPagingScrollView:self]; + _scrollView.frame = [self frameForPagingScrollView]; + _scrollView.contentSize = [self contentSizeForPagingScrollView]; + + [self didReloadNumberOfPages]; + + NSInteger oldCenterPageIndex = _centerPageIndex; + if (oldCenterPageIndex >= 0) { + _centerPageIndex = NIBoundi(_centerPageIndex, 0, self.numberOfPages - 1); + + if (![_scrollView isTracking] && ![_scrollView isDragging]) { + // The content size is calculated based on the number of pages and the scroll view frame. + CGPoint offset = [self frameForPageAtIndex:_centerPageIndex].origin; + offset = [self contentOffsetFromPageOffset:offset]; + _scrollView.contentOffset = offset; + + _isKillingAnimation = YES; + } + } + + // Begin requesting the page information from the data source. + [self updateVisiblePagesShouldNotifyDelegate:NO]; +} + +- (void)willRotateToInterfaceOrientation: (UIInterfaceOrientation)toInterfaceOrientation + duration: (NSTimeInterval)duration { + // Here, our pagingScrollView bounds have not yet been updated for the new interface + // orientation. This is a good place to calculate the content offset that we will + // need in the new orientation. + CGFloat offset = [self scrolledPageOffset]; + CGFloat pageScrollableDimension = [self pageScrollableDimension]; + + if (offset >= 0) { + _firstVisiblePageIndexBeforeRotation = (NSInteger)NICGFloatFloor(offset / pageScrollableDimension); + _percentScrolledIntoFirstVisiblePage = ((offset + - (_firstVisiblePageIndexBeforeRotation * pageScrollableDimension)) + / pageScrollableDimension); + + } else { + _firstVisiblePageIndexBeforeRotation = 0; + _percentScrolledIntoFirstVisiblePage = offset / pageScrollableDimension; + } +} + +- (void)willAnimateRotationToInterfaceOrientation: (UIInterfaceOrientation)toInterfaceOrientation + duration: (NSTimeInterval)duration { + // Recalculate contentSize based on current orientation. + _scrollView.contentSize = [self contentSizeForPagingScrollView]; + + [self layoutVisiblePages]; + + // Adjust contentOffset to preserve page location based on values collected prior to location. + CGFloat pageScrollableDimension = [self pageScrollableDimension]; + CGFloat newOffset = ((_firstVisiblePageIndexBeforeRotation * pageScrollableDimension) + + (_percentScrolledIntoFirstVisiblePage * pageScrollableDimension)); + _scrollView.contentOffset = [self contentOffsetFromOffset:newOffset]; +} + +- (BOOL)hasNext { + return (self.centerPageIndex < self.numberOfPages - 1); +} + +- (BOOL)hasPrevious { + return self.centerPageIndex > 0; +} + +- (void)didAnimateToPage:(NSInteger)pageIndex { + _shouldUpdateVisiblePagesWhileScrolling = NO; + _animatingToPageIndex = -1; + if (_queuedAnimationPageIndex >= 0 && _queuedAnimationPageIndex != pageIndex) { + [self moveToPageAtIndex:_queuedAnimationPageIndex animated:YES]; + return; + } + + // Reset the content offset once the animation completes, just to be sure that the + // viewer sits on a page bounds even if we rotate the device while animating. + CGPoint offset = [self frameForPageAtIndex:pageIndex].origin; + offset = [self contentOffsetFromPageOffset:offset]; + + _scrollView.contentOffset = offset; + + [self updateVisiblePagesShouldNotifyDelegate:YES]; +} + +- (BOOL)moveToPageAtIndex:(NSInteger)pageIndex animated:(BOOL)animated { + return [self moveToPageAtIndex:pageIndex animated:animated updateVisiblePagesWhileScrolling:NO]; +} + +- (BOOL)moveToPageAtIndex:(NSInteger)pageIndex animated:(BOOL)animated updateVisiblePagesWhileScrolling:(BOOL)updateVisiblePagesWhileScrolling { + if (_animatingToPageIndex >= 0) { + // Don't allow re-entry for sliding animations. + _queuedAnimationPageIndex = pageIndex; + return NO; + } + _shouldUpdateVisiblePagesWhileScrolling = updateVisiblePagesWhileScrolling; + _isKillingAnimation = NO; + _queuedAnimationPageIndex = -1; + + CGPoint offset = [self frameForPageAtIndex:pageIndex].origin; + offset = [self contentOffsetFromPageOffset:offset]; + + // The paging scroll view won't actually animate if the offsets are identical. + animated = animated && !CGPointEqualToPoint(offset, _scrollView.contentOffset); + + if (animated) { + _animatingToPageIndex = pageIndex; + } + [_scrollView setContentOffset:offset animated:animated]; + if (!animated) { + [self resetSurroundingPages]; + [self didAnimateToPage:pageIndex]; + } + return YES; +} + +- (void)moveToNextAnimated:(BOOL)animated { + if ([self hasNext]) { + NSInteger pageIndex = self.centerPageIndex + 1; + + [self moveToPageAtIndex:pageIndex animated:animated]; + } +} + +- (void)moveToPreviousAnimated:(BOOL)animated { + if ([self hasPrevious]) { + NSInteger pageIndex = self.centerPageIndex - 1; + + [self moveToPageAtIndex:pageIndex animated:animated]; + } +} + +- (UIView *)centerPageView { + for (UIView* page in _visiblePages) { + if (page.pageIndex == self.centerPageIndex) { + return page; + } + } + return nil; +} + +- (void)setCenterPageIndex:(NSInteger)centerPageIndex { + [self moveToPageAtIndex:centerPageIndex animated:NO]; +} + +- (void)setPageMargin:(CGFloat)pageMargin { + _pageMargin = pageMargin; + [self setNeedsLayout]; +} + +- (void)setPageInset:(CGFloat)pageInset { + _pageInset = pageInset; + [self setNeedsLayout]; +} + +- (void)setType:(NIPagingScrollViewType)type { + if (_type != type) { + _type = type; + _scrollView.scrollsToTop = (type == NIPagingScrollViewVertical); + } +} + +- (UIScrollView *)scrollView { + return _scrollView; +} + +- (NSMutableSet *)visiblePages { + return _visiblePages; +} + +@end diff --git a/Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollViewPage.h b/Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollViewPage.h new file mode 100644 index 0000000..fca4f8d --- /dev/null +++ b/Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollViewPage.h @@ -0,0 +1,36 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +#import "NIPagingScrollView.h" + +/** + * A skeleton implementation of a page view. + * + * This view simply implements the required properties of NIPagingScrollViewPage. + * + * @ingroup NimbusPagingScrollView + */ +@interface NIPagingScrollViewPage : NIRecyclableView +@property (nonatomic) NSInteger pageIndex; +@end + +/** + * The page index. + * + * @fn NIPagingScrollViewPage::pageIndex + */ diff --git a/Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollViewPage.m b/Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollViewPage.m new file mode 100644 index 0000000..89c94cb --- /dev/null +++ b/Pods/Nimbus/src/pagingscrollview/src/NIPagingScrollViewPage.m @@ -0,0 +1,26 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIPagingScrollViewPage.h" + +#import "NimbusCore.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +@implementation NIPagingScrollViewPage +@end diff --git a/Pods/Nimbus/src/pagingscrollview/src/NimbusPagingScrollView.h b/Pods/Nimbus/src/pagingscrollview/src/NimbusPagingScrollView.h new file mode 100644 index 0000000..5b0c0b1 --- /dev/null +++ b/Pods/Nimbus/src/pagingscrollview/src/NimbusPagingScrollView.h @@ -0,0 +1,53 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +/** + * @defgroup NimbusPagingScrollView Nimbus Paging Scroll View + * @{ + * + *
+ * + * A paging scroll view is a UIScrollView that scrolls horizontally and shows a series of + * pages that are efficiently recycled. + * + * The Nimbus paging scroll view is powered by a datasource that allows you to separate the + * data from the view. This makes it easy to efficiently recycle pages and only create as many + * pages of content as may be visible at any given point in time. Nimbus' implementation also + * provides helpful features such as keeping the center page centered when the device changes + * orientation. + * + * Paging scroll views are commonly used in many iOS applications. For example, Nimbus' Photos + * feature uses a paging scroll view to power its NIPhotoAlbumScrollView. + * + *

Building a Component with NIPagingScrollView

+ * + * NIPagingScrollView works much like a UITableView in that you must implement a data source + * and optionally a delegate. The data source fetches information about the contents of the + * paging scroll view, such as the total number of pages and the view for a given page when it + * is required. The views that you return for pages must conform to the NIPagingScrollViewPage + * protocol. This is similar to UITableViewCell, but rather than subclass a view you can simply + * implement a protocol. If you would prefer not to implement the protocol, you can subclass + * NIPageView which implements the required methods of NIPagingScrollViewPage. + */ + +/**@}*/ + +#import "NIPagingScrollView.h" +#import "NIPagingScrollViewPage.h" + +#import "NimbusCore.h" diff --git a/Pods/Nimbus/src/photos/src/NIPhotoAlbumScrollView.h b/Pods/Nimbus/src/photos/src/NIPhotoAlbumScrollView.h new file mode 100644 index 0000000..3b9b396 --- /dev/null +++ b/Pods/Nimbus/src/photos/src/NIPhotoAlbumScrollView.h @@ -0,0 +1,170 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIPhotoScrollViewDelegate.h" +#import "NIPhotoAlbumScrollViewDataSource.h" +#import "NIPhotoAlbumScrollViewDelegate.h" + +#import "NimbusPagingScrollView.h" + +#import +#import + +/** + * A paged scroll view that shows a collection of photos. + * + * @ingroup NimbusPhotos + * + * This view provides a light-weight implementation of a photo viewer, complete with + * pinch-to-zoom and swiping to change photos. It is designed to perform well with + * large sets of photos and large images that are loaded from either the network or + * disk. + * + * It is intended for this view to be used in conjunction with a view controller that + * implements the data source protocol and presents any required chrome. + * + * @see NIToolbarPhotoViewController + */ +@interface NIPhotoAlbumScrollView : NIPagingScrollView + +#pragma mark Data Source + +// For use in your pagingScrollView:pageForIndex: data source implementation. +- (UIView *)pagingScrollView:(NIPagingScrollView *)pagingScrollView pageViewForIndex:(NSInteger)pageIndex; + +@property (nonatomic, weak) id dataSource; +@property (nonatomic, weak) id delegate; + +#pragma mark Configuring Functionality + +@property (nonatomic, assign, getter=isZoomingEnabled) BOOL zoomingIsEnabled; +@property (nonatomic, assign, getter=isZoomingAboveOriginalSizeEnabled) BOOL zoomingAboveOriginalSizeIsEnabled; +@property (nonatomic, strong) UIColor* photoViewBackgroundColor; + +#pragma mark Configuring Presentation + +@property (nonatomic, strong) UIImage* loadingImage; + +#pragma mark Notifying the View of Loaded Photos + +- (void)didLoadPhoto: (UIImage *)image + atIndex: (NSInteger)photoIndex + photoSize: (NIPhotoScrollViewPhotoSize)photoSize; + +@end + + +/** @name Data Source */ + +/** + * The data source for this photo album view. + * + * This is the only means by which this photo album view acquires any information about the + * album to be displayed. + * + * @fn NIPhotoAlbumScrollView::dataSource + */ + +/** + * Use this method in your implementation of NIPhotoAlbumScrollViewDataSource's + * pagingScrollView:pageForIndex:. + * + * Example: + * +@code +- (id)pagingScrollView:(NIPagingScrollView *)pagingScrollView pageForIndex:(NSInteger)pageIndex { + return [self.photoAlbumView pagingScrollView:pagingScrollView pageForIndex:pageIndex]; +} +@endcode + * + * Automatically uses the paging scroll view's page recycling methods and creates + * NIPhotoScrollViews as needed. + * + * @fn NIPhotoAlbumScrollView::pagingScrollView:pageForIndex: + */ + +/** + * The delegate for this photo album view. + * + * Any user interactions or state changes are sent to the delegate through this property. + * + * @fn NIPhotoAlbumScrollView::delegate + */ + + +/** @name Configuring Functionality */ + +/** + * Whether zooming is enabled or not. + * + * Regardless of whether this is enabled, only original-sized images will be zoomable. + * This is because we often don't know how large the final image is so we can't + * calculate min and max zoom amounts correctly. + * + * By default this is YES. + * + * @fn NIPhotoAlbumScrollView::zoomingIsEnabled + */ + +/** + * Whether small photos can be zoomed at least until they fit the screen. + * + * @see NIPhotoScrollView::zoomingAboveOriginalSizeIsEnabled + * + * By default this is YES. + * + * @fn NIPhotoAlbumScrollView::zoomingAboveOriginalSizeIsEnabled + */ + +/** + * The background color of each photo's view. + * + * By default this is [UIColor blackColor]. + * + * @fn NIPhotoAlbumScrollView::photoViewBackgroundColor + */ + + +/** @name Configuring Presentation */ + +/** + * An image that is displayed while the photo is loading. + * + * This photo will be presented if no image is returned in the data source's implementation + * of photoAlbumScrollView:photoAtIndex:photoSize:isLoading:. + * + * Zooming is disabled when showing a loading image, regardless of the state of zoomingIsEnabled. + * + * By default this is nil. + * + * @fn NIPhotoAlbumScrollView::loadingImage + */ + + +/** @name Notifying the View of Loaded Photos */ + +/** + * Notify the scroll view that a photo has been loaded at a given index. + * + * You should notify the completed loading of thumbnails as well. Calling this method + * is fairly lightweight and will only update the images of the visible pages. Err on the + * side of calling this method too much rather than too little. + * + * The photo at the given index will only be replaced with the given image if photoSize + * is of a higher quality than the currently-displayed photo's size. + * + * @fn NIPhotoAlbumScrollView::didLoadPhoto:atIndex:photoSize: + */ diff --git a/Pods/Nimbus/src/photos/src/NIPhotoAlbumScrollView.m b/Pods/Nimbus/src/photos/src/NIPhotoAlbumScrollView.m new file mode 100644 index 0000000..79709d4 --- /dev/null +++ b/Pods/Nimbus/src/photos/src/NIPhotoAlbumScrollView.m @@ -0,0 +1,221 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIPhotoAlbumScrollView.h" + +#import "NIPagingScrollView+Subclassing.h" +#import "NIPhotoScrollView.h" +#import "NIPhotoAlbumScrollViewDataSource.h" +#import "NimbusCore.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +@implementation NIPhotoAlbumScrollView { + // Configurable Properties + UIImage* _loadingImage; + BOOL _zoomingIsEnabled; + BOOL _zoomingAboveOriginalSizeIsEnabled; +} + +- (id)initWithFrame:(CGRect)frame { + if ((self = [super initWithFrame:frame])) { + // Default state. + self.zoomingIsEnabled = YES; + self.zoomingAboveOriginalSizeIsEnabled = YES; + } + return self; +} + +- (void)setBackgroundColor:(UIColor *)backgroundColor { + [super setBackgroundColor:backgroundColor]; + + self.scrollView.backgroundColor = backgroundColor; +} + +- (void)notifyDelegatePhotoDidLoadAtIndex:(NSInteger)photoIndex { + if (photoIndex == (self.centerPageIndex + 1) + && [self.delegate respondsToSelector:@selector(photoAlbumScrollViewDidLoadNextPhoto:)]) { + [self.delegate photoAlbumScrollViewDidLoadNextPhoto:self]; + + } else if (photoIndex == (self.centerPageIndex - 1) + && [self.delegate respondsToSelector:@selector(photoAlbumScrollViewDidLoadPreviousPhoto:)]) { + [self.delegate photoAlbumScrollViewDidLoadPreviousPhoto:self]; + } +} + +#pragma mark - Visible Page Management + + +- (void)willDisplayPage:(NIPhotoScrollView *)page { + // When we ask the data source for the image we expect the following to happen: + // 1) If the data source has any image at this index, it should return it and set the + // photoSize accordingly. + // 2) If the returned photo is not the highest quality available, the data source should + // start loading the high quality photo and set isLoading to YES. + // 3) If no photo was available, then the data source should start loading the photo + // at its highest available quality and nil should be returned. The loadingImage property + // will be displayed until the image is loaded. isLoading should be set to YES. + NIPhotoScrollViewPhotoSize photoSize = NIPhotoScrollViewPhotoSizeUnknown; + BOOL isLoading = NO; + CGSize originalPhotoDimensions = CGSizeZero; + UIImage* image = [self.dataSource photoAlbumScrollView: self + photoAtIndex: page.pageIndex + photoSize: &photoSize + isLoading: &isLoading + originalPhotoDimensions: &originalPhotoDimensions]; + + page.photoDimensions = originalPhotoDimensions; + // Only mark the view as loading if the center image is loading. + page.loading = (page.pageIndex == self.centerPageIndex) && isLoading; + + if (nil == image) { + page.zoomingIsEnabled = NO; + [page setImage:self.loadingImage photoSize:NIPhotoScrollViewPhotoSizeUnknown]; + + } else { + BOOL updateImage = photoSize > page.photoSize; + if (updateImage) { + [page setImage:image photoSize:photoSize]; + } + + // Configure this after the image is set otherwise if the page's image isn't there + // e.g. (after prepareForReuse), zooming will always be disabled + page.zoomingIsEnabled = ([self isZoomingEnabled] + && (NIPhotoScrollViewPhotoSizeOriginal == photoSize)); + + if (updateImage && NIPhotoScrollViewPhotoSizeOriginal == photoSize) { + [self notifyDelegatePhotoDidLoadAtIndex:page.pageIndex]; + } + } +} + +- (void)didRecyclePage:(UIView *)page { + // Give the data source the opportunity to kill any asynchronous operations for this + // now-recycled page. + if ([self.dataSource respondsToSelector: + @selector(photoAlbumScrollView:stopLoadingPhotoAtIndex:)]) { + [self.dataSource photoAlbumScrollView: self + stopLoadingPhotoAtIndex: page.pageIndex]; + } +} + +#pragma mark - NIPhotoScrollViewDelegate + + +- (void)photoScrollViewDidDoubleTapToZoom: (NIPhotoScrollView *)photoScrollView + didZoomIn: (BOOL)didZoomIn { + if ([self.delegate respondsToSelector:@selector(photoAlbumScrollView:didZoomIn:)]) { + [self.delegate photoAlbumScrollView:self didZoomIn:didZoomIn]; + } +} + +#pragma mark - Public + + +- (UIView *)pagingScrollView:(NIPagingScrollView *)pagingScrollView + pageViewForIndex:(NSInteger)pageIndex { + UIView* pageView = nil; + NSString* reuseIdentifier = @"photo"; + pageView = [pagingScrollView dequeueReusablePageWithIdentifier:reuseIdentifier]; + if (nil == pageView) { + pageView = [[NIPhotoScrollView alloc] init]; + pageView.reuseIdentifier = reuseIdentifier; + pageView.backgroundColor = self.photoViewBackgroundColor; + } + + NIPhotoScrollView* photoScrollView = (NIPhotoScrollView *)pageView; + photoScrollView.photoScrollViewDelegate = self; + photoScrollView.zoomingAboveOriginalSizeIsEnabled = [self isZoomingAboveOriginalSizeEnabled]; + + return pageView; +} + +- (void)didLoadPhoto: (UIImage *)image + atIndex: (NSInteger)pageIndex + photoSize: (NIPhotoScrollViewPhotoSize)photoSize { + // This modifies the UI and therefor MUST be executed on the main thread. + NIDASSERT([NSThread isMainThread]); + + for (NIPhotoScrollView* page in self.visiblePages) { + if (page.pageIndex == pageIndex) { + + // Only replace the photo if it's of a higher quality than one we're already showing. + if (photoSize > page.photoSize) { + page.loading = NO; + [page setImage:image photoSize:photoSize]; + + page.zoomingIsEnabled = ([self isZoomingEnabled] + && (NIPhotoScrollViewPhotoSizeOriginal == photoSize)); + + // Notify the delegate that the photo has been loaded. + if (NIPhotoScrollViewPhotoSizeOriginal == photoSize) { + [self notifyDelegatePhotoDidLoadAtIndex:pageIndex]; + } + } + break; + } + } +} + +- (void)setZoomingAboveOriginalSizeIsEnabled:(BOOL)enabled { + _zoomingAboveOriginalSizeIsEnabled = enabled; + + for (NIPhotoScrollView* page in self.visiblePages) { + page.zoomingAboveOriginalSizeIsEnabled = enabled; + } +} + +- (void)setPhotoViewBackgroundColor:(UIColor *)photoViewBackgroundColor { + if (_photoViewBackgroundColor != photoViewBackgroundColor) { + _photoViewBackgroundColor = photoViewBackgroundColor; + + for (UIView* page in self.visiblePages) { + page.backgroundColor = photoViewBackgroundColor; + } + } +} + +- (BOOL)hasNext { + return (self.centerPageIndex < self.numberOfPages - 1); +} + +- (BOOL)hasPrevious { + return self.centerPageIndex > 0; +} + +- (id)dataSource { + NIDASSERT([[super dataSource] conformsToProtocol:@protocol(NIPhotoAlbumScrollViewDataSource)]); + return (id)[super dataSource]; +} + +- (void)setDataSource:(id)dataSource { + [super setDataSource:(id)dataSource]; +} + +- (id)delegate { + id superDelegate = [super delegate]; + NIDASSERT(nil == superDelegate + || [superDelegate conformsToProtocol:@protocol(NIPhotoAlbumScrollViewDelegate)]); + return (id)superDelegate; +} + +- (void)setDelegate:(id)delegate { + [super setDelegate:(id)delegate]; +} + +@end diff --git a/Pods/Nimbus/src/photos/src/NIPhotoAlbumScrollViewDataSource.h b/Pods/Nimbus/src/photos/src/NIPhotoAlbumScrollViewDataSource.h new file mode 100644 index 0000000..8287a4a --- /dev/null +++ b/Pods/Nimbus/src/photos/src/NIPhotoAlbumScrollViewDataSource.h @@ -0,0 +1,93 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIPhotoScrollViewPhotoSize.h" + +#import "NimbusPagingScrollView.h" + +#import + +@class NIPhotoAlbumScrollView; + +/** + * The photo album scroll data source. + * + * @ingroup NimbusPhotos + * + * This data source emphasizes speed and memory efficiency by requesting images only when + * they're needed and encouraging immediate responses from the data source implementation. + * + * @see NIPhotoAlbumScrollView + */ +@protocol NIPhotoAlbumScrollViewDataSource + +@required + +#pragma mark Fetching Required Album Information /** @name [NIPhotoAlbumScrollViewDataSource] Fetching Required Album Information */ + +/** + * Fetches the highest-quality image available for the photo at the given index. + * + * Your goal should be to make this implementation return as fast as possible. Avoid + * hitting the disk or blocking on a network request. Aim to load images asynchronously. + * + * If you already have the highest-quality image in memory (like in an NIImageMemoryCache), + * then you can simply return the image and set photoSize to be + * NIPhotoScrollViewPhotoSizeOriginal. + * + * If the highest-quality image is not available when this method is called then you should + * spin off an asynchronous operation to load the image and set isLoading to YES. + * + * If you have a thumbnail in memory but not the full-size image yet, then you should return + * the thumbnail, set isLoading to YES, and set photoSize to NIPhotoScrollViewPhotoSizeThumbnail. + * + * Once the high-quality image finishes loading, call didLoadPhoto:atIndex:photoSize: with + * the image. + * + * This method will be called to prefetch the next and previous photos in the scroll view. + * The currently displayed photo will always be requested first. + * + * @attention The photo scroll view does not hold onto the UIImages for very long at all. + * It is up to the controller to decide on an adequate caching policy to ensure + * that images are kept in memory through the life of the photo album. + * In your implementation of the data source you should prioritize thumbnails + * being kept in memory over full-size images. When a memory warning is received, + * the original photos should be relinquished from memory first. + */ +- (UIImage *)photoAlbumScrollView: (NIPhotoAlbumScrollView *)photoAlbumScrollView + photoAtIndex: (NSInteger)photoIndex + photoSize: (NIPhotoScrollViewPhotoSize *)photoSize + isLoading: (BOOL *)isLoading + originalPhotoDimensions: (CGSize *)originalPhotoDimensions; + +@optional + +#pragma mark Optimizing Data Retrieval /** @name [NIPhotoAlbumScrollViewDataSource] Optimizing Data Retrieval */ + +/** + * Called when you should cancel any asynchronous loading requests for the given photo. + * + * When a photo is not immediately visible this method is called to allow the data + * source to minimize the number of active asynchronous operations in place. + * + * This method is optional, though recommended because it focuses the device's processing + * power on the most immediately accessible photos. + */ +- (void)photoAlbumScrollView: (NIPhotoAlbumScrollView *)photoAlbumScrollView + stopLoadingPhotoAtIndex: (NSInteger)photoIndex; + +@end + diff --git a/Pods/Nimbus/src/photos/src/NIPhotoAlbumScrollViewDelegate.h b/Pods/Nimbus/src/photos/src/NIPhotoAlbumScrollViewDelegate.h new file mode 100644 index 0000000..265e4c9 --- /dev/null +++ b/Pods/Nimbus/src/photos/src/NIPhotoAlbumScrollViewDelegate.h @@ -0,0 +1,55 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +#import "NimbusPagingScrollView.h" + +@class NIPhotoAlbumScrollView; + +/** + * The photo album scroll view delegate. + * + * @ingroup Photos-Protocols + * @see NIPhotoAlbumScrollView + */ +@protocol NIPhotoAlbumScrollViewDelegate + +@optional + +#pragma mark Scrolling and Zooming /** @name [NIPhotoAlbumScrollViewDelegate] Scrolling and Zooming */ + +/** + * The user double-tapped to zoom in or out. + */ +- (void)photoAlbumScrollView: (NIPhotoAlbumScrollView *)photoAlbumScrollView + didZoomIn: (BOOL)didZoomIn; + + +#pragma mark Data Availability /** @name [NIPhotoAlbumScrollViewDelegate] Data Availability */ + +/** + * The next photo in the album has been loaded and is ready to be displayed. + */ +- (void)photoAlbumScrollViewDidLoadNextPhoto:(NIPhotoAlbumScrollView *)photoAlbumScrollView; + +/** + * The previous photo in the album has been loaded and is ready to be displayed. + */ +- (void)photoAlbumScrollViewDidLoadPreviousPhoto:(NIPhotoAlbumScrollView *)photoAlbumScrollView; + +@end + diff --git a/Pods/Nimbus/src/photos/src/NIPhotoScrollView.h b/Pods/Nimbus/src/photos/src/NIPhotoScrollView.h new file mode 100644 index 0000000..7a85d04 --- /dev/null +++ b/Pods/Nimbus/src/photos/src/NIPhotoScrollView.h @@ -0,0 +1,162 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIPagingScrollViewPage.h" +#import "NIPhotoScrollViewPhotoSize.h" + +#import +#import + +@protocol NIPhotoScrollViewDelegate; +@class NICenteringScrollView; + +/** + * A single photo view that supports zooming and rotation. + * + * @ingroup NimbusPhotos + */ +@interface NIPhotoScrollView : UIView + +#pragma mark Configuring Functionality + +@property (nonatomic, assign, getter=isZoomingEnabled) BOOL zoomingIsEnabled; // default: yes +@property (nonatomic, assign, getter=isZoomingAboveOriginalSizeEnabled) BOOL zoomingAboveOriginalSizeIsEnabled; // default: yes +@property (nonatomic, assign, getter=isDoubleTapToZoomEnabled) BOOL doubleTapToZoomIsEnabled; // default: yes +@property (nonatomic, assign) CGFloat maximumScale; // default: 0 (autocalculate) +@property (nonatomic, weak) id photoScrollViewDelegate; + +#pragma mark State + +- (UIImage *)image; +- (NIPhotoScrollViewPhotoSize)photoSize; +- (void)setImage:(UIImage *)image photoSize:(NIPhotoScrollViewPhotoSize)photoSize; +@property (nonatomic, assign, getter = isLoading) BOOL loading; + +@property (nonatomic, assign) NSInteger pageIndex; +@property (nonatomic, assign) CGSize photoDimensions; +@property (nonatomic, readonly, strong) UITapGestureRecognizer* doubleTapGestureRecognizer; + +@end + +/** @name Configuring Functionality */ + +/** + * Whether the photo is allowed to be zoomed. + * + * By default this is YES. + * + * @fn NIPhotoScrollView::zoomingIsEnabled + */ + +/** + * Whether small photos can be zoomed at least until they fit the screen. + * + * If this is disabled, images smaller than the view size can not be zoomed in beyond + * their original dimensions. + * + * If this is enabled, images smaller than the view size can be zoomed in only until + * they fit the view bounds. + * + * The default behavior in Photos.app allows small photos to be zoomed in. + * + * @attention This will allow photos to be zoomed in even if they don't have any more + * pixels to show, causing the photo to blur. This can look ok for photographs, + * but might not look ok for software design mockups. + * + * By default this is YES. + * + * @fn NIPhotoScrollView::zoomingAboveOriginalSizeIsEnabled + */ + +/** + * Whether double-tapping zooms in and out of the image. + * + * Available on iOS 3.2 and later. + * + * By default this is YES. + * + * @fn NIPhotoScrollView::doubleTapToZoomIsEnabled + */ + +/** + * The maximum scale of the image. + * + * By default this is 0, meaning the view will automatically determine the maximum scale. + * Setting this to a non-zero value will override the automatically-calculated maximum scale. + * + * @fn NIPhotoScrollView::maximumScale + */ + +/** + * The photo scroll view delegate. + * + * @fn NIPhotoScrollView::photoScrollViewDelegate + */ + + +/** @name State */ + +/** + * The currently-displayed photo. + * + * @fn NIPhotoScrollView::image + */ + +/** + * Set a new photo with a specific size. + * + * If image is nil then the photoSize will be overridden as NIPhotoScrollViewPhotoSizeUnknown. + * + * Resets the current zoom levels and zooms to fit the image. + * + * @fn NIPhotoScrollView::setImage:photoSize: + */ + +/** + * The index of this photo within a photo album. + * + * @fn NIPhotoScrollView::pageIndex + */ + +/** + * The current size of the photo. + * + * This is used to replace the photo only with successively higher-quality versions. + * + * @fn NIPhotoScrollView::photoSize + */ + +/** + * The largest dimensions of the photo. + * + * This is used to show the thumbnail at the final image size in case the final image size + * is smaller than the album's frame. Without this value we have to assume that the thumbnail + * will take up the full screen. If the final image doesn't take up the full screen, then + * the photo view will appear to "snap" to the smaller full-size image when the final image + * does load. + * + * CGSizeZero is used to signify an unknown final photo dimension. + * + * @fn NIPhotoScrollView::photoDimensions + */ + +/** + * The gesture recognizer for double-tapping zooms in and out of the image. + * + * This is used mainly for setting up dependencies between gesture recognizers. + * + * @fn NIPhotoScrollView::doubleTapGestureRecognizer + */ diff --git a/Pods/Nimbus/src/photos/src/NIPhotoScrollView.m b/Pods/Nimbus/src/photos/src/NIPhotoScrollView.m new file mode 100644 index 0000000..196afb3 --- /dev/null +++ b/Pods/Nimbus/src/photos/src/NIPhotoScrollView.m @@ -0,0 +1,510 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIPhotoScrollView.h" + +#import "NIPhotoScrollViewDelegate.h" + +#import "NimbusCore.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +/** + * A UIScrollView that centers the zooming view's frame as the user zooms. + * + * We must update the zooming view's frame within the scroll view's layoutSubviews, + * thus why we've subclassed UIScrollView. + */ +@interface NICenteringScrollView : UIScrollView +@end + + +@implementation NICenteringScrollView + + +#pragma mark - UIView + + +- (void)layoutSubviews { + [super layoutSubviews]; + + // Center the image as it becomes smaller than the size of the screen. + + UIView* zoomingSubview = [self.delegate viewForZoomingInScrollView:self]; + CGSize boundsSize = self.bounds.size; + CGRect frameToCenter = zoomingSubview.frame; + + // Center horizontally. + if (frameToCenter.size.width < boundsSize.width) { + frameToCenter.origin.x = NICGFloatFloor((boundsSize.width - frameToCenter.size.width) / 2); + + } else { + frameToCenter.origin.x = 0; + } + + // Center vertically. + if (frameToCenter.size.height < boundsSize.height) { + frameToCenter.origin.y = NICGFloatFloor((boundsSize.height - frameToCenter.size.height) / 2); + + } else { + frameToCenter.origin.y = 0; + } + + zoomingSubview.frame = frameToCenter; +} + +@end + +@interface NIPhotoScrollView () +@property (nonatomic, assign) NIPhotoScrollViewPhotoSize photoSize; +- (void)setMaxMinZoomScalesForCurrentBounds; +@end + +@implementation NIPhotoScrollView { + // The photo view to be zoomed. + UIImageView* _imageView; + // The scroll view. + NICenteringScrollView* _scrollView; + UIActivityIndicatorView* _loadingView; + + // Photo Information + NIPhotoScrollViewPhotoSize _photoSize; + CGSize _photoDimensions; + + // Configurable State + BOOL _zoomingIsEnabled; + BOOL _zoomingAboveOriginalSizeIsEnabled; + + UITapGestureRecognizer* _doubleTapGestureRecognizer; +} + +@synthesize reuseIdentifier; + +- (id)initWithFrame:(CGRect)frame { + if ((self = [super initWithFrame:frame])) { + // Default configuration. + self.zoomingIsEnabled = YES; + self.zoomingAboveOriginalSizeIsEnabled = YES; + self.doubleTapToZoomIsEnabled = YES; + + // Autorelease so that we don't have to worry about releasing the subviews in dealloc. + _scrollView = [[NICenteringScrollView alloc] initWithFrame:self.bounds]; + _scrollView.autoresizingMask = (UIViewAutoresizingFlexibleWidth + | UIViewAutoresizingFlexibleHeight); + + _loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; + [_loadingView sizeToFit]; + _loadingView.frame = NIFrameOfCenteredViewWithinView(_loadingView, self); + _loadingView.autoresizingMask = UIViewAutoresizingFlexibleMargins; + + // We implement viewForZoomingInScrollView: and return the image view for zooming. + _scrollView.delegate = self; + + // Disable the scroll indicators. + _scrollView.showsVerticalScrollIndicator = NO; + _scrollView.showsHorizontalScrollIndicator = NO; + + // Photo viewers should feel sticky when you're panning around, not smooth and slippery + // like a UITableView. + _scrollView.decelerationRate = UIScrollViewDecelerationRateFast; + + // Ensure that empty areas of the scroll view are draggable. + self.backgroundColor = [UIColor blackColor]; + _scrollView.backgroundColor = self.backgroundColor; + + _imageView = [[UIImageView alloc] initWithFrame:CGRectZero]; + + [_scrollView addSubview:_imageView]; + [self addSubview:_scrollView]; + [self addSubview:_loadingView]; + } + return self; +} + +- (void)setBackgroundColor:(UIColor *)backgroundColor { + [super setBackgroundColor:backgroundColor]; + + _scrollView.backgroundColor = backgroundColor; +} + +#pragma mark - UIScrollView + + +- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { + return _imageView; +} + +#pragma mark - Gesture Recognizers + + +- (CGRect)rectAroundPoint:(CGPoint)point atZoomScale:(CGFloat)zoomScale { + NIDASSERT(zoomScale > 0); + + // Define the shape of the zoom rect. + CGSize boundsSize = self.bounds.size; + + // Modify the size according to the requested zoom level. + // For example, if we're zooming in to 0.5 zoom, then this will increase the bounds size + // by a factor of two. + CGSize scaledBoundsSize = CGSizeMake(boundsSize.width / zoomScale, + boundsSize.height / zoomScale); + + CGRect rect = CGRectMake(point.x - scaledBoundsSize.width / 2, + point.y - scaledBoundsSize.height / 2, + scaledBoundsSize.width, + scaledBoundsSize.height); + + // When the image is zoomed out there is a bit of empty space around the image due + // to the fact that it's centered on the screen. When we created the rect around the + // point we need to take this "space" into account. + + // 1: get the frame of the image in this view's coordinates. + CGRect imageScaledFrame = [self convertRect:_imageView.frame toView:self]; + + // 2: Offset the frame by the excess amount. This will ensure that the zoomed location + // is always centered on the tap location. We only allow positive values because a + // negative value implies that there isn't actually any offset. + rect = CGRectOffset(rect, -MAX(0, imageScaledFrame.origin.x), -MAX(0, imageScaledFrame.origin.y)); + + return rect; +} + +- (void)didDoubleTap:(UITapGestureRecognizer *)tapGesture { + BOOL isCompletelyZoomedIn = (_scrollView.maximumZoomScale <= _scrollView.zoomScale + FLT_EPSILON); + + BOOL didZoomIn; + + if (isCompletelyZoomedIn) { + // Zoom the photo back out. + [_scrollView setZoomScale:_scrollView.minimumZoomScale animated:YES]; + + didZoomIn = NO; + + } else { + // Zoom into the tap point. + CGPoint tapCenter = [tapGesture locationInView:_imageView]; + + CGRect maxZoomRect = [self rectAroundPoint:tapCenter atZoomScale:_scrollView.maximumZoomScale]; + [_scrollView zoomToRect:maxZoomRect animated:YES]; + + didZoomIn = YES; + } + + if ([self.photoScrollViewDelegate respondsToSelector: + @selector(photoScrollViewDidDoubleTapToZoom:didZoomIn:)]) { + [self.photoScrollViewDelegate photoScrollViewDidDoubleTapToZoom:self didZoomIn:didZoomIn]; + } +} + +#pragma mark - NIPagingScrollViewPage + + +- (void)prepareForReuse { + _imageView.image = nil; + self.photoSize = NIPhotoScrollViewPhotoSizeUnknown; + _scrollView.zoomScale = 1; + _scrollView.contentSize = self.bounds.size; +} + +- (void)pageDidDisappear { + _scrollView.zoomScale = _scrollView.minimumZoomScale; +} + +#pragma mark - Public + + +- (void)setImage:(UIImage *)image photoSize:(NIPhotoScrollViewPhotoSize)photoSize { + _imageView.image = image; + [_imageView sizeToFit]; + + if (nil == image) { + self.photoSize = NIPhotoScrollViewPhotoSizeUnknown; + + } else { + self.photoSize = photoSize; + } + + // The min/max zoom values assume that the content size is the image size. The max zoom will + // be a value that allows the image to be seen at a 1-to-1 pixel resolution, while the min + // zoom will be small enough to fit the image on the screen perfectly. + if (nil != image) { + _scrollView.contentSize = image.size; + + } else { + _scrollView.contentSize = self.bounds.size; + } + + [self setMaxMinZoomScalesForCurrentBounds]; + + // Start off with the image fully-visible on the screen. + _scrollView.zoomScale = _scrollView.minimumZoomScale; + + [self setNeedsLayout]; +} + +- (void)setLoading:(BOOL)loading { + _loading = loading; + + if (loading) { + [_loadingView startAnimating]; + } else { + [_loadingView stopAnimating]; + } +} + +- (UIImage *)image { + return _imageView.image; +} + +- (void)setZoomingIsEnabled:(BOOL)enabled { + _zoomingIsEnabled = enabled; + + if (nil != _imageView.image) { + [self setMaxMinZoomScalesForCurrentBounds]; + + // Fit the image on screen. + _scrollView.zoomScale = _scrollView.minimumZoomScale; + + // Disable zoom bouncing if zooming is disabled, otherwise the view will allow pinching. + _scrollView.bouncesZoom = enabled; + + } else { + // Reset to the defaults if there is no set image yet. + _scrollView.zoomScale = 1; + _scrollView.minimumZoomScale = 1; + _scrollView.maximumZoomScale = 1; + _scrollView.bouncesZoom = NO; + } +} + +- (void)setDoubleTapToZoomIsEnabled:(BOOL)enabled { + if (enabled && nil == _doubleTapGestureRecognizer) { + _doubleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didDoubleTap:)]; + _doubleTapGestureRecognizer.numberOfTapsRequired = 2; + [self addGestureRecognizer:_doubleTapGestureRecognizer]; + } + + _doubleTapGestureRecognizer.enabled = enabled; +} + +- (BOOL)isDoubleTapToZoomEnabled { + return [_doubleTapGestureRecognizer isEnabled]; +} + +- (CGFloat)scaleForSize:(CGSize)size boundsSize:(CGSize)boundsSize useMinimalScale:(BOOL)minimalScale { + CGFloat xScale = boundsSize.width / size.width; // The scale needed to perfectly fit the image width-wise. + CGFloat yScale = boundsSize.height / size.height; // The scale needed to perfectly fit the image height-wise. + CGFloat minScale = minimalScale ? MIN(xScale, yScale) : MAX(xScale, yScale); // Use the minimum of these to allow the image to become fully visible, or the maximum to get fullscreen size + + return minScale; +} + +/** + * Calculate the min and max scale for the given dimensions and photo size. + * + * minScale will fit the photo to the bounds, unless it is too small in which case it will + * show the image at a 1-to-1 resolution. + * + * maxScale will be whatever value shows the image at a 1-to-1 resolution, UNLESS + * isZoomingAboveOriginalSizeEnabled is enabled, in which case maxScale will be calculated + * such that the image completely fills the bounds. + * + * Exception: If the photo size is unknown (this is a loading image, for example) then + * the minimum scale will be set without considering the screen scale. This allows the + * loading image to draw with its own image scale if it's a high-res @2x image. + */ +- (void)minAndMaxScaleForDimensions: (CGSize)dimensions + boundsSize: (CGSize)boundsSize + photoScale: (CGFloat)photoScale + photoSize: (NIPhotoScrollViewPhotoSize)photoSize + minScale: (CGFloat *)pMinScale + maxScale: (CGFloat *)pMaxScale { + NIDASSERT(nil != pMinScale); + NIDASSERT(nil != pMaxScale); + if (nil == pMinScale + || nil == pMaxScale) { + return; + } + + CGFloat minScale = [self scaleForSize: dimensions + boundsSize: boundsSize + useMinimalScale: YES]; + + // On high resolution screens we have double the pixel density, so we will be seeing + // every pixel if we limit the maximum zoom scale to 0.5. + // If the photo size is unknown, it's likely that we're showing the loading image and + // don't want to shrink it down with the zoom because it should be a scaled image. + CGFloat maxScale = ((NIPhotoScrollViewPhotoSizeUnknown == photoSize) + ? 1 + : (photoScale / NIScreenScale())); + + if (NIPhotoScrollViewPhotoSizeThumbnail != photoSize) { + // Don't let minScale exceed maxScale. (If the image is smaller than the screen, we + // don't want to force it to be zoomed.) + // todo marco minScale = MIN(minScale, maxScale); + } + + // At this point if the image is small, then minScale and maxScale will be the same because + // we don't want to allow the photo to be zoomed. + + // If zooming above the original size IS enabled, however, expand the max zoom to + // whatever value would make the image fit the view perfectly. + if ([self isZoomingAboveOriginalSizeEnabled]) { + CGFloat idealMaxScale = [self scaleForSize: dimensions + boundsSize: boundsSize + useMinimalScale: NO]; + maxScale = MAX(maxScale, idealMaxScale); + } + + *pMinScale = minScale; + *pMaxScale = maxScale; +} + +- (void)setMaxMinZoomScalesForCurrentBounds { + CGSize imageSize = _imageView.bounds.size; + + // Avoid crashing if the image has no dimensions. + if (imageSize.width <= 0 || imageSize.height <= 0) { + _scrollView.maximumZoomScale = 1; + _scrollView.minimumZoomScale = 1; + return; + } + + // The following code is from Apple's ImageScrollView example application and has been used + // here because it is well-documented and concise. + + CGSize boundsSize = _scrollView.bounds.size; + + CGFloat minScale = 0; + CGFloat maxScale = 0; + + // Calculate the min/max scale for the image to be presented. + [self minAndMaxScaleForDimensions: imageSize + boundsSize: boundsSize + photoScale: _imageView.image.scale + photoSize: self.photoSize + minScale: &minScale + maxScale: &maxScale]; + + // When we show thumbnails for images that are too small for the bounds, we try to use + // the known photo dimensions to scale the minimum scale to match what the final image + // would be. This avoids any "snapping" effects from stretching the thumbnail too large. + if ((NIPhotoScrollViewPhotoSizeThumbnail == self.photoSize) + && !CGSizeEqualToSize(self.photoDimensions, CGSizeZero)) { + CGFloat scaleToFitOriginal = 0; + CGFloat originalMaxScale = 0; + // Calculate the original-sized image's min/max scale. + [self minAndMaxScaleForDimensions: self.photoDimensions + boundsSize: boundsSize + photoScale: _imageView.image.scale + photoSize: NIPhotoScrollViewPhotoSizeOriginal + minScale: &scaleToFitOriginal + maxScale: &originalMaxScale]; + + if (scaleToFitOriginal + FLT_EPSILON >= (1.0 / NIScreenScale())) { + // If the final image will be smaller than the view then we want to use that + // scale as the "true" scale and adjust it relatively to the thumbnail's dimensions. + // This ensures that the thumbnail will always be the same visual size as the original + // image, giving us that sexy "crisping" effect when the thumbnail is loaded. + CGFloat relativeSize = self.photoDimensions.width / imageSize.width; + minScale = scaleToFitOriginal * relativeSize; + } + } + + // If zooming is disabled then we flatten the range for zooming to only allow the min zoom. + if (self.isZoomingEnabled && NIPhotoScrollViewPhotoSizeOriginal == self.photoSize && self.maximumScale > 0) { + _scrollView.maximumZoomScale = self.maximumScale; + } else { + _scrollView.maximumZoomScale = self.isZoomingEnabled ? maxScale : minScale; + } + _scrollView.minimumZoomScale = minScale; +} + +#pragma mark Saving/Restoring Offset and Scale + +// Parts of the following code are from Apple's ImageScrollView example application and +// have been used here because they are well-documented and concise. + + +// Fetch the visual center point of this view in the image view's coordinate space. +- (CGPoint)pointToCenterAfterRotation { + CGRect bounds = _scrollView.bounds; + CGPoint boundsCenter = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)); + return [self convertPoint:boundsCenter toView:_imageView]; +} + +- (CGFloat)scaleToRestoreAfterRotation { + CGFloat contentScale = _scrollView.zoomScale; + + // If we're at the minimum zoom scale, preserve that by returning 0, which + // will be converted to the minimum allowable scale when the scale is restored. + if (contentScale <= _scrollView.minimumZoomScale + FLT_EPSILON) { + contentScale = 0; + } + + return contentScale; +} + +- (CGPoint)maximumContentOffset { + CGSize contentSize = _scrollView.contentSize; + CGSize boundsSize = _scrollView.bounds.size; + return CGPointMake(contentSize.width - boundsSize.width, + contentSize.height - boundsSize.height); +} + +- (CGPoint)minimumContentOffset { + return CGPointZero; +} + +- (void)restoreCenterPoint:(CGPoint)oldCenter scale:(CGFloat)oldScale { + // Step 1: restore zoom scale, making sure it is within the allowable range. + _scrollView.zoomScale = NIBoundf(oldScale, + _scrollView.minimumZoomScale, _scrollView.maximumZoomScale); + + // Step 2: restore center point, making sure it is within the allowable range. + + // 2a: convert our desired center point back to the scroll view's coordinate space from the + // image's coordinate space. + CGPoint boundsCenter = [self convertPoint:oldCenter fromView:_imageView]; + + // 2b: calculate the content offset that would yield that center point + CGPoint offset = CGPointMake(boundsCenter.x - _scrollView.bounds.size.width / 2.0f, + boundsCenter.y - _scrollView.bounds.size.height / 2.0f); + + // 2c: restore offset, adjusted to be within the allowable range + CGPoint maxOffset = [self maximumContentOffset]; + CGPoint minOffset = [self minimumContentOffset]; + offset.x = NIBoundf(offset.x, minOffset.x, maxOffset.x); + offset.y = NIBoundf(offset.y, minOffset.y, maxOffset.y); + _scrollView.contentOffset = offset; +} + +#pragma mark Saving/Restoring Offset and Scale + + +- (void)setFrameAndMaintainState:(CGRect)frame { + CGPoint restorePoint = [self pointToCenterAfterRotation]; + CGFloat restoreScale = [self scaleToRestoreAfterRotation]; + self.frame = frame; + [self setMaxMinZoomScalesForCurrentBounds]; + [self restoreCenterPoint:restorePoint scale:restoreScale]; + + [_scrollView setNeedsLayout]; +} + +@end diff --git a/Pods/Nimbus/src/photos/src/NIPhotoScrollViewDelegate.h b/Pods/Nimbus/src/photos/src/NIPhotoScrollViewDelegate.h new file mode 100644 index 0000000..64cdff5 --- /dev/null +++ b/Pods/Nimbus/src/photos/src/NIPhotoScrollViewDelegate.h @@ -0,0 +1,41 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +@class NIPhotoScrollView; + +/** + * The photo scroll view delegate. + * + * @ingroup NimbusPhotos + */ +@protocol NIPhotoScrollViewDelegate + +@optional + +#pragma mark Zooming /** @name [NIPhotoScrollViewDelegate] Zooming */ + +/** + * The user has double-tapped the photo to zoom either in or out. + * + * @param photoScrollView The photo scroll view that was tapped. + * @param didZoomIn YES if the photo was zoomed in. NO if the photo was zoomed out. + */ +- (void)photoScrollViewDidDoubleTapToZoom: (NIPhotoScrollView *)photoScrollView + didZoomIn: (BOOL)didZoomIn; + +@end diff --git a/Pods/Nimbus/src/photos/src/NIPhotoScrollViewPhotoSize.h b/Pods/Nimbus/src/photos/src/NIPhotoScrollViewPhotoSize.h new file mode 100644 index 0000000..2955cd2 --- /dev/null +++ b/Pods/Nimbus/src/photos/src/NIPhotoScrollViewPhotoSize.h @@ -0,0 +1,31 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +/** + * Contextual information about the size of the photo. + */ +typedef enum { + // Unknown photo size. + NIPhotoScrollViewPhotoSizeUnknown, + + // A smaller version of the image. + NIPhotoScrollViewPhotoSizeThumbnail, + + // The full-size image. + NIPhotoScrollViewPhotoSizeOriginal, +} NIPhotoScrollViewPhotoSize; diff --git a/Pods/Nimbus/src/photos/src/NIPhotoScrubberView.h b/Pods/Nimbus/src/photos/src/NIPhotoScrubberView.h new file mode 100644 index 0000000..04794b0 --- /dev/null +++ b/Pods/Nimbus/src/photos/src/NIPhotoScrubberView.h @@ -0,0 +1,168 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +#import "NIPreprocessorMacros.h" /* for weak */ + +@protocol NIPhotoScrubberViewDataSource; +@protocol NIPhotoScrubberViewDelegate; + +/** + * A control built for quickly skimming through a collection of images. + * + * @ingroup NimbusPhotos + * + * The user interacts with the scrubber by "scrubbing" their finger along the control, + * or more simply, touching the control and moving their finger along a single axis. + * Scrubbers can be seen in the Photos.app on the iPad. + * + * The thumbnails displayed in a scrubber will be a subset of the overall set of photos. + * The wider the scrubber, the more thumbnails will be shown. The displayed thumbnails will + * be chosen at constant intervals in the album, with a larger "selected" thumbnail image + * that will show whatever image is currently selected. This larger thumbnail will be + * positioned relatively within the scrubber to show the user what the current selection + * is in a physically intuitive way. + * + * This view is a completely independent view from the photo scroll view so you can choose + * to use this in your already built photo viewer. + * + * @image html scrubber1.png "Screenshot of NIPhotoScrubberView on the iPad." + * + * @see NIPhotoScrubberViewDataSource + * @see NIPhotoScrubberViewDelegate + */ +@interface NIPhotoScrubberView : UIView + +#pragma mark Data Source /** @name Data Source */ + +/** + * The data source for this scrubber view. + */ +@property (nonatomic, weak) id dataSource; + +/** + * Forces the scrubber view to reload all of its data. + * + * This must be called at least once after dataSource has been set in order for the view + * to gather any presentable information. + * + * This method is expensive. It will reset the state of the view and remove all existing + * thumbnails before requesting the new information from the data source. + */ +- (void)reloadData; + +/** + * Notify the scrubber view that a thumbnail has been loaded at a given index. + * + * This method is cheap, so do not be afraid to call it whenever a thumbnail loads. + * It will only modify visible thumbnails. + */ +- (void)didLoadThumbnail: (UIImage *)image + atIndex: (NSInteger)photoIndex; + +#pragma mark Delegate /** @name Delegate */ + +/** + * The delegate for this scrubber view. + */ +@property (nonatomic, weak) id delegate; + +#pragma mark Accessing Selection /** @name Accessing Selection */ + +/** + * The selected photo index. + */ +@property (nonatomic, assign) NSInteger selectedPhotoIndex; + +/** + * Set the selected photo with animation. + */ +- (void)setSelectedPhotoIndex:(NSInteger)photoIndex animated:(BOOL)animated; + +@end + +/** + * The data source for the photo scrubber. + * + * @ingroup NimbusPhotos + * + *

Performance Considerations

+ * + * A scrubber view's purpose is for instantly flipping through an album of photos. As such, + * it's crucial that your implementation of the data source performs blazingly fast. When + * the scrubber requests a thumbnail from you you should *not* be hitting the disk or blocking + * on a network call. If you don't have the thumbnail available at that exact moment, fire + * off an asynchronous load request (using NIReadFileFromDiskOperation or NIHTTPRequest) + * and return nil. Once the thumbnail is loaded, call didLoadThumbnail:atIndex: to notify + * the scrubber that it can display the thumbnail now. + * + * It is not recommended to use high-res images for your scrubber thumbnails. This is because + * the scrubber will keep a large set of images in memory and if you're giving it + * high-resolution images then you'll find that your app quickly burns through memory. + * If you don't have access to thumbnails from whatever API you're using then you should consider + * not using a scrubber. + * + * @see NIPhotoScrubberView + */ +@protocol NIPhotoScrubberViewDataSource + +@required + +#pragma mark Fetching Required Information /** @name Fetching Required Information */ + +/** + * Fetches the total number of photos in the scroll view. + * + * The value returned in this method will be cached by the scroll view until reloadData + * is called again. + */ +- (NSInteger)numberOfPhotosInScrubberView:(NIPhotoScrubberView *)photoScrubberView; + +/** + * Fetch the thumbnail image for the given photo index. + * + * Please read and understand the performance considerations for this data source. + */ +- (UIImage *)photoScrubberView: (NIPhotoScrubberView *)photoScrubberView + thumbnailAtIndex: (NSInteger)thumbnailIndex; + +@end + +/** + * The delegate for the photo scrubber. + * + * @ingroup NimbusPhotos + * + * Sends notifications of state changes. + * + * @see NIPhotoScrubberView + */ +@protocol NIPhotoScrubberViewDelegate + +@optional + +#pragma mark Selection Changes /** @name Selection Changes */ + +/** + * The photo scrubber changed its selection. + * + * Use photoScrubberView.selectedPhotoIndex to access the current selection. + */ +- (void)photoScrubberViewDidChangeSelection:(NIPhotoScrubberView *)photoScrubberView; + +@end diff --git a/Pods/Nimbus/src/photos/src/NIPhotoScrubberView.m b/Pods/Nimbus/src/photos/src/NIPhotoScrubberView.m new file mode 100644 index 0000000..1df8545 --- /dev/null +++ b/Pods/Nimbus/src/photos/src/NIPhotoScrubberView.m @@ -0,0 +1,465 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIPhotoScrubberView.h" + +#import "NimbusCore.h" + +#import + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +static const NSInteger NIPhotoScrubberViewUnknownTag = -1; + +@interface NIPhotoScrubberView() + +/** + * @internal + * + * A method to encapsulate initilization logic that can be shared by different init methods. + */ +- (void)initializeScrubber; + +/** + * @internal + * + * A lightweight method for updating all of the visible thumbnails in the scrubber. + * + * This method will force the scrubber to lay itself out, calculate how many thumbnails might + * be visible, and then lay out the thumbnails and fetch any thumbnail images it can find. + * + * This method should never take much time to run, so it can safely be used in layoutSubviews. + */ +- (void)updateVisiblePhotos; + +/** + * @internal + * + * Returns a new, autoreleased image view in the style of this photo scrubber. + * + * This implementation returns an image with a 1px solid white border and a black background. + */ +- (UIImageView *)photoView; + +@end + + +@implementation NIPhotoScrubberView { + NSMutableArray* _visiblePhotoViews; + NSMutableSet* _recycledPhotoViews; + + UIView* _containerView; + UIImageView* _selectionView; + + // State + NSInteger _selectedPhotoIndex; + + // Cached data source values + NSInteger _numberOfPhotos; + + // Cached display values + NSInteger _numberOfVisiblePhotos; +} + +- (id)initWithFrame:(CGRect)frame { + if ((self = [super initWithFrame:frame])) { + [self initializeScrubber]; + } + + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder { + if ((self = [super initWithCoder:aDecoder])) { + [self initializeScrubber]; + } + + return self; +} + +- (void)initializeScrubber { + // Only one finger should be allowed to interact with the scrubber at a time. + self.multipleTouchEnabled = NO; + + _containerView = [[UIView alloc] init]; + _containerView.layer.borderColor = [UIColor colorWithWhite:1 alpha:0.1f].CGColor; + _containerView.layer.borderWidth = 1; + _containerView.backgroundColor = [UIColor colorWithWhite:1 alpha:0.3f]; + _containerView.userInteractionEnabled = NO; + [self addSubview:_containerView]; + + _selectionView = [self photoView]; + [self addSubview:_selectionView]; + + _selectedPhotoIndex = -1; +} + +#pragma mark - View Creation + + +- (UIImageView *)photoView { + UIImageView* imageView = [[UIImageView alloc] init]; + + imageView.layer.borderColor = [UIColor whiteColor].CGColor; + imageView.layer.borderWidth = 1; + imageView.backgroundColor = [UIColor blackColor]; + imageView.clipsToBounds = YES; + + imageView.userInteractionEnabled = NO; + + imageView.contentMode = UIViewContentModeScaleAspectFill; + + imageView.tag = NIPhotoScrubberViewUnknownTag; + + return imageView; +} + +#pragma mark - Layout + + +- (CGSize)photoSize { + CGSize boundsSize = self.bounds.size; + + // These numbers are roughly estimated from the Photos.app's scrubber. + CGFloat photoWidth = NICGFloatFloor(boundsSize.height / 2.4f); + CGFloat photoHeight = NICGFloatFloor(photoWidth * 0.75f); + + return CGSizeMake(photoWidth, photoHeight); +} + +- (CGSize)selectionSize { + CGSize boundsSize = self.bounds.size; + + // These numbers are roughly estimated from the Photos.app's scrubber. + CGFloat selectionWidth = NICGFloatFloor(boundsSize.height / 1.2f); + CGFloat selectionHeight = NICGFloatFloor(selectionWidth * 0.75f); + + return CGSizeMake(selectionWidth, selectionHeight); +} + +// The amount of space on either side of the scrubber's left and right edges. +- (CGFloat)horizontalMargins { + CGSize photoSize = [self photoSize]; + return NICGFloatFloor(photoSize.width / 2); +} + +- (CGFloat)spaceBetweenPhotos { + return 1; +} + +// The maximum number of pixels that the scrubber can utilize. The scrubber layer's border +// is contained within this width and must be considered when laying out the thumbnails. +- (CGFloat)maxContentWidth { + CGSize boundsSize = self.bounds.size; + CGFloat horizontalMargins = [self horizontalMargins]; + + CGFloat maxContentWidth = (boundsSize.width + - horizontalMargins * 2); + return maxContentWidth; +} + +- (NSInteger)numberOfVisiblePhotos { + CGSize photoSize = [self photoSize]; + CGFloat spaceBetweenPhotos = [self spaceBetweenPhotos]; + + // Here's where we take into account the container layer's border because we don't want to + // display thumbnails on top of the border. + CGFloat maxContentWidth = ([self maxContentWidth] + - _containerView.layer.borderWidth * 2); + + NSInteger numberOfPhotosThatFit = (NSInteger)floor((maxContentWidth + spaceBetweenPhotos) + / (photoSize.width + spaceBetweenPhotos)); + return MIN(_numberOfPhotos, numberOfPhotosThatFit); +} + +- (CGRect)frameForSelectionAtIndex:(NSInteger)photoIndex { + CGSize photoSize = [self photoSize]; + CGSize selectionSize = [self selectionSize]; + + CGFloat containerWidth = _containerView.bounds.size.width; + // TODO (jverkoey July 21, 2011): I need to figure out why this is necessary. + // Basically, when there are a lot of photos it seems like the selection frame + // slowly gets offset from the thumbnail frame it's supposed to be representing until by the end + // it's off the right edge by a noticeable amount. Trimming off some fat from the right + // edge seems to fix this. + if (_numberOfVisiblePhotos < _numberOfPhotos) { + containerWidth -= photoSize.width / 2; + } + + // Calculate the offset into the container view based on index/numberOfPhotos. + CGFloat relativeOffset = NICGFloatFloor((((CGFloat)photoIndex * containerWidth) + / (CGFloat)MAX(1, _numberOfPhotos))); + + return CGRectMake(NICGFloatFloor(_containerView.frame.origin.x + + relativeOffset + + photoSize.width / 2 - selectionSize.width / 2), + NICGFloatFloor(_containerView.center.y - selectionSize.height / 2), + selectionSize.width, selectionSize.height); +} + +- (CGRect)frameForThumbAtIndex:(NSInteger)thumbIndex { + CGSize photoSize = [self photoSize]; + CGFloat spaceBetweenPhotos = [self spaceBetweenPhotos]; + return CGRectMake(_containerView.layer.borderWidth + + (photoSize.width + spaceBetweenPhotos) * thumbIndex, + _containerView.layer.borderWidth, + photoSize.width, photoSize.height); +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + CGSize boundsSize = self.bounds.size; + + CGSize photoSize = [self photoSize]; + CGFloat spaceBetweenPhotos = [self spaceBetweenPhotos]; + CGFloat maxContentWidth = [self maxContentWidth]; + + // Update the total number of visible photos. + _numberOfVisiblePhotos = [self numberOfVisiblePhotos]; + + // Hide views if there isn't any interesting information to show. + _containerView.hidden = (0 == _numberOfVisiblePhotos); + _selectionView.hidden = (_selectedPhotoIndex < 0 || _containerView.hidden); + + // Calculate the container width using the number of visible photos. + CGFloat containerWidth = ((_numberOfVisiblePhotos * photoSize.width) + + (MAX(0, _numberOfVisiblePhotos - 1) * spaceBetweenPhotos) + + _containerView.layer.borderWidth * 2); + + // Then we center the container in the content area. + CGFloat containerMargins = MAX(0, NICGFloatFloor((maxContentWidth - containerWidth) / 2)); + CGFloat horizontalMargins = [self horizontalMargins]; + CGFloat containerHeight = photoSize.height + _containerView.layer.borderWidth * 2; + + CGFloat containerLeftMargin = horizontalMargins + containerMargins; + CGFloat containerTopMargin = NICGFloatFloor((boundsSize.height - containerHeight) / 2); + + _containerView.frame = CGRectMake(containerLeftMargin, + containerTopMargin, + containerWidth, + containerHeight); + + // Don't bother updating the selected photo index if there isn't a selection; the + // selection view will be hidden anyway. + if (_selectedPhotoIndex >= 0) { + _selectionView.frame = [self frameForSelectionAtIndex:_selectedPhotoIndex]; + } + + // Update the frames for all of the thumbnails. + [self updateVisiblePhotos]; +} + +// Transforms an index into the number of visible photos into an index into the total +// number of photos. +- (NSInteger)photoIndexAtScrubberIndex:(NSInteger)scrubberIndex { + return (NSInteger)(NICGFloatCeil((CGFloat)(scrubberIndex * _numberOfPhotos) + / (CGFloat)_numberOfVisiblePhotos) + + 0.5f); +} + +- (void)updateVisiblePhotos { + if (nil == self.dataSource) { + return; + } + + // This will update the number of visible photos if the layout did indeed change. + [self layoutIfNeeded]; + + // Recycle any views that we no longer need. + while ([_visiblePhotoViews count] > (NSUInteger)_numberOfVisiblePhotos) { + UIView* photoView = [_visiblePhotoViews lastObject]; + [photoView removeFromSuperview]; + + [_recycledPhotoViews addObject:photoView]; + + [_visiblePhotoViews removeLastObject]; + } + + // Lay out the visible photos. + for (NSUInteger ix = 0; ix < (NSUInteger)_numberOfVisiblePhotos; ++ix) { + UIImageView* photoView = nil; + + // We must first get the photo view at this index. + + // If there aren't enough visible photo views then try to recycle another view. + if (ix >= [_visiblePhotoViews count]) { + photoView = [_recycledPhotoViews anyObject]; + if (nil == photoView) { + // Couldn't recycle the view, so create a new one. + photoView = [self photoView]; + + } else { + [_recycledPhotoViews removeObject:photoView]; + } + [_containerView addSubview:photoView]; + [_visiblePhotoViews addObject:photoView]; + + } else { + photoView = [_visiblePhotoViews objectAtIndex:ix]; + } + + NSInteger photoIndex = [self photoIndexAtScrubberIndex:ix]; + + // Only request the thumbnail if this thumbnail's photo index has changed. Otherwise + // we assume that this photo either already has the thumbnail or it's still loading. + if (photoView.tag != photoIndex) { + photoView.tag = photoIndex; + + UIImage* image = [self.dataSource photoScrubberView:self thumbnailAtIndex:photoIndex]; + photoView.image = image; + + if (_selectedPhotoIndex == photoIndex) { + _selectionView.image = image; + } + } + + photoView.frame = [self frameForThumbAtIndex:ix]; + } +} + +#pragma mark - Changing Selection + + +- (NSInteger)photoIndexAtPoint:(CGPoint)point { + NSInteger photoIndex; + + if (point.x <= 0) { + // Beyond the left edge + photoIndex = 0; + + } else if (point.x >= _containerView.bounds.size.width) { + // Beyond the right edge + photoIndex = (_numberOfPhotos - 1); + + } else { + // Somewhere in between + photoIndex = (NSInteger)(NICGFloatFloor((point.x / _containerView.bounds.size.width) * _numberOfPhotos) + + 0.5f); + } + + return photoIndex; +} + +- (void)updateSelectionWithPoint:(CGPoint)point { + NSInteger photoIndex = [self photoIndexAtPoint:point]; + + if (photoIndex != _selectedPhotoIndex) { + [self setSelectedPhotoIndex:photoIndex]; + + if ([self.delegate respondsToSelector:@selector(photoScrubberViewDidChangeSelection:)]) { + [self.delegate photoScrubberViewDidChangeSelection:self]; + } + } +} + +#pragma mark - UIResponder + + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { + [super touchesBegan:touches withEvent:event]; + + UITouch* touch = [touches anyObject]; + CGPoint touchPoint = [touch locationInView:_containerView]; + + [self updateSelectionWithPoint:touchPoint]; +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + [super touchesMoved:touches withEvent:event]; + + UITouch* touch = [touches anyObject]; + CGPoint touchPoint = [touch locationInView:_containerView]; + + [self updateSelectionWithPoint:touchPoint]; +} + +#pragma mark - Public + + +- (void)didLoadThumbnail: (UIImage *)image + atIndex: (NSInteger)photoIndex { + for (UIImageView* thumbView in _visiblePhotoViews) { + if (thumbView.tag == photoIndex) { + thumbView.image = image; + break; + } + } + + // Update the selected thumbnail if it's the one that just received a photo. + if (_selectedPhotoIndex == photoIndex) { + _selectionView.image = image; + } +} + +- (void)reloadData { + NIDASSERT(nil != _dataSource); + + // Remove any visible photos from the view before we release the sets. + for (UIView* photoView in _visiblePhotoViews) { + [photoView removeFromSuperview]; + } + + // If there is no data source then we can't do anything particularly interesting. + if (nil == _dataSource) { + return; + } + + _visiblePhotoViews = [[NSMutableArray alloc] init]; + _recycledPhotoViews = [[NSMutableSet alloc] init]; + + // Cache the number of photos. + _numberOfPhotos = [_dataSource numberOfPhotosInScrubberView:self]; + + [self setNeedsLayout]; + + // This will call layoutIfNeeded and layoutSubviews will then be called because we + // set the needsLayout flag. + [self updateVisiblePhotos]; +} + +- (void)setSelectedPhotoIndex:(NSInteger)photoIndex animated:(BOOL)animated { + if (_selectedPhotoIndex != photoIndex) { + // Don't animate the selection if it was previously invalid. + animated = animated && (_selectedPhotoIndex >= 0); + + _selectedPhotoIndex = photoIndex; + + if (animated) { + [UIView beginAnimations:nil context:nil]; + [UIView setAnimationDuration:0.2]; + [UIView setAnimationCurve:UIViewAnimationCurveEaseOut]; + [UIView setAnimationBeginsFromCurrentState:YES]; + } + + _selectionView.frame = [self frameForSelectionAtIndex:_selectedPhotoIndex]; + + if (animated) { + [UIView commitAnimations]; + } + + _selectionView.image = [self.dataSource photoScrubberView: self + thumbnailAtIndex: _selectedPhotoIndex]; + } +} + +- (void)setSelectedPhotoIndex:(NSInteger)photoIndex { + [self setSelectedPhotoIndex:photoIndex animated:NO]; +} + +@end diff --git a/Pods/Nimbus/src/photos/src/NIToolbarPhotoViewController.h b/Pods/Nimbus/src/photos/src/NIToolbarPhotoViewController.h new file mode 100644 index 0000000..051a43c --- /dev/null +++ b/Pods/Nimbus/src/photos/src/NIToolbarPhotoViewController.h @@ -0,0 +1,201 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +#import "NIPhotoAlbumScrollView.h" +#import "NIPhotoScrubberView.h" + +@class NIPhotoAlbumScrollView; + +/** + * A simple photo album view controller implementation with a toolbar. + * + * @ingroup NimbusPhotos + * + * This controller does not implement the photo album data source, it simply implements + * some of the most common UI elements that are associated with a photo viewer. + * + * For an example of implementing the data source, see the photos examples in the + * examples directory. + * + *

Implementing Delegate Methods

+ * + * This view controller already implements NIPhotoAlbumScrollViewDelegate. If you want to + * implement methods of this delegate you should take care to call the super implementation + * if necessary. The following methods have implementations in this class: + * + * - photoAlbumScrollViewDidScroll: + * - photoAlbumScrollView:didZoomIn: + * - photoAlbumScrollViewDidChangePages: + * + * + *

Recommended Configurations

+ * + *

Default: Zooming enabled with translucent toolbar

+ * + * The default settings are good for showing a photo album that takes up the entire screen. + * The photos will be visible beneath the toolbar because it is translucent. The chrome will + * be hidden whenever the user starts interacting with the photos. + * + * @code + * toolbarIsTranslucent = YES; + * hidesChromeWhenScrolling = YES; + * chromeCanBeHidden = YES; + * @endcode + * + *

Zooming disabled with opaque toolbar

+ * + * The following settings are good for viewing photo albums when you want to keep the chrome + * visible at all times without zooming enabled. + * + * @code + * toolbarIsTranslucent = NO; + * chromeCanBeHidden = NO; + * photoAlbumView.zoomingIsEnabled = NO; + * @endcode + */ +@interface NIToolbarPhotoViewController : UIViewController + +#pragma mark Configuring Functionality + +@property (nonatomic, assign, getter=isToolbarTranslucent) BOOL toolbarIsTranslucent; // default: yes +@property (nonatomic, assign) BOOL hidesChromeWhenScrolling; // default: yes +@property (nonatomic, assign) BOOL chromeCanBeHidden; // default: yes +@property (nonatomic, assign) BOOL animateMovingToNextAndPreviousPhotos; // default: no +@property (nonatomic, assign, getter=isScrubberEnabled) BOOL scrubberIsEnabled; // default: ipad yes - iphone no + +#pragma mark Views + +@property (nonatomic, readonly, strong) UIToolbar* toolbar; +@property (nonatomic, readonly, strong) NIPhotoAlbumScrollView* photoAlbumView; +@property (nonatomic, readonly, strong) NIPhotoScrubberView* photoScrubberView; +- (void)refreshChromeState; + +#pragma mark Toolbar Buttons + +@property (nonatomic, readonly, strong) UIBarButtonItem* nextButton; +@property (nonatomic, readonly, strong) UIBarButtonItem* previousButton; + +#pragma mark Subclassing + +- (void)setChromeVisibility:(BOOL)isVisible animated:(BOOL)animated; +- (void)setChromeTitle; + +@end + +/** @name Configuring Functionality */ + +/** + * Whether the toolbar is translucent and shows photos beneath it or not. + * + * If this is enabled, the toolbar will be translucent and the photo view will + * take up the entire view controller's bounds. + * + * If this is disabled, the photo will only occupy the remaining space above the + * toolbar. The toolbar will also not be hidden when the chrome is dismissed. This is by design + * because dismissing the toolbar when photos can't be displayed beneath it would leave + * an empty space below the album. + * + * By default this is YES. + * + * @fn NIToolbarPhotoViewController::toolbarIsTranslucent + */ + +/** + * Whether or not to hide the chrome when the user begins interacting with the photo. + * + * If this is enabled, then the chrome will be hidden when the user starts swiping from + * one photo to another. + * + * The chrome is the toolbar and the system status bar. + * + * By default this is YES. + * + * @attention This will be set to NO if toolbarCanBeHidden is set to NO. + * + * @fn NIToolbarPhotoViewController::hidesChromeWhenScrolling + */ + +/** + * Whether or not to allow hiding the chrome. + * + * If this is enabled then the user will be able to single-tap to dismiss or show the + * toolbar. + * + * The chrome is the toolbar and the system status bar. + * + * If this is disabled then the chrome will always be visible. + * + * By default this is YES. + * + * @attention Setting this to NO will also disable hidesToolbarWhenScrolling. + * + * @fn NIToolbarPhotoViewController::chromeCanBeHidden + */ + +/** + * Whether to animate moving to a next or previous photo when the user taps the button. + * + * By default this is NO. + * + * @fn NIToolbarPhotoViewController::animateMovingToNextAndPreviousPhotos + */ + +/** + * Whether to show a scrubber in the toolbar instead of next/previous buttons. + * + * By default this is YES on the iPad and NO on the iPhone. + * + * @fn NIToolbarPhotoViewController::scrubberIsEnabled + */ + + +/** @name Views */ + +/** + * The toolbar view. + * + * @fn NIToolbarPhotoViewController::toolbar + */ + +/** + * The photo album view. + * + * @fn NIToolbarPhotoViewController::photoAlbumView + */ + +/** + * The photo scrubber view. + * + * @fn NIToolbarPhotoViewController::photoScrubberView + */ + + +/** @name Toolbar Buttons */ + +/** + * The 'next' button. + * + * @fn NIToolbarPhotoViewController::nextButton + */ + +/** + * The 'previous' button. + * + * @fn NIToolbarPhotoViewController::previousButton + */ diff --git a/Pods/Nimbus/src/photos/src/NIToolbarPhotoViewController.m b/Pods/Nimbus/src/photos/src/NIToolbarPhotoViewController.m new file mode 100644 index 0000000..6ecf0e3 --- /dev/null +++ b/Pods/Nimbus/src/photos/src/NIToolbarPhotoViewController.m @@ -0,0 +1,533 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NIToolbarPhotoViewController.h" + +#import "NIPhotoAlbumScrollView.h" + +#import "NimbusCore.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "Nimbus requires ARC support." +#endif + +@implementation NIToolbarPhotoViewController { + // Views + UIToolbar* _toolbar; + NIPhotoAlbumScrollView* _photoAlbumView; + + // Scrubber View + NIPhotoScrubberView* _photoScrubberView; + + // Toolbar Buttons + UIBarButtonItem* _nextButton; + UIBarButtonItem* _previousButton; + + // Gestures + UITapGestureRecognizer* _tapGesture; + + // State + BOOL _isAnimatingChrome; + BOOL _isChromeHidden; + BOOL _prefersStatusBarHidden; + + // Configuration + BOOL _toolbarIsTranslucent; + BOOL _hidesChromeWhenScrolling; + BOOL _chromeCanBeHidden; + BOOL _animateMovingToNextAndPreviousPhotos; + BOOL _scrubberIsEnabled; +} + + +- (void)shutdown_NIToolbarPhotoViewController { + _toolbar = nil; + _photoAlbumView = nil; + _nextButton = nil; + _previousButton = nil; + _photoScrubberView = nil; + _tapGesture = nil; +} + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) { + // Default Configuration Settings + self.toolbarIsTranslucent = YES; + self.hidesChromeWhenScrolling = YES; + self.chromeCanBeHidden = YES; + self.animateMovingToNextAndPreviousPhotos = NO; + if ([self respondsToSelector:@selector(setAutomaticallyAdjustsScrollViewInsets:)]) { + self.automaticallyAdjustsScrollViewInsets = NO; + } + + // The scrubber is better use of the extra real estate on the iPad. + // If you ask me, though, the scrubber works pretty well on the iPhone too. It's up + // to you if you want to use it in your own implementations. + self.scrubberIsEnabled = NIIsPad(); + + // Allow the photos to display beneath the status bar. + self.wantsFullScreenLayout = YES; + } + return self; +} + +- (void)addTapGestureToView { + if ([self isViewLoaded]) { + if (nil == _tapGesture) { + _tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTap)]; + [self.photoAlbumView addGestureRecognizer:_tapGesture]; + } + } + + _tapGesture.enabled = YES; +} + +- (void)updateToolbarItems { + UIBarItem* flexibleSpace = + [[UIBarButtonItem alloc] initWithBarButtonSystemItem: UIBarButtonSystemItemFlexibleSpace + target: nil + action: nil]; + + if ([self isScrubberEnabled]) { + _nextButton = nil; + _previousButton = nil; + + if (nil == _photoScrubberView) { + CGRect scrubberFrame = CGRectMake(0, 0, + self.toolbar.bounds.size.width, + self.toolbar.bounds.size.height); + _photoScrubberView = [[NIPhotoScrubberView alloc] initWithFrame:scrubberFrame]; + _photoScrubberView.autoresizingMask = (UIViewAutoresizingFlexibleWidth + | UIViewAutoresizingFlexibleHeight); + _photoScrubberView.delegate = self; + } + + UIBarButtonItem* scrubberItem = + [[UIBarButtonItem alloc] initWithCustomView:self.photoScrubberView]; + self.toolbar.items = [NSArray arrayWithObjects: + flexibleSpace, scrubberItem, flexibleSpace, + nil]; + + [_photoScrubberView setSelectedPhotoIndex:self.photoAlbumView.centerPageIndex]; + + } else { + _photoScrubberView = nil; + + if (nil == _nextButton) { + UIImage* nextIcon = [UIImage imageWithContentsOfFile: + NIPathForBundleResource(nil, @"NimbusPhotos.bundle/gfx/next.png")]; + + // We weren't able to find the next or previous icons in your application's resources. + // Ensure that you've dragged the NimbusPhotos.bundle from src/photos/resources into your + // application with the "Create Folder References" option selected. You can verify that + // you've done this correctly by expanding the NimbusPhotos.bundle file in your project + // and verifying that the 'gfx' directory is blue. Also verify that the bundle is being + // copied in the Copy Bundle Resources phase. + NIDASSERT(nil != nextIcon); + + _nextButton = [[UIBarButtonItem alloc] initWithImage: nextIcon + style: UIBarButtonItemStylePlain + target: self + action: @selector(didTapNextButton)]; + + } + + if (nil == _previousButton) { + UIImage* previousIcon = [UIImage imageWithContentsOfFile: + NIPathForBundleResource(nil, @"NimbusPhotos.bundle/gfx/previous.png")]; + + // We weren't able to find the next or previous icons in your application's resources. + // Ensure that you've dragged the NimbusPhotos.bundle from src/photos/resources into your + // application with the "Create Folder References" option selected. You can verify that + // you've done this correctly by expanding the NimbusPhotos.bundle file in your project + // and verifying that the 'gfx' directory is blue. Also verify that the bundle is being + // copied in the Copy Bundle Resources phase. + NIDASSERT(nil != previousIcon); + + _previousButton = [[UIBarButtonItem alloc] initWithImage: previousIcon + style: UIBarButtonItemStylePlain + target: self + action: @selector(didTapPreviousButton)]; + } + + self.toolbar.items = [NSArray arrayWithObjects: + flexibleSpace, self.previousButton, + flexibleSpace, self.nextButton, + flexibleSpace, + nil]; + } + +} + +- (void)loadView { + [super loadView]; + + self.view.backgroundColor = [UIColor blackColor]; + + CGRect bounds = self.view.bounds; + + // Toolbar Setup + + CGFloat toolbarHeight = NIToolbarHeightForOrientation(NIInterfaceOrientation()); + CGRect toolbarFrame = CGRectMake(0, bounds.size.height - toolbarHeight, + bounds.size.width, toolbarHeight); + + _toolbar = [[UIToolbar alloc] initWithFrame:toolbarFrame]; + _toolbar.barStyle = UIBarStyleBlack; + _toolbar.translucent = self.toolbarIsTranslucent; + _toolbar.autoresizingMask = (UIViewAutoresizingFlexibleWidth + | UIViewAutoresizingFlexibleTopMargin); + + [self updateToolbarItems]; + + // Photo Album View Setup + + CGRect photoAlbumFrame = bounds; + if (!self.toolbarIsTranslucent) { + photoAlbumFrame = NIRectContract(bounds, 0, toolbarHeight); + } + _photoAlbumView = [[NIPhotoAlbumScrollView alloc] initWithFrame:photoAlbumFrame]; + _photoAlbumView.autoresizingMask = (UIViewAutoresizingFlexibleWidth + | UIViewAutoresizingFlexibleHeight); + _photoAlbumView.delegate = self; + + [self.view addSubview:_photoAlbumView]; + [self.view addSubview:_toolbar]; + + if (self.hidesChromeWhenScrolling || self.chromeCanBeHidden) { + [self addTapGestureToView]; + } +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + [[UIApplication sharedApplication] setStatusBarStyle: (NIIsPad() + ? UIStatusBarStyleBlackOpaque + : UIStatusBarStyleBlackTranslucent) + animated: animated]; + + UINavigationBar* navBar = self.navigationController.navigationBar; + navBar.barStyle = UIBarStyleBlack; + navBar.translucent = self.toolbarIsTranslucent; + + _previousButton.enabled = [self.photoAlbumView hasPrevious]; + _nextButton.enabled = [self.photoAlbumView hasNext]; +} + +#if __IPHONE_OS_VERSION_MIN_REQUIRED < NIIOS_6_0 +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation { + return NIIsSupportedOrientation(toInterfaceOrientation); +} +#endif + + +- (void)willRotateToInterfaceOrientation: (UIInterfaceOrientation)toInterfaceOrientation + duration: (NSTimeInterval)duration { + [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; + + [self.photoAlbumView willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; +} + +- (void)willAnimateRotationToInterfaceOrientation: (UIInterfaceOrientation)toInterfaceOrientation + duration: (NSTimeInterval)duration { + [self.photoAlbumView willAnimateRotationToInterfaceOrientation: toInterfaceOrientation + duration: duration]; + + [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation + duration:duration]; + + CGRect toolbarFrame = self.toolbar.frame; + toolbarFrame.size.height = NIToolbarHeightForOrientation(toInterfaceOrientation); + toolbarFrame.origin.y = self.view.bounds.size.height - toolbarFrame.size.height; + self.toolbar.frame = toolbarFrame; + + if (!self.toolbarIsTranslucent) { + CGRect photoAlbumFrame = self.photoAlbumView.frame; + photoAlbumFrame.size.height = self.view.bounds.size.height - toolbarFrame.size.height; + self.photoAlbumView.frame = photoAlbumFrame; + } +} + +- (UIView *)rotatingFooterView { + return self.toolbar.hidden ? nil : self.toolbar; +} + +- (void)didHideChrome { + _isAnimatingChrome = NO; + if (self.toolbarIsTranslucent) { + self.toolbar.hidden = YES; + } + + [self.navigationController setNavigationBarHidden:YES animated:NO]; + _isChromeHidden = YES; +} + +- (void)didShowChrome { + _isAnimatingChrome = NO; + + _isChromeHidden = NO; +} + +- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation { + return UIStatusBarAnimationSlide; +} + +- (BOOL)prefersStatusBarHidden { + return _prefersStatusBarHidden; +} + +- (void)setChromeVisibility:(BOOL)isVisible animated:(BOOL)animated { + if (_isAnimatingChrome + || (!isVisible && _isChromeHidden) + || (isVisible && !_isChromeHidden) + || !self.chromeCanBeHidden) { + // Nothing to do here. + return; + } + + CGRect toolbarFrame = self.toolbar.frame; + CGRect bounds = self.view.bounds; + + if (self.toolbarIsTranslucent) { + // Reset the toolbar's initial position. + if (!isVisible) { + toolbarFrame.origin.y = bounds.size.height - toolbarFrame.size.height; + + } else { + // Ensure that the toolbar is visible through the animation. + self.toolbar.hidden = NO; + + toolbarFrame.origin.y = bounds.size.height; + } + self.toolbar.frame = toolbarFrame; + } + + // Show/hide the system chrome. + BOOL isStatusBarAppearanceSupported = [self respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]; + if (!isStatusBarAppearanceSupported) { + [[UIApplication sharedApplication] setStatusBarHidden:!isVisible + withAnimation:(animated + ? UIStatusBarAnimationSlide + : UIStatusBarAnimationNone)]; + } + + if (self.toolbarIsTranslucent) { + // Place the toolbar at its final location. + if (isVisible) { + // Slide up. + toolbarFrame.origin.y = bounds.size.height - toolbarFrame.size.height; + + } else { + // Slide down. + toolbarFrame.origin.y = bounds.size.height; + } + } + + // If there is a navigation bar, place it at its final location. + CGRect navigationBarFrame = self.navigationController.navigationBar.frame; + + if (animated) { + [UIView beginAnimations:nil context:nil]; + [UIView setAnimationDelegate:self]; + [UIView setAnimationDidStopSelector:(isVisible + ? @selector(didShowChrome) + : @selector(didHideChrome))]; + + // Ensure that the animation matches the status bar's. + [UIView setAnimationDuration:NIStatusBarAnimationDuration()]; + [UIView setAnimationCurve:NIStatusBarAnimationCurve()]; + } + + if (isStatusBarAppearanceSupported) { + _prefersStatusBarHidden = !isVisible; + [self setNeedsStatusBarAppearanceUpdate]; + } + + if (nil != self.navigationController.navigationBar) { + if (isVisible) { + [UIView setAnimationsEnabled:NO]; + [self.navigationController setNavigationBarHidden:NO animated:NO]; + navigationBarFrame.origin.y = 0; + self.navigationController.navigationBar.frame = navigationBarFrame; + self.navigationController.navigationBar.alpha = 0; + [UIView setAnimationsEnabled:YES]; + + navigationBarFrame.origin.y = NIStatusBarHeight(); + + } else { + navigationBarFrame.origin.y = 0; + } + } + + if (self.toolbarIsTranslucent) { + self.toolbar.frame = toolbarFrame; + } + if (nil != self.navigationController.navigationBar) { + self.navigationController.navigationBar.frame = navigationBarFrame; + self.navigationController.navigationBar.alpha = (isVisible ? 1 : 0); + } + + if (animated) { + _isAnimatingChrome = YES; + [UIView commitAnimations]; + + } else if (!isVisible) { + [self didHideChrome]; + + } else if (isVisible) { + [self didShowChrome]; + } +} + +- (void)toggleChromeVisibility { + [self setChromeVisibility:(_isChromeHidden || _isAnimatingChrome) animated:YES]; +} + +#pragma mark - UIGestureRecognizer + + +- (void)didTap { + SEL selector = @selector(toggleChromeVisibility); + if (self.photoAlbumView.zoomingIsEnabled) { + // Cancel any previous delayed performs so that we don't stack them. + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:selector object:nil]; + + // We need to delay taking action on the first tap in case a second tap comes in, causing + // a double-tap gesture to be recognized and the photo to be zoomed. + [self performSelector: selector + withObject: nil + afterDelay: 0.3]; + + } else { + // When zooming is disabled, double-tap-to-zoom is also disabled so we don't have to + // be as careful; just toggle the chrome immediately. + [self toggleChromeVisibility]; + } +} + +- (void)refreshChromeState { + self.previousButton.enabled = [self.photoAlbumView hasPrevious]; + self.nextButton.enabled = [self.photoAlbumView hasNext]; + + [self setChromeTitle]; +} + +- (void)setChromeTitle { + self.title = [NSString stringWithFormat:@"%zd of %zd", + (self.photoAlbumView.centerPageIndex + 1), + self.photoAlbumView.numberOfPages]; +} + +#pragma mark - NIPhotoAlbumScrollViewDelegate + + +- (void)pagingScrollViewDidScroll:(NIPagingScrollView *)pagingScrollView { + if (self.hidesChromeWhenScrolling) { + [self setChromeVisibility:NO animated:YES]; + } +} + +- (void)photoAlbumScrollView: (NIPhotoAlbumScrollView *)photoAlbumScrollView + didZoomIn: (BOOL)didZoomIn { + // This delegate method is called after a double-tap gesture, so cancel any pending + // single-tap gestures. + [NSObject cancelPreviousPerformRequestsWithTarget: self + selector: @selector(toggleChromeVisibility) + object: nil]; +} + +- (void)pagingScrollViewDidChangePages:(NIPagingScrollView *)pagingScrollView { + // We animate the scrubber when the chrome won't disappear as a nice touch. + // We don't bother animating if the chrome disappears when scrolling because the user + // will barely see the animation happen. + [self.photoScrubberView setSelectedPhotoIndex: [pagingScrollView centerPageIndex] + animated: !self.hidesChromeWhenScrolling]; + + [self refreshChromeState]; +} + +#pragma mark - NIPhotoScrubberViewDelegate + + +- (void)photoScrubberViewDidChangeSelection:(NIPhotoScrubberView *)photoScrubberView { + [self.photoAlbumView moveToPageAtIndex:photoScrubberView.selectedPhotoIndex animated:NO]; + + [self refreshChromeState]; +} + +#pragma mark - Actions + + +- (void)didTapNextButton { + [self.photoAlbumView moveToNextAnimated:self.animateMovingToNextAndPreviousPhotos]; + + [self refreshChromeState]; +} + +- (void)didTapPreviousButton { + [self.photoAlbumView moveToPreviousAnimated:self.animateMovingToNextAndPreviousPhotos]; + + [self refreshChromeState]; +} + +#pragma mark - Public + + +- (void)settoolbarIsTranslucent:(BOOL)enabled { + _toolbarIsTranslucent = enabled; + + self.toolbar.translucent = enabled; +} + +- (void)setHidesChromeWhenScrolling:(BOOL)hidesToolbar { + _hidesChromeWhenScrolling = hidesToolbar; + + if (hidesToolbar) { + [self addTapGestureToView]; + + } else { + [_tapGesture setEnabled:_chromeCanBeHidden]; + } +} + +- (void)setChromeCanBeHidden:(BOOL)canBeHidden { + _chromeCanBeHidden = canBeHidden; + + if (canBeHidden) { + [self addTapGestureToView]; + + } else { + self.hidesChromeWhenScrolling = NO; + + if ([self isViewLoaded]) { + // Ensure that the chrome is visible. + [self setChromeVisibility:YES animated:NO]; + } + } +} + +- (void)setScrubberIsEnabled:(BOOL)enabled { + if (_scrubberIsEnabled != enabled) { + _scrubberIsEnabled = enabled; + + if ([self isViewLoaded]) { + [self updateToolbarItems]; + } + } +} + +@end diff --git a/Pods/Nimbus/src/photos/src/NimbusPhotos.h b/Pods/Nimbus/src/photos/src/NimbusPhotos.h new file mode 100644 index 0000000..37569ac --- /dev/null +++ b/Pods/Nimbus/src/photos/src/NimbusPhotos.h @@ -0,0 +1,122 @@ +// +// Copyright 2011-2014 NimbusKit +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +/** + * @defgroup NimbusPhotos Nimbus Photos + * @{ + * + *
+ * + * Photo viewers are a common, non-trivial feature in many types of iOS apps ranging from + * simple photo viewers to apps that fetch photos from an API. The Nimbus photo album viewer + * is designed to consume minimal amounts of memory and encourage the use of threads to provide + * a high quality user experience that doesn't include any blocking of the UI while images + * are loaded from disk or the network. The photo viewer pre-caches images in an album to either + * side of the current image so that the user will ideally always have a high-quality + * photo experience. + * + *

Adding the Photos Feature to Your Application

+ * + * The Nimbus Photos feature uses a small number of custom photos that are stored in the + * NimbusPhotos bundle. You must add this bundle to your application, ensuring that you select + * the "Create Folder References" option and that the bundle is copied in the + * "Copy Bundle Resources" phase. + * + * The bundle can be found at src/photos/resources/NimbusPhotos.bundle. + * + * + *

Feature Breakdown

+ * + * NIPhotoAlbumScrollView - A paged scroll view that implements a data source similar to that + * of UITableView. This scroll view consumes minimal amounts of memory and is built to be fast + * and responsive. + * + * NIPhotoScrollView - A single page within the NIPhotoAlbumScrollView. This view implements + * the zooming and rotation functionality for a photo. + * + * NIPhotoScrubberView - A scrubber view for skimming through a set of photos. This view + * made its debut by Apple on the iPad in Photos.app. Nimbus' implementation of this view + * is built to be responsive and consume little memory. + * + * NIToolbarPhotoViewController - A skeleton implementation of a view controller that includes + * multiple configurable properties. This controller will show a scrubber on the iPad and + * next/previous arrows on the iPhone. It also provides support for hiding and showing the + * app's chrome. If you wish to use this controller you must simply implement the data source + * for the photo album. NetworkPhotoAlbum in the examples/photos directory demos building + * a data source that fetches its information from the network. + * + * + *

Architecture

+ * + * The architectural design of the photo album view takes inspiration from UITableView. Images + * are requested only when they might become visible and are released when they become + * inaccessible again. Each page of the photo album view is a recycled NIPhotoScrollView. + * These page views handle zooming and panning within a given photo. The photo album view + * NIPhotoAlbumScrollView contains a paging scroll view of these page views and provides + * interfaces for maintaining the orientation during rotations. + * + * The view controller NIToolbarPhotoViewController is provided as a basic implementation of + * functionality that is expected from a photo viewer. This includes: a toolbar with next and + * previous arrows; auto-rotation support; and toggling the chrome. + * + *

NIPhotoAlbumScrollView

+ * + * NIPhotoAlbumScrollView is the meat of the Nimbus photo viewer's functionality. Contained + * within this view are pages of NIPhotoScrollView views. In your view controller you are + * expected to implement the NIPhotoAlbumScrollViewDataSource in order to provide the photo + * album view with the necessary information for presenting an album. + * + * + *

Example Applications

+ * + *

Network Photo Albums

+ * + * View the README on GitHub + * + * This sample application demos the use of the multiple photo APIs to fetch photos from public + * photo album and display them in high-definition on the iPad and iPhone. + * + * The following APIs are currently demoed: + * + * - Facebook Graph API + * - Dribbble Shots + * + * Sample location: examples/photos/NetworkPhotoAlbums + * + * + *

Screenshots

+ * + * @image html photos-iphone-example1.png "Screenshot of a basic photo album on the iPhone." + * + * Image source: flickr.com/photos/janekm/360669001 + */ + +/**@}*/ + +#import +#import + +#import "NIPhotoAlbumScrollView.h" +#import "NIPhotoAlbumScrollViewDataSource.h" +#import "NIPhotoAlbumScrollViewDelegate.h" +#import "NIPhotoScrollView.h" +#import "NIPhotoScrollViewDelegate.h" +#import "NIPhotoScrollViewPhotoSize.h" +#import "NIPhotoScrubberView.h" +#import "NIToolbarPhotoViewController.h" + +#import "NimbusPagingScrollView.h" +#import "NimbusCore.h"