├── FishEyeDemo
├── en.lproj
│ └── InfoPlist.strings
├── MCSViewController.h
├── MCSDemoXibFishEyeViewItem.m
├── MCSAppDelegate.h
├── MCSDemoXibFishEyeViewItem.h
├── FishEyeDemo-Prefix.pch
├── main.m
├── MCSDemoFishEyeItem.h
├── MCSAppDelegate.m
├── FishEyeDemo-Info.plist
├── MCSDemoFishEyeItem.m
├── MCSViewController.m
├── MCSDemoXibFishEyeViewItem.xib
└── MCSViewController.xib
├── Screens
├── collapsed.png
├── fishEye.gif
├── selected.png
└── highlighted.png
├── FishEyeDemo.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── FishEyeDemo.xccheckout
└── project.pbxproj
├── LICENSE
├── MCSFishEyeView.podspec
├── MCSFishEyeView
├── MCSFishEyeViewItem.m
├── MCSFishEyeViewItem.h
├── MCSFishEyeView.h
└── MCSFishEyeView.m
└── README.md
/FishEyeDemo/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/Screens/collapsed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macoscope/MCSFishEye/HEAD/Screens/collapsed.png
--------------------------------------------------------------------------------
/Screens/fishEye.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macoscope/MCSFishEye/HEAD/Screens/fishEye.gif
--------------------------------------------------------------------------------
/Screens/selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macoscope/MCSFishEye/HEAD/Screens/selected.png
--------------------------------------------------------------------------------
/Screens/highlighted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/macoscope/MCSFishEye/HEAD/Screens/highlighted.png
--------------------------------------------------------------------------------
/FishEyeDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/FishEyeDemo/MCSViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MCSViewController.h
3 | // FishEyeDemo
4 | //
5 | // Created by Bartosz Ciechanowski on 8/30/13.
6 | // Copyright (c) 2013 Macoscope. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface MCSViewController : UIViewController
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/FishEyeDemo/MCSDemoXibFishEyeViewItem.m:
--------------------------------------------------------------------------------
1 | //
2 | // MCSDemoXibFishEyeViewItem.m
3 | // FishEyeDemo
4 | //
5 | // Created by Bartosz Ciechanowski on 9/3/13.
6 | // Copyright (c) 2013 Macoscope. All rights reserved.
7 | //
8 |
9 | #import "MCSDemoXibFishEyeViewItem.h"
10 |
11 | @implementation MCSDemoXibFishEyeViewItem
12 |
13 |
14 | @end
15 |
--------------------------------------------------------------------------------
/FishEyeDemo/MCSAppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // MCSAppDelegate.h
3 | // FishEyeDemo
4 | //
5 | // Created by Bartosz Ciechanowski on 8/29/13.
6 | // Copyright (c) 2013 Macoscope. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface MCSAppDelegate : UIResponder
12 |
13 | @property (strong, nonatomic) UIWindow *window;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/FishEyeDemo/MCSDemoXibFishEyeViewItem.h:
--------------------------------------------------------------------------------
1 | //
2 | // MCSDemoXibFishEyeViewItem.h
3 | // FishEyeDemo
4 | //
5 | // Created by Bartosz Ciechanowski on 9/3/13.
6 | // Copyright (c) 2013 Macoscope. All rights reserved.
7 | //
8 |
9 | #import "MCSFishEyeViewItem.h"
10 |
11 | @interface MCSDemoXibFishEyeViewItem : MCSFishEyeViewItem
12 |
13 | @property (weak, nonatomic) IBOutlet UILabel *label;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/FishEyeDemo/FishEyeDemo-Prefix.pch:
--------------------------------------------------------------------------------
1 | //
2 | // Prefix header
3 | //
4 | // The contents of this file are implicitly included at the beginning of every source file.
5 | //
6 |
7 | #import
8 |
9 | #ifndef __IPHONE_5_0
10 | # warning "This project uses features only available in iOS SDK 5.0 and later."
11 | #endif
12 |
13 | #ifdef __OBJC__
14 | # import
15 | # import
16 | #endif
17 |
--------------------------------------------------------------------------------
/FishEyeDemo/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // FishEyeDemo
4 | //
5 | // Created by Bartosz Ciechanowski on 8/29/13.
6 | // Copyright (c) 2013 Macoscope. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #import "MCSAppDelegate.h"
12 |
13 | int main(int argc, char * argv[])
14 | {
15 | @autoreleasepool {
16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([MCSAppDelegate class]));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/FishEyeDemo/MCSDemoFishEyeItem.h:
--------------------------------------------------------------------------------
1 | //
2 | // MCSDemoFishEyeItem.h
3 | // FishEyeDemo
4 | //
5 | // Created by Bartosz Ciechanowski on 8/30/13.
6 | // Copyright (c) 2013 Macoscope. All rights reserved.
7 | //
8 |
9 | #import "MCSFishEyeViewItem.h"
10 |
11 | @interface MCSDemoFishEyeItem : MCSFishEyeViewItem
12 |
13 | @property (nonatomic, strong, readonly) UIView *backgroundView;
14 | @property (nonatomic, strong, readonly) UILabel *label;
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2013 Macoscope, sp z o.o.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4 | this file except in compliance with the License. You may obtain a copy of the
5 | License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software distributed
10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the
12 | specific language governing permissions and limitations under the License.
--------------------------------------------------------------------------------
/MCSFishEyeView.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "MCSFishEyeView"
3 | s.version = "1.0"
4 | s.summary = "The fisheye from Bubble Browser for iPad."
5 | s.homepage = "https://github.com/macoscope/MCSFishEyeView"
6 | s.license = { :type => 'MIT', :file => 'LICENSE.txt' }
7 | s.author = { "Bartosz Ciechanowski" => "ciechan@gmail.com" }
8 | s.source = { :git => "https://github.com/macoscope/MCSFishEyeView.git", :tag => "1.0" }
9 | s.platform = :ios
10 | s.source_files = 'FishEyeView/*.{h,m}'
11 | s.requires_arc = true
12 | s.frameworks = 'QuartzCore', 'CoreGraphics'
13 | s.ios.deployment_target = '5.0'
14 | end
15 |
--------------------------------------------------------------------------------
/FishEyeDemo/MCSAppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // MCSAppDelegate.m
3 | // FishEyeDemo
4 | //
5 | // Created by Bartosz Ciechanowski on 8/29/13.
6 | // Copyright (c) 2013 Macoscope. All rights reserved.
7 | //
8 |
9 | #import "MCSAppDelegate.h"
10 | #import "MCSViewController.h"
11 |
12 | @implementation MCSAppDelegate
13 |
14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
15 | {
16 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
17 |
18 | MCSViewController *viewController = [[MCSViewController alloc] init];
19 | [self.window setRootViewController:viewController];
20 | [self.window makeKeyAndVisible];
21 |
22 | return YES;
23 | }
24 |
25 | @end
26 |
--------------------------------------------------------------------------------
/MCSFishEyeView/MCSFishEyeViewItem.m:
--------------------------------------------------------------------------------
1 | //
2 | // MCSFishEyeViewItem.m
3 | //
4 | // Created by Bartosz Ciechanowski on 8/29/13.
5 | // Copyright (c) 2013 Macoscope. All rights reserved.
6 | //
7 |
8 | #import "MCSFishEyeViewItem.h"
9 |
10 | @implementation MCSFishEyeViewItem
11 |
12 |
13 | - (void)setSelected:(BOOL)selected
14 | {
15 | [self setSelected:selected animated:NO];
16 | }
17 |
18 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated
19 | {
20 | _selected = selected;
21 | }
22 |
23 | - (void)setHighlighted:(BOOL)highlighted
24 | {
25 | [self setHighlighted:highlighted animated:NO];
26 | }
27 |
28 | - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
29 | {
30 | _highlighted = highlighted;
31 | }
32 |
33 | @end
34 |
--------------------------------------------------------------------------------
/MCSFishEyeView/MCSFishEyeViewItem.h:
--------------------------------------------------------------------------------
1 | //
2 | // MCSFishEyeViewItem.h
3 | //
4 | // Created by Bartosz Ciechanowski on 8/29/13.
5 | // Copyright (c) 2013 Macoscope. All rights reserved.
6 | //
7 |
8 | #import
9 |
10 | // for the sake of pre iOS7 SDK compatibility
11 | #ifndef NS_REQUIRES_SUPER
12 | #if __has_attribute(objc_requires_super)
13 | #define NS_REQUIRES_SUPER __attribute__((objc_requires_super))
14 | #else
15 | #define NS_REQUIRES_SUPER
16 | #endif
17 | #endif
18 |
19 |
20 | @interface MCSFishEyeViewItem : UIView
21 |
22 | @property (nonatomic, getter=isSelected) BOOL selected;
23 | @property (nonatomic, getter=isHighlighted) BOOL highlighted;
24 |
25 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated NS_REQUIRES_SUPER;
26 | - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated NS_REQUIRES_SUPER;
27 |
28 | @end
29 |
--------------------------------------------------------------------------------
/FishEyeDemo/FishEyeDemo-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | ${PRODUCT_NAME}
9 | CFBundleExecutable
10 | ${EXECUTABLE_NAME}
11 | CFBundleIdentifier
12 | net.macoscope.${PRODUCT_NAME:rfc1034identifier}
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | ${PRODUCT_NAME}
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1.0
25 | LSRequiresIPhoneOS
26 |
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/FishEyeDemo.xcodeproj/project.xcworkspace/xcshareddata/FishEyeDemo.xccheckout:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDESourceControlProjectFavoriteDictionaryKey
6 |
7 | IDESourceControlProjectIdentifier
8 | B31253C6-69A2-4987-9052-C92B4E1A6BA8
9 | IDESourceControlProjectName
10 | FishEyeDemo
11 | IDESourceControlProjectOriginsDictionary
12 |
13 | A5EB103F-4984-443B-A8A2-702CD44C6DB9
14 | ssh://github.com/macoscope/MCSFishEye.git
15 |
16 | IDESourceControlProjectPath
17 | FishEyeDemo.xcodeproj/project.xcworkspace
18 | IDESourceControlProjectRelativeInstallPathDictionary
19 |
20 | A5EB103F-4984-443B-A8A2-702CD44C6DB9
21 | ../..
22 |
23 | IDESourceControlProjectURL
24 | ssh://github.com/macoscope/MCSFishEye.git
25 | IDESourceControlProjectVersion
26 | 110
27 | IDESourceControlProjectWCCIdentifier
28 | A5EB103F-4984-443B-A8A2-702CD44C6DB9
29 | IDESourceControlProjectWCConfigurations
30 |
31 |
32 | IDESourceControlRepositoryExtensionIdentifierKey
33 | public.vcs.git
34 | IDESourceControlWCCIdentifierKey
35 | A5EB103F-4984-443B-A8A2-702CD44C6DB9
36 | IDESourceControlWCCName
37 | MCSFishEye
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/FishEyeDemo/MCSDemoFishEyeItem.m:
--------------------------------------------------------------------------------
1 | //
2 | // MCSDemoFishEyeItem.m
3 | // FishEyeDemo
4 | //
5 | // Created by Bartosz Ciechanowski on 8/30/13.
6 | // Copyright (c) 2013 Macoscope. All rights reserved.
7 | //
8 |
9 | #import "MCSDemoFishEyeItem.h"
10 | #import
11 |
12 | @interface MCSDemoFishEyeItem()
13 | @end
14 |
15 | @implementation MCSDemoFishEyeItem
16 |
17 | - (id)initWithFrame:(CGRect)frame
18 | {
19 | self = [super initWithFrame:frame];
20 | if (self) {
21 | _backgroundView = [[UIView alloc] initWithFrame:self.bounds];
22 | _backgroundView.layer.cornerRadius = 24.0f;
23 |
24 | _label = [[UILabel alloc] initWithFrame:self.bounds];
25 | _label.textAlignment = NSTextAlignmentCenter;
26 | _label.font = [UIFont systemFontOfSize:40.0f];
27 | _label.backgroundColor = [UIColor clearColor];
28 |
29 | [self addSubview:_backgroundView];
30 | [self addSubview:_label];
31 | }
32 | return self;
33 | }
34 |
35 | - (void)layoutSubviews
36 | {
37 | [super layoutSubviews];
38 | _backgroundView.frame = CGRectInset(self.bounds, 5.0, 5.0);
39 | _label.frame = CGRectInset(self.bounds, 5.0, 5.0);
40 |
41 | }
42 |
43 | - (UIColor *)selectedColor
44 | {
45 | return [UIColor colorWithRed:192.0f/255.0f green:41.0f/255.0f blue:66.0f/255.0f alpha:1.0];
46 | }
47 |
48 | - (UIColor *)highlightedColor
49 | {
50 | return [UIColor colorWithRed:217.0f/255.0f green:91.0f/255.0f blue:67.0f/255.0f alpha:1.0];
51 | }
52 |
53 | - (UIColor *)defaultColor
54 | {
55 | return [UIColor colorWithRed:236.0f/255.0f green:208.0f/255.0f blue:120.0f/255.0f alpha:1.0];
56 | }
57 |
58 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated
59 | {
60 | [super setSelected:selected animated:animated];
61 |
62 | [UIView animateWithDuration:animated ? 0.2 : 0.0 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
63 | if (selected) {
64 | self.backgroundView.backgroundColor = [self selectedColor];
65 | self.label.textColor = [UIColor colorWithWhite:1.0 alpha:1.0];
66 | } else {
67 | self.backgroundView.backgroundColor = [self defaultColor];
68 | self.label.textColor = [UIColor colorWithWhite:0.2 alpha:0.7];
69 | }
70 | } completion:NULL];
71 | }
72 |
73 | - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
74 | {
75 | [super setHighlighted:highlighted animated:animated];
76 |
77 | [UIView animateWithDuration:animated ? 0.2 : 0.0 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
78 | if (highlighted) {
79 | self.backgroundView.backgroundColor = [self highlightedColor];
80 | self.label.textColor = [UIColor colorWithWhite:1.0 alpha:1.0];
81 | } else {
82 | self.backgroundView.backgroundColor = [self defaultColor];
83 | self.label.textColor = [UIColor colorWithWhite:0.2 alpha:0.7];
84 | }
85 | } completion:NULL];
86 |
87 | }
88 |
89 | @end
90 |
--------------------------------------------------------------------------------
/FishEyeDemo/MCSViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MCSViewController.m
3 | // FishEyeDemo
4 | //
5 | // Created by Bartosz Ciechanowski on 8/30/13.
6 | // Copyright (c) 2013 Macoscope. All rights reserved.
7 | //
8 |
9 | #import "MCSViewController.h"
10 | #import "MCSFishEyeView.h"
11 | #import "MCSDemoFishEyeItem.h"
12 |
13 | @interface MCSViewController ()
14 |
15 | @property (weak, nonatomic) IBOutlet MCSFishEyeView *leftFishEyeView;
16 | @property (weak, nonatomic) IBOutlet MCSFishEyeView *topFishEyeView;
17 | @property (weak, nonatomic) IBOutlet MCSFishEyeView *rightFishEyeView;
18 | @property (weak, nonatomic) IBOutlet MCSFishEyeView *bottomFishEyeView;
19 |
20 | @property (strong, nonatomic) IBOutletCollection(MCSFishEyeView) NSArray *fishEyeViews;
21 |
22 | @end
23 |
24 | @implementation MCSViewController
25 |
26 | - (void)viewDidLoad
27 | {
28 | [super viewDidLoad];
29 |
30 | self.leftFishEyeView.itemSize = CGSizeMake(120.0, 120.0);
31 | self.leftFishEyeView.contentInset = UIEdgeInsetsMake(20.0, 5.0, 20.0, 0.0);
32 | [self.leftFishEyeView registerItemClass:[MCSDemoFishEyeItem class]];
33 |
34 | self.rightFishEyeView.itemSize = CGSizeMake(130.0, 130.0);
35 | self.rightFishEyeView.contentInset = UIEdgeInsetsMake(20.0, 0.0, 20.0, 10.0);
36 | self.rightFishEyeView.expansionDirection = MCSFishEyeExpansionDirectionLeft;
37 | self.rightFishEyeView.selectedItemOffset = 80.0f;
38 | [self.rightFishEyeView registerItemNib:[UINib nibWithNibName:@"MCSDemoXibFishEyeViewItem" bundle:nil]];
39 |
40 | self.topFishEyeView.itemSize = CGSizeMake(70.0, 70.0);
41 | self.topFishEyeView.expansionDirection = MCSFishEyeExpansionDirectionBottom;
42 | self.topFishEyeView.contentInset = UIEdgeInsetsMake(14.0, 0.0, 0.0, 0.0);
43 | [self.topFishEyeView registerItemClass:[MCSDemoFishEyeItem class]];
44 |
45 | self.bottomFishEyeView.itemSize = CGSizeMake(90.0, 90.0);
46 | self.bottomFishEyeView.contentInset = UIEdgeInsetsMake(0.0, 40.0, 0.0, 40.0);
47 | self.bottomFishEyeView.expansionDirection = MCSFishEyeExpansionDirectionTop;
48 | self.bottomFishEyeView.selectedItemOffset = 40.0f;
49 | [self.bottomFishEyeView registerItemClass:[MCSDemoFishEyeItem class]];
50 |
51 | for (MCSFishEyeView *fishEye in self.fishEyeViews) {
52 | fishEye.dataSource = self;
53 | fishEye.delegate = self;
54 |
55 |
56 | [fishEye reloadData];
57 | }
58 | }
59 |
60 | #pragma mark - FishEye Data Source
61 |
62 | - (NSUInteger)numberOfItemsInFishEyeView:(MCSFishEyeView *)fishEyeView
63 | {
64 | return fishEyeView == self.rightFishEyeView ? 4 : 20;
65 | }
66 |
67 | - (void)fishEyeView:(MCSFishEyeView *)fishEyeView configureItem:(MCSDemoFishEyeItem *)item atIndex:(NSUInteger)index
68 | {
69 | if (fishEyeView == self.leftFishEyeView) {
70 | item.label.text = [@(index + 1) stringValue];
71 | } else {
72 | item.label.text = [NSString stringWithFormat:@"%c", 'A' + index];
73 | }
74 | }
75 |
76 | #pragma mark - FishEye Delegate
77 |
78 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
79 | {
80 | for (MCSFishEyeView *fishEye in self.fishEyeViews) {
81 | [fishEye deselectSelectedItemAnimated:YES];
82 | }
83 | }
84 |
85 | @end
86 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | MCSFishEyeView
2 | ==========
3 |
4 | The fisheye from our [Bubble Browser](http://bubblebrowserapp.com) for iPad.
5 |
6 | [](https://raw.github.com/macoscope/MCSFisheye/master/Screens/fishEye.gif)
7 |
8 |
9 | ## How To Use
10 |
11 | Checkout demo project for some real life action!
12 |
13 | ### Instantiating
14 |
15 | Setup a `dataSource`, an optional `delegate`, add subview and you're done
16 |
17 | ```
18 | MCSFishEyeView *fisheye = [[MCSFishEyeView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 50.0f, 600.0f)];
19 | fisheye.dataSource = self;
20 | fisheye.delegate = self;
21 | [self.view addSubview:fisheye];
22 | ```
23 |
24 | ### Items
25 |
26 | `MCSFishEyeView` has the notion of items, which are more or less similar to table view cells. Each item should be a subclass of `MCSFishEyeViewItem`, as it already provides convinent methods for setting `highlighted` and `selected` state. You register a class to `MCSFishEyeView` by this one-liner:
27 |
28 | ```
29 | [fisheye registerItemClass:[MCSExampleFishEyeItem class]];
30 | ```
31 | Inside your custom `MCSFishEyeViewItem` subclass you are free to do whatever you want in both
32 |
33 | `- (void)setSelected:(BOOL)selected animated:(BOOL)animated`
34 |
35 | and
36 |
37 | `- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated`
38 |
39 | methods, just make sure you call `super` (don't worry, compiler will warn you if you forget to do so).
40 |
41 | ### States
42 |
43 | `MCSFishEyeView` has three different states:
44 |
45 |
46 |
47 | - `MCSFishEyeStateCollapsed` - in this state all the items are collapsed, no item is `highlighted` or `selected`
48 |
49 | [](https://raw.github.com/macoscope/MCSFisheye/master/Screens/collapsed.png)
50 |
51 | - `MCSFishEyeStateExpandedActive` - items are moving around according to touch location, at most one element is `highlighted`, no elements are `selected`
52 |
53 | [](https://raw.github.com/macoscope/MCSFisheye/master/Screens/highlighted.png)
54 |
55 | - `MCSFishEyeStateExpandedPassive` - in this state a single item is standing out in `selected` state, all the other items are collapsed
56 |
57 | [](https://raw.github.com/macoscope/MCSFisheye/master/Screens/selected.png)
58 |
59 |
60 | ## Detailed explanation
61 |
62 | For more detailed description of how `MCSFishEye` works checkout [this post on our blog](http://macoscope.com/blog/bubble-browsers-fisheye/)!
63 |
64 |
65 | ## Requirements
66 |
67 | - iOS 5.0
68 | - ARC
69 | - QuartzCore framework in your project
70 |
71 |
72 | ## Contact
73 |
74 | [Macoscope](http://macoscope.com)
75 |
76 | ## License
77 |
78 | Copyright 2013 Macoscope, sp z o.o.
79 |
80 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use
81 | this file except in compliance with the License. You may obtain a copy of the
82 | License at
83 |
84 | http://www.apache.org/licenses/LICENSE-2.0
85 |
86 | Unless required by applicable law or agreed to in writing, software distributed
87 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
88 | CONDITIONS OF ANY KIND, either express or implied. See the License for the
89 | specific language governing permissions and limitations under the License.
90 |
91 |
--------------------------------------------------------------------------------
/MCSFishEyeView/MCSFishEyeView.h:
--------------------------------------------------------------------------------
1 | //
2 | // MCSFishEyeView.h
3 | //
4 | // Created by Bartosz Ciechanowski on 2/25/13.
5 | // Copyright (c) 2013 Macoscope. All rights reserved.
6 | //
7 |
8 | #import
9 |
10 | @class MCSFishEyeView, MCSFishEyeViewItem;
11 |
12 | typedef NS_ENUM(NSInteger, MCSFishEyeState) {
13 | MCSFishEyeStateCollapsed, // all elements are docked in
14 | MCSFishEyeStateExpandedActive, // touch event happening, elements are moving around
15 | MCSFishEyeStateExpandedPassive // single element is out, rest is docked in
16 | };
17 |
18 | typedef NS_ENUM(NSInteger, MCSFishEyeExpansionDirection) {
19 | MCSFishEyeExpansionDirectionRight,
20 | MCSFishEyeExpansionDirectionLeft,
21 | MCSFishEyeExpansionDirectionTop,
22 | MCSFishEyeExpansionDirectionBottom
23 | };
24 |
25 | // those methods get called only after reloadData
26 | @protocol MCSFishEyeViewDataSource
27 |
28 | - (NSUInteger)numberOfItemsInFishEyeView:(MCSFishEyeView *)fishEyeView;
29 | - (void)fishEyeView:(MCSFishEyeView *)fishEyeView configureItem:(MCSFishEyeViewItem *)item atIndex:(NSUInteger)index;
30 |
31 | @end
32 |
33 | @protocol MCSFishEyeViewDelegate
34 |
35 | @optional
36 | - (void)fishEyeView:(MCSFishEyeView *)fishEyeView willChangeToState:(MCSFishEyeState)newState;
37 | - (void)fishEyeView:(MCSFishEyeView *)fishEyeView didChangeFromState:(MCSFishEyeState)oldState;
38 |
39 | - (BOOL)fishEyeView:(MCSFishEyeView *)fishEyeView shouldHighlightItemAtIndex:(NSUInteger)index;
40 | - (void)fishEyeView:(MCSFishEyeView *)fishEyeView didHighlightItemAtIndex:(NSUInteger)index;
41 | - (void)fishEyeView:(MCSFishEyeView *)fishEyeView didUnhighlightItemAtIndex:(NSUInteger)index;
42 |
43 | - (BOOL)fishEyeView:(MCSFishEyeView *)fishEyeView shouldSelectItemAtIndex:(NSUInteger)index;
44 | - (void)fishEyeView:(MCSFishEyeView *)fishEyeView didSelectItemAtIndex:(NSUInteger)index;
45 | - (void)fishEyeView:(MCSFishEyeView *)fishEyeView didDeselectItemAtIndex:(NSUInteger)index;
46 |
47 | @end
48 |
49 | @interface MCSFishEyeView : UIView
50 |
51 | @property (nonatomic, weak) id dataSource;
52 | @property (nonatomic, weak) id delegate;
53 |
54 | @property (nonatomic, readonly) MCSFishEyeState state;
55 |
56 | @property (nonatomic) MCSFishEyeExpansionDirection expansionDirection; // defaults to MCSFishEyeExpansionDirectionRight
57 | @property (nonatomic) BOOL evadesFinger; // if YES, then fisheye's items will get translated in expansion direction, so that they're not overlapped by finger, defaults to YES
58 |
59 | @property (nonatomic) CGSize itemSize; // size of fully expanded item, defaults to CGSizeMake(100.0f, 100.0f);
60 | @property (nonatomic) CGFloat selectedItemOffset; // offset of selected item in the direction of expansion, defaults to 50.0f
61 | @property (nonatomic) UIEdgeInsets contentInset; // additional insets applied when layouting items, defaults to UIEdgeInsetsZero
62 |
63 | @property (nonatomic, readonly) NSUInteger highlightedIndex; // returns NSNotFound if none is highlighted
64 | @property (nonatomic, readonly) NSUInteger selectedIndex; // returns NSNotFound if none is selected
65 |
66 | // the last one used counts
67 | - (void)registerItemNib:(UINib *)nib; // the nib file must contain only one top-level object and that object must be subclass of MCSFishEyeViewItem
68 | - (void)registerItemClass:(Class)itemClass; // itemClass must be subclass of MCSFishEyeViewItem
69 |
70 | - (void)reloadData;
71 |
72 | - (void)selectItemAtIndex:(NSUInteger)index animated:(BOOL)animated; // will not notify delegate
73 | - (void)deselectSelectedItemAnimated:(BOOL)animated; // will not notify delegate
74 |
75 | - (MCSFishEyeViewItem *)itemAtIndex:(NSUInteger)index;
76 | - (NSUInteger)indexForItem:(MCSFishEyeViewItem *)item;
77 |
78 | @end
79 |
--------------------------------------------------------------------------------
/FishEyeDemo/MCSDemoXibFishEyeViewItem.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 1280
5 | 12E55
6 | 4510
7 | 1187.39
8 | 626.00
9 |
13 |
14 | IBProxyObject
15 | IBUILabel
16 | IBUIView
17 |
18 |
19 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin
20 |
21 |
25 |
26 |
30 |
34 |
102 |
103 |
104 | NO
105 |
106 |
107 |
108 | label
109 |
110 |
111 |
112 | KBP-Lm-miJ
113 |
114 |
115 |
116 |
117 |
118 | 0
119 |
120 |
121 |
122 |
123 |
124 | -1
125 |
126 |
127 | File's Owner
128 |
129 |
130 | -2
131 |
132 |
133 |
134 |
135 | 1
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 | FPT-DN-MrP
145 |
146 |
147 |
148 |
149 | YzM-dm-kNv
150 |
151 |
152 |
153 |
154 |
155 |
156 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin
157 |
158 | UIResponder
159 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin
160 |
161 | MCSDemoXibFishEyeViewItem
162 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin
163 |
164 |
165 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin
166 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 | MCSDemoXibFishEyeViewItem
177 | MCSFishEyeViewItem
178 |
179 | label
180 | UILabel
181 |
182 |
183 | label
184 |
185 | label
186 | UILabel
187 |
188 |
189 |
190 | IBProjectSource
191 | ./Classes/MCSDemoXibFishEyeViewItem.h
192 |
193 |
194 |
195 | MCSFishEyeViewItem
196 | UIView
197 |
198 | IBProjectSource
199 | ./Classes/MCSFishEyeViewItem.h
200 |
201 |
202 |
203 |
204 | 0
205 | IBIPadFramework
206 | YES
207 |
208 | com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS
209 |
210 |
211 |
212 | com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3
213 |
214 |
215 | YES
216 | 3
217 | 3742
218 |
219 |
220 |
--------------------------------------------------------------------------------
/FishEyeDemo/MCSViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 1280
5 | 12E55
6 | 4488.1
7 | 1187.39
8 | 626.00
9 |
10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin
11 | 3715.3
12 |
13 |
14 | IBProxyObject
15 | IBUIView
16 |
17 |
18 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin
19 |
20 |
21 | PluginDependencyRecalculationVersion
22 |
23 |
24 |
25 |
26 | IBFilesOwner
27 | IBIPadFramework
28 |
29 |
30 | IBFirstResponder
31 | IBIPadFramework
32 |
33 |
34 |
35 | 1316
36 |
37 |
38 |
39 | 1300
40 |
41 | {60, 894}
42 |
43 |
44 |
45 | 3
46 | MCAwAA
47 |
48 | IBIPadFramework
49 |
50 |
51 |
52 | 1321
53 |
54 | {{657, 352}, {111, 300}}
55 |
56 |
57 |
58 | IBIPadFramework
59 |
60 |
61 |
62 | 1290
63 |
64 | {{0, 909}, {768, 95}}
65 |
66 |
67 |
68 | IBIPadFramework
69 |
70 |
71 |
72 | 1314
73 |
74 | {{86, 0}, {597, 54}}
75 |
76 |
77 |
78 | IBIPadFramework
79 |
80 |
81 |
82 | {{0, 20}, {768, 1004}}
83 |
84 |
85 |
86 | 3
87 | MQA
88 |
89 | 2
90 |
91 |
92 | NO
93 |
94 | 2
95 |
96 | IBIPadFramework
97 |
98 |
99 |
100 | NO
101 |
102 |
103 |
104 | bottomFishEyeView
105 |
106 |
107 |
108 | X5M-Ft-47q
109 |
110 |
111 |
112 | leftFishEyeView
113 |
114 |
115 |
116 | yPg-n2-TN2
117 |
118 |
119 |
120 | rightFishEyeView
121 |
122 |
123 |
124 | yCR-c6-eyV
125 |
126 |
127 |
128 | topFishEyeView
129 |
130 |
131 |
132 | 4Ci-iz-yOr
133 |
134 |
135 |
136 | view
137 |
138 |
139 |
140 | 3
141 |
142 |
143 |
144 | fishEyeViews
145 |
146 |
147 | NSArray
148 | NO
149 |
150 | Tzv-8e-SY4
151 |
152 |
153 |
154 | fishEyeViews
155 |
156 |
157 | NSArray
158 | NO
159 |
160 | 6AJ-Cc-2qb
161 |
162 |
163 |
164 | fishEyeViews
165 |
166 |
167 | NSArray
168 | NO
169 |
170 | O4X-U5-iIL
171 |
172 |
173 |
174 | fishEyeViews
175 |
176 |
177 | NSArray
178 | NO
179 |
180 | fGE-0y-LA7
181 |
182 |
183 |
184 |
185 |
186 | 0
187 |
188 |
189 |
190 |
191 |
192 | -1
193 |
194 |
195 | File's Owner
196 |
197 |
198 | -2
199 |
200 |
201 |
202 |
203 | 2
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 | dOc-Kq-xUJ
215 |
216 |
217 |
218 |
219 | koS-6J-5J5
220 |
221 |
222 |
223 |
224 | Vok-Ng-ZoO
225 |
226 |
227 |
228 |
229 | sGD-WF-cgg
230 |
231 |
232 |
233 |
234 |
235 |
236 | MCSViewController
237 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin
238 |
239 | UIResponder
240 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin
241 |
242 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin
243 |
244 |
245 | MCSFishEyeView
246 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin
247 |
248 |
249 | MCSFishEyeView
250 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin
251 |
252 |
253 | MCSFishEyeView
254 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin
255 |
256 |
257 | MCSFishEyeView
258 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 | 0
269 | IBIPadFramework
270 | YES
271 |
272 | com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS
273 |
274 |
275 |
276 | com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3
277 |
278 |
279 | YES
280 | 3
281 | 3715.3
282 |
283 |
284 |
--------------------------------------------------------------------------------
/FishEyeDemo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 296F083D17CF8FA000D1906F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 296F083C17CF8FA000D1906F /* Foundation.framework */; };
11 | 296F083F17CF8FA000D1906F /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 296F083E17CF8FA000D1906F /* CoreGraphics.framework */; };
12 | 296F084117CF8FA000D1906F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 296F084017CF8FA000D1906F /* UIKit.framework */; };
13 | 296F084717CF8FA000D1906F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 296F084517CF8FA000D1906F /* InfoPlist.strings */; };
14 | 296F084917CF8FA000D1906F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 296F084817CF8FA000D1906F /* main.m */; };
15 | 296F084D17CF8FA000D1906F /* MCSAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 296F084C17CF8FA000D1906F /* MCSAppDelegate.m */; };
16 | 29B71BB117D09B5F00AA7946 /* MCSViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B71BAF17D09B5F00AA7946 /* MCSViewController.m */; };
17 | 29B71BB217D09B5F00AA7946 /* MCSViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29B71BB017D09B5F00AA7946 /* MCSViewController.xib */; };
18 | 29B71BFB17D0A63C00AA7946 /* MCSDemoFishEyeItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B71BFA17D0A63C00AA7946 /* MCSDemoFishEyeItem.m */; };
19 | 29B71BFD17D0D4A000AA7946 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29B71BFC17D0D4A000AA7946 /* QuartzCore.framework */; };
20 | 29E2EDEF17D61E0200BEEA47 /* MCSFishEyeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 29E2EDEC17D61E0200BEEA47 /* MCSFishEyeView.m */; };
21 | 29E2EDF017D61E0200BEEA47 /* MCSFishEyeViewItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 29E2EDEE17D61E0200BEEA47 /* MCSFishEyeViewItem.m */; };
22 | 29E2EDF317D6207F00BEEA47 /* MCSDemoXibFishEyeViewItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 29E2EDF217D6207F00BEEA47 /* MCSDemoXibFishEyeViewItem.m */; };
23 | 29E2EDF517D6220E00BEEA47 /* MCSDemoXibFishEyeViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29E2EDF417D6209C00BEEA47 /* MCSDemoXibFishEyeViewItem.xib */; };
24 | /* End PBXBuildFile section */
25 |
26 | /* Begin PBXFileReference section */
27 | 296F083917CF8FA000D1906F /* FishEyeDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FishEyeDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
28 | 296F083C17CF8FA000D1906F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
29 | 296F083E17CF8FA000D1906F /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
30 | 296F084017CF8FA000D1906F /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
31 | 296F084417CF8FA000D1906F /* FishEyeDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "FishEyeDemo-Info.plist"; sourceTree = ""; };
32 | 296F084617CF8FA000D1906F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; };
33 | 296F084817CF8FA000D1906F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
34 | 296F084A17CF8FA000D1906F /* FishEyeDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FishEyeDemo-Prefix.pch"; sourceTree = ""; };
35 | 296F084B17CF8FA000D1906F /* MCSAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MCSAppDelegate.h; sourceTree = ""; };
36 | 296F084C17CF8FA000D1906F /* MCSAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MCSAppDelegate.m; sourceTree = ""; };
37 | 29B71BAE17D09B5F00AA7946 /* MCSViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCSViewController.h; sourceTree = ""; };
38 | 29B71BAF17D09B5F00AA7946 /* MCSViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = MCSViewController.m; sourceTree = ""; };
39 | 29B71BB017D09B5F00AA7946 /* MCSViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MCSViewController.xib; sourceTree = ""; };
40 | 29B71BF917D0A63C00AA7946 /* MCSDemoFishEyeItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCSDemoFishEyeItem.h; sourceTree = ""; };
41 | 29B71BFA17D0A63C00AA7946 /* MCSDemoFishEyeItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCSDemoFishEyeItem.m; sourceTree = ""; };
42 | 29B71BFC17D0D4A000AA7946 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
43 | 29E2EDEB17D61E0200BEEA47 /* MCSFishEyeView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCSFishEyeView.h; sourceTree = ""; };
44 | 29E2EDEC17D61E0200BEEA47 /* MCSFishEyeView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCSFishEyeView.m; sourceTree = ""; };
45 | 29E2EDED17D61E0200BEEA47 /* MCSFishEyeViewItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCSFishEyeViewItem.h; sourceTree = ""; };
46 | 29E2EDEE17D61E0200BEEA47 /* MCSFishEyeViewItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCSFishEyeViewItem.m; sourceTree = ""; };
47 | 29E2EDF117D6207F00BEEA47 /* MCSDemoXibFishEyeViewItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCSDemoXibFishEyeViewItem.h; sourceTree = ""; };
48 | 29E2EDF217D6207F00BEEA47 /* MCSDemoXibFishEyeViewItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCSDemoXibFishEyeViewItem.m; sourceTree = ""; };
49 | 29E2EDF417D6209C00BEEA47 /* MCSDemoXibFishEyeViewItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MCSDemoXibFishEyeViewItem.xib; sourceTree = ""; };
50 | /* End PBXFileReference section */
51 |
52 | /* Begin PBXFrameworksBuildPhase section */
53 | 296F083617CF8FA000D1906F /* Frameworks */ = {
54 | isa = PBXFrameworksBuildPhase;
55 | buildActionMask = 2147483647;
56 | files = (
57 | 29B71BFD17D0D4A000AA7946 /* QuartzCore.framework in Frameworks */,
58 | 296F083F17CF8FA000D1906F /* CoreGraphics.framework in Frameworks */,
59 | 296F084117CF8FA000D1906F /* UIKit.framework in Frameworks */,
60 | 296F083D17CF8FA000D1906F /* Foundation.framework in Frameworks */,
61 | );
62 | runOnlyForDeploymentPostprocessing = 0;
63 | };
64 | /* End PBXFrameworksBuildPhase section */
65 |
66 | /* Begin PBXGroup section */
67 | 296F083017CF8FA000D1906F = {
68 | isa = PBXGroup;
69 | children = (
70 | 29E2EDEA17D61E0200BEEA47 /* MCSFishEyeView */,
71 | 296F084217CF8FA000D1906F /* FishEyeDemo */,
72 | 296F083B17CF8FA000D1906F /* Frameworks */,
73 | 296F083A17CF8FA000D1906F /* Products */,
74 | );
75 | indentWidth = 2;
76 | sourceTree = "";
77 | tabWidth = 2;
78 | };
79 | 296F083A17CF8FA000D1906F /* Products */ = {
80 | isa = PBXGroup;
81 | children = (
82 | 296F083917CF8FA000D1906F /* FishEyeDemo.app */,
83 | );
84 | name = Products;
85 | sourceTree = "";
86 | };
87 | 296F083B17CF8FA000D1906F /* Frameworks */ = {
88 | isa = PBXGroup;
89 | children = (
90 | 29B71BFC17D0D4A000AA7946 /* QuartzCore.framework */,
91 | 296F083C17CF8FA000D1906F /* Foundation.framework */,
92 | 296F083E17CF8FA000D1906F /* CoreGraphics.framework */,
93 | 296F084017CF8FA000D1906F /* UIKit.framework */,
94 | );
95 | name = Frameworks;
96 | sourceTree = "";
97 | };
98 | 296F084217CF8FA000D1906F /* FishEyeDemo */ = {
99 | isa = PBXGroup;
100 | children = (
101 | 296F084B17CF8FA000D1906F /* MCSAppDelegate.h */,
102 | 296F084C17CF8FA000D1906F /* MCSAppDelegate.m */,
103 | 29B71BF917D0A63C00AA7946 /* MCSDemoFishEyeItem.h */,
104 | 29B71BFA17D0A63C00AA7946 /* MCSDemoFishEyeItem.m */,
105 | 29E2EDF117D6207F00BEEA47 /* MCSDemoXibFishEyeViewItem.h */,
106 | 29E2EDF217D6207F00BEEA47 /* MCSDemoXibFishEyeViewItem.m */,
107 | 29E2EDF417D6209C00BEEA47 /* MCSDemoXibFishEyeViewItem.xib */,
108 | 29B71BAE17D09B5F00AA7946 /* MCSViewController.h */,
109 | 29B71BAF17D09B5F00AA7946 /* MCSViewController.m */,
110 | 29B71BB017D09B5F00AA7946 /* MCSViewController.xib */,
111 | 296F084317CF8FA000D1906F /* Supporting Files */,
112 | );
113 | path = FishEyeDemo;
114 | sourceTree = "";
115 | };
116 | 296F084317CF8FA000D1906F /* Supporting Files */ = {
117 | isa = PBXGroup;
118 | children = (
119 | 296F084417CF8FA000D1906F /* FishEyeDemo-Info.plist */,
120 | 296F084517CF8FA000D1906F /* InfoPlist.strings */,
121 | 296F084817CF8FA000D1906F /* main.m */,
122 | 296F084A17CF8FA000D1906F /* FishEyeDemo-Prefix.pch */,
123 | );
124 | name = "Supporting Files";
125 | sourceTree = "";
126 | };
127 | 29E2EDEA17D61E0200BEEA47 /* MCSFishEyeView */ = {
128 | isa = PBXGroup;
129 | children = (
130 | 29E2EDEB17D61E0200BEEA47 /* MCSFishEyeView.h */,
131 | 29E2EDEC17D61E0200BEEA47 /* MCSFishEyeView.m */,
132 | 29E2EDED17D61E0200BEEA47 /* MCSFishEyeViewItem.h */,
133 | 29E2EDEE17D61E0200BEEA47 /* MCSFishEyeViewItem.m */,
134 | );
135 | path = MCSFishEyeView;
136 | sourceTree = "";
137 | };
138 | /* End PBXGroup section */
139 |
140 | /* Begin PBXNativeTarget section */
141 | 296F083817CF8FA000D1906F /* FishEyeDemo */ = {
142 | isa = PBXNativeTarget;
143 | buildConfigurationList = 296F086E17CF8FA000D1906F /* Build configuration list for PBXNativeTarget "FishEyeDemo" */;
144 | buildPhases = (
145 | 296F083517CF8FA000D1906F /* Sources */,
146 | 296F083617CF8FA000D1906F /* Frameworks */,
147 | 296F083717CF8FA000D1906F /* Resources */,
148 | );
149 | buildRules = (
150 | );
151 | dependencies = (
152 | );
153 | name = FishEyeDemo;
154 | productName = FishEyeDemo;
155 | productReference = 296F083917CF8FA000D1906F /* FishEyeDemo.app */;
156 | productType = "com.apple.product-type.application";
157 | };
158 | /* End PBXNativeTarget section */
159 |
160 | /* Begin PBXProject section */
161 | 296F083117CF8FA000D1906F /* Project object */ = {
162 | isa = PBXProject;
163 | attributes = {
164 | CLASSPREFIX = MCS;
165 | LastUpgradeCheck = 0500;
166 | ORGANIZATIONNAME = Macoscope;
167 | };
168 | buildConfigurationList = 296F083417CF8FA000D1906F /* Build configuration list for PBXProject "FishEyeDemo" */;
169 | compatibilityVersion = "Xcode 3.2";
170 | developmentRegion = English;
171 | hasScannedForEncodings = 0;
172 | knownRegions = (
173 | en,
174 | Base,
175 | );
176 | mainGroup = 296F083017CF8FA000D1906F;
177 | productRefGroup = 296F083A17CF8FA000D1906F /* Products */;
178 | projectDirPath = "";
179 | projectRoot = "";
180 | targets = (
181 | 296F083817CF8FA000D1906F /* FishEyeDemo */,
182 | );
183 | };
184 | /* End PBXProject section */
185 |
186 | /* Begin PBXResourcesBuildPhase section */
187 | 296F083717CF8FA000D1906F /* Resources */ = {
188 | isa = PBXResourcesBuildPhase;
189 | buildActionMask = 2147483647;
190 | files = (
191 | 29B71BB217D09B5F00AA7946 /* MCSViewController.xib in Resources */,
192 | 29E2EDF517D6220E00BEEA47 /* MCSDemoXibFishEyeViewItem.xib in Resources */,
193 | 296F084717CF8FA000D1906F /* InfoPlist.strings in Resources */,
194 | );
195 | runOnlyForDeploymentPostprocessing = 0;
196 | };
197 | /* End PBXResourcesBuildPhase section */
198 |
199 | /* Begin PBXSourcesBuildPhase section */
200 | 296F083517CF8FA000D1906F /* Sources */ = {
201 | isa = PBXSourcesBuildPhase;
202 | buildActionMask = 2147483647;
203 | files = (
204 | 29E2EDF317D6207F00BEEA47 /* MCSDemoXibFishEyeViewItem.m in Sources */,
205 | 29E2EDEF17D61E0200BEEA47 /* MCSFishEyeView.m in Sources */,
206 | 29B71BB117D09B5F00AA7946 /* MCSViewController.m in Sources */,
207 | 296F084D17CF8FA000D1906F /* MCSAppDelegate.m in Sources */,
208 | 29E2EDF017D61E0200BEEA47 /* MCSFishEyeViewItem.m in Sources */,
209 | 296F084917CF8FA000D1906F /* main.m in Sources */,
210 | 29B71BFB17D0A63C00AA7946 /* MCSDemoFishEyeItem.m in Sources */,
211 | );
212 | runOnlyForDeploymentPostprocessing = 0;
213 | };
214 | /* End PBXSourcesBuildPhase section */
215 |
216 | /* Begin PBXVariantGroup section */
217 | 296F084517CF8FA000D1906F /* InfoPlist.strings */ = {
218 | isa = PBXVariantGroup;
219 | children = (
220 | 296F084617CF8FA000D1906F /* en */,
221 | );
222 | name = InfoPlist.strings;
223 | sourceTree = "";
224 | };
225 | /* End PBXVariantGroup section */
226 |
227 | /* Begin XCBuildConfiguration section */
228 | 296F086C17CF8FA000D1906F /* Debug */ = {
229 | isa = XCBuildConfiguration;
230 | buildSettings = {
231 | ALWAYS_SEARCH_USER_PATHS = NO;
232 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
233 | CLANG_CXX_LIBRARY = "libc++";
234 | CLANG_ENABLE_MODULES = YES;
235 | CLANG_ENABLE_OBJC_ARC = YES;
236 | CLANG_WARN_BOOL_CONVERSION = YES;
237 | CLANG_WARN_CONSTANT_CONVERSION = YES;
238 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
239 | CLANG_WARN_EMPTY_BODY = YES;
240 | CLANG_WARN_ENUM_CONVERSION = YES;
241 | CLANG_WARN_INT_CONVERSION = YES;
242 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
243 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
244 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
245 | COPY_PHASE_STRIP = NO;
246 | GCC_C_LANGUAGE_STANDARD = gnu99;
247 | GCC_DYNAMIC_NO_PIC = NO;
248 | GCC_OPTIMIZATION_LEVEL = 0;
249 | GCC_PREPROCESSOR_DEFINITIONS = (
250 | "DEBUG=1",
251 | "$(inherited)",
252 | );
253 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
254 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
255 | GCC_WARN_UNDECLARED_SELECTOR = YES;
256 | GCC_WARN_UNINITIALIZED_AUTOS = YES;
257 | GCC_WARN_UNUSED_FUNCTION = YES;
258 | GCC_WARN_UNUSED_VARIABLE = YES;
259 | IPHONEOS_DEPLOYMENT_TARGET = 5.0;
260 | ONLY_ACTIVE_ARCH = YES;
261 | SDKROOT = iphoneos;
262 | TARGETED_DEVICE_FAMILY = "1,2";
263 | };
264 | name = Debug;
265 | };
266 | 296F086D17CF8FA000D1906F /* Release */ = {
267 | isa = XCBuildConfiguration;
268 | buildSettings = {
269 | ALWAYS_SEARCH_USER_PATHS = NO;
270 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
271 | CLANG_CXX_LIBRARY = "libc++";
272 | CLANG_ENABLE_MODULES = YES;
273 | CLANG_ENABLE_OBJC_ARC = YES;
274 | CLANG_WARN_BOOL_CONVERSION = YES;
275 | CLANG_WARN_CONSTANT_CONVERSION = YES;
276 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
277 | CLANG_WARN_EMPTY_BODY = YES;
278 | CLANG_WARN_ENUM_CONVERSION = YES;
279 | CLANG_WARN_INT_CONVERSION = YES;
280 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
281 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
282 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
283 | COPY_PHASE_STRIP = YES;
284 | ENABLE_NS_ASSERTIONS = NO;
285 | GCC_C_LANGUAGE_STANDARD = gnu99;
286 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
287 | GCC_WARN_UNDECLARED_SELECTOR = YES;
288 | GCC_WARN_UNINITIALIZED_AUTOS = YES;
289 | GCC_WARN_UNUSED_FUNCTION = YES;
290 | GCC_WARN_UNUSED_VARIABLE = YES;
291 | IPHONEOS_DEPLOYMENT_TARGET = 5.0;
292 | SDKROOT = iphoneos;
293 | TARGETED_DEVICE_FAMILY = "1,2";
294 | VALIDATE_PRODUCT = YES;
295 | };
296 | name = Release;
297 | };
298 | 296F086F17CF8FA000D1906F /* Debug */ = {
299 | isa = XCBuildConfiguration;
300 | buildSettings = {
301 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
302 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
303 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
304 | GCC_PREFIX_HEADER = "FishEyeDemo/FishEyeDemo-Prefix.pch";
305 | INFOPLIST_FILE = "FishEyeDemo/FishEyeDemo-Info.plist";
306 | IPHONEOS_DEPLOYMENT_TARGET = 5.0;
307 | PRODUCT_NAME = "$(TARGET_NAME)";
308 | WRAPPER_EXTENSION = app;
309 | };
310 | name = Debug;
311 | };
312 | 296F087017CF8FA000D1906F /* Release */ = {
313 | isa = XCBuildConfiguration;
314 | buildSettings = {
315 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
316 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
317 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
318 | GCC_PREFIX_HEADER = "FishEyeDemo/FishEyeDemo-Prefix.pch";
319 | INFOPLIST_FILE = "FishEyeDemo/FishEyeDemo-Info.plist";
320 | IPHONEOS_DEPLOYMENT_TARGET = 5.0;
321 | PRODUCT_NAME = "$(TARGET_NAME)";
322 | WRAPPER_EXTENSION = app;
323 | };
324 | name = Release;
325 | };
326 | /* End XCBuildConfiguration section */
327 |
328 | /* Begin XCConfigurationList section */
329 | 296F083417CF8FA000D1906F /* Build configuration list for PBXProject "FishEyeDemo" */ = {
330 | isa = XCConfigurationList;
331 | buildConfigurations = (
332 | 296F086C17CF8FA000D1906F /* Debug */,
333 | 296F086D17CF8FA000D1906F /* Release */,
334 | );
335 | defaultConfigurationIsVisible = 0;
336 | defaultConfigurationName = Release;
337 | };
338 | 296F086E17CF8FA000D1906F /* Build configuration list for PBXNativeTarget "FishEyeDemo" */ = {
339 | isa = XCConfigurationList;
340 | buildConfigurations = (
341 | 296F086F17CF8FA000D1906F /* Debug */,
342 | 296F087017CF8FA000D1906F /* Release */,
343 | );
344 | defaultConfigurationIsVisible = 0;
345 | defaultConfigurationName = Release;
346 | };
347 | /* End XCConfigurationList section */
348 | };
349 | rootObject = 296F083117CF8FA000D1906F /* Project object */;
350 | }
351 |
--------------------------------------------------------------------------------
/MCSFishEyeView/MCSFishEyeView.m:
--------------------------------------------------------------------------------
1 | //
2 | // MCSFishEyeView.m
3 | //
4 | // Created by Bartosz Ciechanowski on 2/25/13.
5 | // Copyright (c) 2013 Macoscope. All rights reserved.
6 | //
7 |
8 | #import "MCSFishEyeView.h"
9 | #import "MCSFishEyeViewItem.h"
10 |
11 | #import
12 |
13 | /*
14 | If an item is within this distance to touch location,
15 | it will get gradually translated in the direction of expansion,
16 | so that this item is not obstructed by finger (only if 'evadesFinger' is set to YES)
17 | */
18 | static const CGFloat FingerTranslationRadius = 100.0;
19 |
20 | /*
21 | Amount of translation applied to items in the direction of expansion
22 | (only if 'evadesFinger' is set to YES)
23 | */
24 | static const CGFloat FingerTranslation = 60.0;
25 |
26 | /*
27 | Determines the maxium number of neighbors that get enalrged by expansion function
28 | */
29 | static const NSInteger MaxExpansionFunctionNeighbors = 3;
30 |
31 |
32 |
33 | @interface MCSFishEyeView()
34 | {
35 | struct {
36 | unsigned int willChangeToState:1;
37 | unsigned int didChangeFromState:1;
38 |
39 | unsigned int shouldHighlight:1;
40 | unsigned int didHighlight:1;
41 | unsigned int didUnhighlight:1;
42 |
43 | unsigned int shouldSelect:1;
44 | unsigned int didSelect:1;
45 | unsigned int didDeselect:1;
46 |
47 | } _delegateRespondsTo;
48 | }
49 |
50 | @property (nonatomic, strong) NSArray *itemContainers;
51 | @property (nonatomic, strong) NSArray *items;
52 |
53 | @property (nonatomic, strong) UIView *transformView;
54 |
55 | @property (nonatomic, readwrite) MCSFishEyeState state;
56 | @property (nonatomic) NSInteger expansionsNeighbors;
57 |
58 | @property (nonatomic) CGAffineTransform itemTransform;
59 | @property (nonatomic) CGSize transformedItemSize;
60 | @property (nonatomic) CGFloat collapsedItemScale;
61 | @property (nonatomic) CGFloat collapsedItemHeight;
62 |
63 | @property (nonatomic) CGFloat startOffset;
64 | @property (nonatomic) CGFloat centeringOffset;
65 |
66 | @property (nonatomic, strong) Class itemClass;
67 | @property (nonatomic, strong) UINib *itemNib;
68 |
69 | @end
70 |
71 | @implementation MCSFishEyeView
72 |
73 | - (id)initWithFrame:(CGRect)frame
74 | {
75 | self = [super initWithFrame:frame];
76 | if (self) {
77 | [self commonInit];
78 | }
79 | return self;
80 | }
81 |
82 | - (id)initWithCoder:(NSCoder *)aDecoder
83 | {
84 | self = [super initWithCoder:aDecoder];
85 | if (self) {
86 | [self commonInit];
87 | }
88 | return self;
89 | }
90 |
91 | - (void)commonInit
92 | {
93 | _itemClass = [MCSFishEyeViewItem class];
94 | _itemSize = CGSizeMake(100.0f, 100.0f);
95 | _selectedItemOffset = 50.0f;
96 | _contentInset = UIEdgeInsetsZero;
97 | _evadesFinger = YES;
98 | _expansionDirection = MCSFishEyeExpansionDirectionRight;
99 |
100 | _selectedIndex = NSNotFound;
101 | _highlightedIndex = NSNotFound;
102 |
103 | _transformView = [[UIView alloc] init];
104 | _transformView.backgroundColor = [UIColor clearColor];
105 | [self addSubview:_transformView];
106 | }
107 |
108 | #pragma mark - Setters
109 |
110 | - (void)setDelegate:(id)delegate
111 | {
112 | _delegate = delegate;
113 | _delegateRespondsTo.willChangeToState = [delegate respondsToSelector:@selector(fishEyeView:willChangeToState:)];
114 | _delegateRespondsTo.didChangeFromState = [delegate respondsToSelector:@selector(fishEyeView:didChangeFromState:)];
115 |
116 | _delegateRespondsTo.shouldHighlight = [delegate respondsToSelector:@selector(fishEyeView:shouldHighlightItemAtIndex:)];
117 | _delegateRespondsTo.didHighlight = [delegate respondsToSelector:@selector(fishEyeView:didHighlightItemAtIndex:)];
118 | _delegateRespondsTo.didUnhighlight = [delegate respondsToSelector:@selector(fishEyeView:didUnhighlightItemAtIndex:)];
119 |
120 | _delegateRespondsTo.shouldSelect = [delegate respondsToSelector:@selector(fishEyeView:shouldSelectItemAtIndex:)];
121 | _delegateRespondsTo.didSelect = [delegate respondsToSelector:@selector(fishEyeView:didSelectItemAtIndex:)];
122 | _delegateRespondsTo.didDeselect = [delegate respondsToSelector:@selector(fishEyeView:didDeselectItemAtIndex:)];
123 | }
124 |
125 | - (void)setState:(MCSFishEyeState)newFishEyeState
126 | {
127 | if (_state == newFishEyeState) {
128 | return;
129 | }
130 |
131 | MCSFishEyeState oldFishEyeState = _state;
132 |
133 | if (_delegateRespondsTo.willChangeToState) {
134 | [self.delegate fishEyeView:self willChangeToState:newFishEyeState];
135 | }
136 |
137 | _state = newFishEyeState;
138 |
139 | if (_delegateRespondsTo.didChangeFromState) {
140 | [self.delegate fishEyeView:self didChangeFromState:oldFishEyeState];
141 | }
142 | }
143 |
144 | - (void)setItemSize:(CGSize)itemSize
145 | {
146 | _itemSize = itemSize;
147 |
148 | for (MCSFishEyeViewItem *item in self.itemContainers) {
149 | item.bounds = CGRectMake(0, 0, itemSize.width, itemSize.height);
150 | item.center = CGPointZero;
151 | }
152 |
153 | [self setNeedsLayout];
154 | }
155 |
156 | - (void)setExpansionDirection:(MCSFishEyeExpansionDirection)expansionDirection
157 | {
158 | _expansionDirection = expansionDirection;
159 | [self setNeedsLayout];
160 | }
161 |
162 | - (void)setContentInset:(UIEdgeInsets)contentInset
163 | {
164 | _contentInset = contentInset;
165 | [self setNeedsLayout];
166 | }
167 |
168 | - (void)layoutSubviews
169 | {
170 | [super layoutSubviews];
171 |
172 | [self repositionTransformView];
173 | [self recalculateDimensions];
174 | [self layoutWithCurrentState];
175 | }
176 |
177 | - (void)setHighlightedIndex:(NSUInteger)highlightedIndex
178 | {
179 | if (highlightedIndex == _highlightedIndex) {
180 | return;
181 | }
182 |
183 | if (_highlightedIndex != NSNotFound) {
184 | [[self itemAtIndex:_highlightedIndex] setHighlighted:NO animated:YES];
185 | if (_delegateRespondsTo.didUnhighlight) {
186 | [self.delegate fishEyeView:self didUnhighlightItemAtIndex:_highlightedIndex];
187 | }
188 | }
189 |
190 | if (_delegateRespondsTo.shouldHighlight && ![self.delegate fishEyeView:self shouldHighlightItemAtIndex:highlightedIndex]) {
191 | highlightedIndex = NSNotFound; // don't highlight anything
192 | }
193 |
194 | _highlightedIndex = highlightedIndex;
195 |
196 | if (_highlightedIndex != NSNotFound) {
197 | [[self itemAtIndex:_highlightedIndex] setHighlighted:YES animated:YES];
198 | if (_delegateRespondsTo.didHighlight) {
199 | [self.delegate fishEyeView:self didHighlightItemAtIndex:_highlightedIndex];
200 | }
201 | }
202 | }
203 |
204 | - (void)setSelectedIndex:(NSUInteger)selectedIndex withDelegateCalls:(BOOL)shouldCallDelegate animated:(BOOL)animated
205 | {
206 | if (selectedIndex == _selectedIndex) {
207 | return;
208 | }
209 |
210 | if (_selectedIndex != NSNotFound) {
211 | [[self itemAtIndex:_selectedIndex] setSelected:NO animated:animated];
212 | if (shouldCallDelegate && _delegateRespondsTo.didDeselect) {
213 | [self.delegate fishEyeView:self didDeselectItemAtIndex:_selectedIndex];
214 | }
215 | }
216 |
217 | if (shouldCallDelegate && _delegateRespondsTo.shouldSelect && ![self.delegate fishEyeView:self shouldSelectItemAtIndex:selectedIndex]) {
218 | selectedIndex = NSNotFound; // don't select anything
219 | }
220 |
221 | _selectedIndex = selectedIndex;
222 |
223 | if (_selectedIndex != NSNotFound) {
224 | [self bringSubviewToFront:self.itemContainers[_selectedIndex]];
225 | [[self itemAtIndex:_selectedIndex] setSelected:YES animated:animated];
226 | if (shouldCallDelegate && _delegateRespondsTo.didSelect) {
227 | [self.delegate fishEyeView:self didSelectItemAtIndex:_selectedIndex];
228 | }
229 | }
230 | }
231 |
232 | #pragma mark - Public functions
233 |
234 | - (void)registerItemClass:(Class)itemClass
235 | {
236 | NSParameterAssert(itemClass != nil);
237 | NSParameterAssert([itemClass isSubclassOfClass:[MCSFishEyeViewItem class]]);
238 |
239 | self.itemClass = itemClass;
240 | self.itemNib = nil;
241 | }
242 |
243 | - (void)registerItemNib:(UINib *)nib
244 | {
245 | NSParameterAssert(nib != nil);
246 |
247 | self.itemClass = nil;
248 | self.itemNib = nib;
249 | }
250 |
251 | - (void)reloadData
252 | {
253 | [self.items makeObjectsPerformSelector:@selector(removeFromSuperview)];
254 |
255 | NSUInteger count = [self.dataSource numberOfItemsInFishEyeView:self];
256 | NSMutableArray *items = [NSMutableArray arrayWithCapacity:count];
257 | NSMutableArray *containers = [NSMutableArray arrayWithCapacity:count];
258 |
259 | for (int i = 0; i < count; i++) {
260 | UIView *container = [[UIView alloc] init];
261 | container.backgroundColor = [UIColor clearColor];
262 | container.bounds = CGRectMake(0, 0, _itemSize.width, _itemSize.height);
263 | container.center = CGPointZero;
264 |
265 | MCSFishEyeViewItem *item;
266 | if (self.itemClass) {
267 | item = [[self.itemClass alloc] init];
268 | } else if (self.itemNib) {
269 | NSArray *elements = [self.itemNib instantiateWithOwner:nil options:nil];
270 | NSAssert(elements.count == 1, @"Instantiated NIB file doesn't have one top level object");
271 | item = elements[0];
272 | NSAssert([item isKindOfClass:[MCSFishEyeViewItem class]], @"Instantiated NIB object isn't subclass of MCSFishEyeViewItem");
273 | }
274 |
275 | item.frame = container.bounds;
276 | item.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
277 | item.highlighted = NO;
278 | item.selected = NO;
279 |
280 | [self.dataSource fishEyeView:self configureItem:item atIndex:i];
281 |
282 | [items addObject:item];
283 | [containers addObject:container];
284 |
285 | [container addSubview:item];
286 | [self.transformView addSubview:container];
287 | }
288 |
289 | self.items = items;
290 | self.itemContainers = containers;
291 | self.state = MCSFishEyeStateCollapsed;
292 |
293 | self.expansionsNeighbors = MIN(MaxExpansionFunctionNeighbors, items.count/2);
294 |
295 | [self recalculateDimensions];
296 | [self layoutWithCurrentState];
297 | [self collapseAnimated:NO notifying:NO];
298 | }
299 |
300 | - (MCSFishEyeViewItem *)itemAtIndex:(NSUInteger)index
301 | {
302 | return self.items[index];
303 | }
304 |
305 | - (NSUInteger)indexForItem:(MCSFishEyeViewItem *)item
306 | {
307 | return [self.items indexOfObject:item];
308 | }
309 |
310 | - (void)selectItemAtIndex:(NSUInteger)index animated:(BOOL)animated
311 | {
312 | [self setState:MCSFishEyeStateExpandedPassive];
313 | [self setSelectedIndex:index withDelegateCalls:NO animated:animated];
314 | [self layoutItemsForOffset:[self offsetForIndex:index] withAnimationDuration:animated ? 0.2 : 0.0];
315 | }
316 |
317 | - (void)deselectSelectedItemAnimated:(BOOL)animated
318 | {
319 | [self setSelectedIndex:NSNotFound withDelegateCalls:NO animated:YES];
320 | [self collapseAnimated:animated notifying:NO];
321 | }
322 |
323 | #pragma mark - Calculations
324 |
325 | - (void)repositionTransformView
326 | {
327 | CGRect bounds = self.bounds;
328 | bounds.origin.x += self.contentInset.left;
329 | bounds.origin.y += self.contentInset.top;
330 | bounds.size.width -= (self.contentInset.left + self.contentInset.right);
331 | bounds.size.height -= (self.contentInset.top + self.contentInset.bottom);
332 |
333 | CGPoint center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
334 | CGAffineTransform viewTransform, itemTransform;
335 |
336 | switch (self.expansionDirection) {
337 | case MCSFishEyeExpansionDirectionRight:
338 | viewTransform = CGAffineTransformIdentity;
339 | itemTransform = CGAffineTransformIdentity;
340 | break;
341 | case MCSFishEyeExpansionDirectionLeft:
342 | viewTransform = CGAffineTransformMakeScale(-1.0, 1.0);
343 | itemTransform = CGAffineTransformMakeScale(-1.0, 1.0);
344 | break;
345 | case MCSFishEyeExpansionDirectionTop:
346 | viewTransform = CGAffineTransformMakeRotation(-M_PI_2);
347 | itemTransform = CGAffineTransformMakeRotation(M_PI_2);
348 | bounds.size = CGSizeMake(bounds.size.height, bounds.size.width);
349 | break;
350 | case MCSFishEyeExpansionDirectionBottom:
351 | viewTransform = CGAffineTransformConcat(CGAffineTransformMakeRotation(M_PI_2), CGAffineTransformMakeScale(-1.0, 1.0));
352 | itemTransform = CGAffineTransformConcat(CGAffineTransformMakeRotation(-M_PI_2), CGAffineTransformMakeScale(1.0, -1.0));
353 | bounds.size = CGSizeMake(bounds.size.height, bounds.size.width);
354 | break;
355 | }
356 |
357 | bounds.origin = CGPointZero;
358 |
359 | self.transformView.bounds = bounds;
360 | self.transformView.center = center;
361 | self.transformView.transform = viewTransform;
362 | self.itemTransform = itemTransform;
363 |
364 | [self layoutWithCurrentState];
365 | }
366 |
367 | - (void)recalculateDimensions
368 | {
369 | switch (self.expansionDirection) {
370 | case MCSFishEyeExpansionDirectionRight:
371 | case MCSFishEyeExpansionDirectionLeft:
372 | self.transformedItemSize = self.itemSize;
373 | break;
374 | case MCSFishEyeExpansionDirectionTop:
375 | case MCSFishEyeExpansionDirectionBottom:
376 | self.transformedItemSize = CGSizeMake(self.itemSize.height, self.itemSize.width);
377 | break;
378 | }
379 |
380 | CGFloat totalHeight = self.transformView.bounds.size.height;
381 | CGFloat itemHeight = self.transformedItemSize.height;
382 |
383 | CGFloat collapsedItemHeight = MIN(totalHeight / self.items.count, itemHeight);
384 | self.collapsedItemHeight = collapsedItemHeight;
385 | self.collapsedItemScale = self.collapsedItemHeight / itemHeight;
386 |
387 | CGFloat expansionSurplus = 0.0;
388 |
389 | for (int i = -self.expansionsNeighbors - 1; i < self.expansionsNeighbors + 1; i++) {
390 | expansionSurplus += [self scaleForOffsetFromFocusPoint:i * collapsedItemHeight] * itemHeight - collapsedItemHeight;
391 | }
392 |
393 | if (collapsedItemHeight == itemHeight) {
394 | self.centeringOffset = (totalHeight - self.items.count * collapsedItemHeight)/2.0;
395 | self.startOffset = 0.0;
396 | } else {
397 | self.centeringOffset = 0.0;
398 | self.startOffset = expansionSurplus/2.0;
399 | }
400 | }
401 |
402 | - (CGFloat)offsetForIndex:(NSUInteger)index
403 | {
404 | return (index + 0.5) * self.collapsedItemHeight + self.centeringOffset;
405 | }
406 |
407 | - (NSUInteger)indexForOffset:(CGFloat)offset
408 | {
409 | NSInteger index = floorf((offset - self.centeringOffset)/self.collapsedItemHeight);
410 |
411 | return (index >= 0 && index < [self.items count]) ? index : NSNotFound;
412 | }
413 |
414 | - (CGFloat)scaleForOffsetFromFocusPoint:(CGFloat)offset
415 | {
416 | CGFloat normalizedOffset = fabsf(offset/self.collapsedItemHeight);
417 | CGFloat scalar = 0.0;
418 |
419 | if (normalizedOffset <= 0.5) {
420 | scalar = 1.0;
421 | } else if (normalizedOffset < (self.expansionsNeighbors + 0.5)) {
422 | scalar = ((self.expansionsNeighbors + 0.5) - normalizedOffset)/self.expansionsNeighbors;
423 | }
424 |
425 | CGFloat scaledHeight = self.transformedItemSize.height * scalar + self.collapsedItemHeight * (1.0 - scalar); //lerping
426 | return scaledHeight/self.transformedItemSize.height;
427 | }
428 |
429 | - (CGFloat)translationForOffsetFromFocusPoint:(CGFloat)offset
430 | {
431 | if (!self.evadesFinger) {
432 | return 0.0f;
433 | }
434 |
435 | CGFloat normalizedOffset = fabsf(offset/self.collapsedItemHeight);
436 | CGFloat scalar = 0.0;
437 |
438 | const CGFloat NormalizedBottomRange = FingerTranslationRadius/self.collapsedItemHeight;
439 |
440 | if (normalizedOffset <= 0.5) {
441 | scalar = 1.0;
442 | } else if (normalizedOffset < NormalizedBottomRange) {
443 | scalar = 1.0 - (normalizedOffset - 0.5)/(NormalizedBottomRange - 0.5);
444 | }
445 |
446 | CGFloat val = scalar*scalar*(3.0 - 2.0*scalar); // ease in out on cubic curve
447 |
448 |
449 | return (FingerTranslation * val);
450 | }
451 |
452 | #pragma mark - Layout
453 |
454 | - (void)layoutItemsForOffset:(CGFloat)offset withAnimationDuration:(NSTimeInterval)duration
455 | {
456 | CGFloat expandedHeight = self.transformedItemSize.height;
457 |
458 | CGAffineTransform itemTransform = CGAffineTransformConcat(self.itemTransform,
459 | CGAffineTransformMakeTranslation(self.transformedItemSize.width/2.0, 0.0)
460 | );
461 |
462 | [UIView animateWithDuration:duration animations:^{
463 | NSEnumerationOptions options = 0;
464 | CGFloat sign = 1.0;
465 | __block CGFloat layoutOffset = -self.startOffset + self.centeringOffset;
466 |
467 | if (offset < (self.transformView.bounds.size.height)/2.0) {
468 | options = NSEnumerationReverse;
469 | sign = -1.0;
470 | layoutOffset = self.transformView.bounds.size.height + self.startOffset - self.centeringOffset;
471 | }
472 |
473 | [self.itemContainers enumerateObjectsWithOptions:options usingBlock:^(UIView *container, NSUInteger index, BOOL *stop) {
474 | CGFloat center = [self offsetForIndex:index];
475 | CGFloat distance = offset - center;
476 |
477 | CGFloat scale, tx, ty;
478 |
479 | if (self.state == MCSFishEyeStateExpandedPassive) {
480 | if (index != self.selectedIndex) {
481 | scale = self.collapsedItemScale;
482 | tx = 0.0;
483 | } else {
484 | scale = 1.0f;
485 | tx = self.selectedItemOffset;
486 | }
487 | ty = center;
488 | } else if (self.state == MCSFishEyeStateExpandedActive) {
489 | scale = [self scaleForOffsetFromFocusPoint:distance];
490 | tx = [self translationForOffsetFromFocusPoint:distance];
491 | ty = layoutOffset + sign * scale * expandedHeight * 0.5;
492 | } else {
493 | scale = self.collapsedItemScale;
494 | tx = 0.0f;
495 | ty = center;
496 | }
497 |
498 | container.transform = CGAffineTransformConcat(itemTransform, [self transformWithScale:scale translation:CGPointMake(tx, ty)]);
499 | layoutOffset += sign * scale * expandedHeight;
500 | }];
501 | }];
502 | }
503 |
504 | - (void)layoutWithCurrentState
505 | {
506 | CGFloat offset = self.selectedIndex == NSNotFound ? 0.0f : [self offsetForIndex:self.selectedIndex];
507 | [self layoutItemsForOffset:offset withAnimationDuration:0.2];
508 | }
509 |
510 | #pragma mark - Convinience
511 |
512 | - (void)collapseAnimated:(BOOL)animated notifying:(BOOL)shouldNotify
513 | {
514 | self.state = MCSFishEyeStateCollapsed;
515 | [self layoutItemsForOffset:0.0 withAnimationDuration:animated ? 0.2 : 0.0];
516 |
517 | [self setHighlightedIndex:NSNotFound];
518 | [self setSelectedIndex:NSNotFound withDelegateCalls:YES animated:animated];
519 | }
520 |
521 | - (CGAffineTransform)transformWithScale:(CGFloat)scale translation:(CGPoint)translation
522 | {
523 | CGAffineTransform scaleTransform = CGAffineTransformMakeScale(scale, scale);
524 | CGAffineTransform translationTransform = CGAffineTransformMakeTranslation(translation.x, translation.y);
525 |
526 | return CGAffineTransformConcat(scaleTransform, translationTransform);
527 | }
528 |
529 | #pragma mark - Touch event handling
530 |
531 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
532 | {
533 | CGFloat offset = [self offsetFromTouchSet:touches];
534 |
535 | self.state = MCSFishEyeStateExpandedActive;
536 |
537 | [self layoutItemsForOffset:offset withAnimationDuration:0.2];
538 | [self setSelectedIndex:NSNotFound withDelegateCalls:YES animated:NO];
539 | [self setHighlightedIndex:[self indexForOffset:offset]];
540 | }
541 |
542 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
543 | {
544 | CGFloat offset = [self offsetFromTouchSet:touches];
545 |
546 | [self layoutItemsForOffset:offset withAnimationDuration:0.07];
547 | [self setHighlightedIndex:[self indexForOffset:offset]];
548 | }
549 |
550 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
551 | {
552 | CGFloat offset = [self offsetFromTouchSet:touches];
553 | NSUInteger index = [self indexForOffset:offset];
554 |
555 | if (index != NSNotFound) {
556 | [self setState:MCSFishEyeStateExpandedPassive];
557 | [self setHighlightedIndex:NSNotFound];
558 | [self setSelectedIndex:index withDelegateCalls:YES animated:YES];
559 | [self layoutItemsForOffset:offset withAnimationDuration:0.2];
560 | } else {
561 | [self collapseAnimated:YES notifying:YES];
562 | }
563 | }
564 |
565 | - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
566 | {
567 | [self collapseAnimated:YES notifying:YES];
568 | }
569 |
570 | - (CGFloat)offsetFromTouchSet:(NSSet *)touches
571 | {
572 | UITouch *touch = [touches anyObject];
573 | return [touch locationInView:self.transformView].y;
574 | }
575 |
576 | @end
577 |
--------------------------------------------------------------------------------