├── .gitignore ├── LICENSE ├── MDCFocusView.podspec ├── MDCFocusView ├── MDCFocalPointView.h ├── MDCFocalPointView.m ├── MDCFocusView.h ├── MDCFocusView.m ├── MDCSpotlightView.h └── MDCSpotlightView.m ├── MDCFocusViewTests ├── MDCFocalPointViewSpec.m └── MDCFocusViewSpec.m ├── Podfile ├── Podfile.lock ├── README.md ├── SampleApp.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── SampleApp.xcworkspace └── contents.xcworkspacedata ├── SampleApp ├── AppDelegate.h ├── AppDelegate.m ├── Controllers │ ├── RootTableViewController.h │ ├── RootTableViewController.m │ ├── SpotlightViewController.h │ └── SpotlightViewController.m ├── SampleApp-Info.plist ├── SampleApp-Prefix.pch ├── en.lproj │ └── InfoPlist.strings └── main.m └── SampleAppTests ├── SampleAppTests-Info.plist └── en.lproj └── InfoPlist.strings /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build/ 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | !default.xcworkspace 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | Pods 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 modocache 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /MDCFocusView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'MDCFocusView' 3 | s.version = '0.1.0' 4 | s.summary = 'Apply a "tutorial screen" overlay to your application window.' 5 | s.homepage = 'https://github.com/modocache/MDCFocusView' 6 | s.license = 'MIT' 7 | s.author = { 'modocache' => 'modocache@gmail.com' } 8 | s.source = { :git => 'https://github.com/modocache/MDCFocusView.git', :tag => "v#{s.version}" } 9 | s.source_files = 'MDCFocusView/*.{h,m}' 10 | s.requires_arc = true 11 | s.platform = :ios, '5.0' 12 | s.framework = 'UIKit' 13 | end 14 | -------------------------------------------------------------------------------- /MDCFocusView/MDCFocalPointView.h: -------------------------------------------------------------------------------- 1 | // MDCFocalPointView.h 2 | // 3 | // Copyright (c) 2013 modocache 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining 6 | // a copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be 14 | // included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | 25 | 26 | #import 27 | 28 | 29 | @interface MDCFocalPointView : UIView 30 | 31 | @property (nonatomic, strong, readonly) UIView *focalView; 32 | 33 | - (id)initWithFocalView:(UIView *)focalView; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /MDCFocusView/MDCFocalPointView.m: -------------------------------------------------------------------------------- 1 | // MDCFocalPointView.m 2 | // 3 | // Copyright (c) 2013 modocache 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining 6 | // a copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be 14 | // included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | 25 | 26 | #import "MDCFocalPointView.h" 27 | 28 | 29 | @implementation MDCFocalPointView 30 | 31 | 32 | #pragma mark - Object Initialization 33 | 34 | - (id)initWithFocalView:(UIView *)focalView { 35 | self = [super initWithFrame:focalView.frame]; 36 | if (self) { 37 | _focalView = focalView; 38 | 39 | self.opaque = NO; 40 | self.userInteractionEnabled = NO; 41 | } 42 | return self; 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /MDCFocusView/MDCFocusView.h: -------------------------------------------------------------------------------- 1 | // MDCFocusView.h 2 | // 3 | // Copyright (c) 2013 modocache 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining 6 | // a copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be 14 | // included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | 25 | 26 | #import 27 | 28 | 29 | @interface MDCFocusView : UIView 30 | 31 | @property (nonatomic, assign, readonly, getter = isFocused) BOOL focused; 32 | @property (nonatomic, assign) Class focalPointViewClass; 33 | @property (nonatomic, assign) NSTimeInterval focusDuration; 34 | 35 | - (void)focus:(UIView *)views, ... NS_REQUIRES_NIL_TERMINATION; 36 | - (void)focusOnViews:(NSArray *)views; 37 | - (void)dismiss:(void (^)())completion; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /MDCFocusView/MDCFocusView.m: -------------------------------------------------------------------------------- 1 | // MDCFocusView.m 2 | // 3 | // Copyright (c) 2013 modocache 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining 6 | // a copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be 14 | // included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | 25 | 26 | #import "MDCFocusView.h" 27 | 28 | #import "MDCFocalPointView.h" 29 | #import "MDCSpotlightView.h" 30 | 31 | 32 | @interface MDCFocusView () 33 | @property (nonatomic, strong) NSArray *focii; 34 | @property (nonatomic, assign, getter = isFocused) BOOL focused; 35 | @end 36 | 37 | 38 | @implementation MDCFocusView 39 | 40 | 41 | #pragma mark - Object Initialization 42 | 43 | - (id)init { 44 | UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; 45 | self = [super initWithFrame:keyWindow.frame]; 46 | if (self) { 47 | _focusDuration = 0.5; 48 | _focalPointViewClass = [MDCFocalPointView class]; 49 | 50 | self.userInteractionEnabled = NO; 51 | self.opaque = NO; 52 | self.alpha = 0.0f; 53 | 54 | [[NSNotificationCenter defaultCenter] addObserver:self 55 | selector:@selector(onApplicationDidChangeStatusBarOrientationNotification:) 56 | name:UIApplicationDidChangeStatusBarOrientationNotification 57 | object:nil]; 58 | } 59 | return self; 60 | } 61 | 62 | - (void)dealloc { 63 | [[NSNotificationCenter defaultCenter] removeObserver:self 64 | name:UIApplicationDidChangeStatusBarOrientationNotification 65 | object:nil]; 66 | } 67 | 68 | 69 | #pragma mark - UIView Overrides 70 | 71 | - (void)drawRect:(CGRect)rect { 72 | [[UIColor clearColor] setFill]; 73 | 74 | for (UIView *focus in self.focii) { 75 | UIRectFill(CGRectIntersection(focus.frame, rect)); 76 | } 77 | } 78 | 79 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { 80 | for (MDCFocalPointView *focus in self.focii) { 81 | if (CGRectContainsPoint(focus.frame, point)) { 82 | return focus.focalView; 83 | } 84 | } 85 | 86 | return self.isFocused ? self : nil; 87 | } 88 | 89 | 90 | #pragma mark - Public Interface 91 | 92 | - (void)focus:(UIView *)views, ... { 93 | NSMutableArray *focii = [NSMutableArray new]; 94 | 95 | va_list viewList; 96 | va_start(viewList, views); 97 | for (UIView *view = views; view != nil; view = va_arg(viewList, UIView *)) { 98 | [focii addObject:view]; 99 | } 100 | va_end(viewList); 101 | 102 | [self focusOnViews:[focii copy]]; 103 | } 104 | 105 | - (void)focusOnViews:(NSArray *)views { 106 | NSParameterAssert(views != nil); 107 | 108 | self.focused = YES; 109 | 110 | [[UIApplication sharedApplication].keyWindow addSubview:self]; 111 | [self adjustRotation]; 112 | 113 | NSMutableArray *focii = [NSMutableArray arrayWithCapacity:[views count]]; 114 | 115 | for (UIView *view in views) { 116 | MDCFocalPointView *focalPointView = [[self.focalPointViewClass alloc] initWithFocalView:view]; 117 | [self addSubview:focalPointView]; 118 | focalPointView.frame = [self convertRect:focalPointView.frame 119 | fromView:focalPointView.focalView.superview]; 120 | 121 | [focii addObject:focalPointView]; 122 | } 123 | 124 | self.focii = [focii copy]; 125 | [self setNeedsDisplay]; 126 | 127 | [UIView animateWithDuration:self.focusDuration animations:^{ 128 | self.alpha = 1.0f; 129 | } completion:^(BOOL finished) { 130 | self.userInteractionEnabled = YES; 131 | }]; 132 | } 133 | 134 | - (void)dismiss:(void (^)())completion { 135 | NSAssert(self.isFocused, @"Cannot dismiss when focus is not applied in the first place."); 136 | 137 | [UIView animateWithDuration:self.focusDuration animations:^{ 138 | self.alpha = 0.0f; 139 | } completion:^(BOOL finished) { 140 | for (MDCFocalPointView *view in self.focii) { 141 | [view removeFromSuperview]; 142 | } 143 | self.focii = nil; 144 | 145 | self.userInteractionEnabled = NO; 146 | [self removeFromSuperview]; 147 | 148 | self.focused = NO; 149 | 150 | if (completion) { 151 | completion(); 152 | } 153 | }]; 154 | } 155 | 156 | 157 | #pragma mark - Internal Methods 158 | 159 | - (void)onApplicationDidChangeStatusBarOrientationNotification:(NSNotification *)notification { 160 | if (!self.isFocused) { 161 | return; 162 | } 163 | 164 | NSMutableArray *views = [NSMutableArray new]; 165 | for (MDCFocalPointView *focalPointView in self.focii) { 166 | [views addObject:focalPointView.focalView]; 167 | } 168 | 169 | [self dismiss:^{ 170 | [self adjustRotation]; 171 | [self focusOnViews:[views copy]]; 172 | }]; 173 | } 174 | 175 | - (void)adjustRotation { 176 | UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; 177 | 178 | CGFloat rotationAngle = 0.0f; 179 | switch (orientation) { 180 | case UIInterfaceOrientationPortrait: 181 | rotationAngle = 0.0f; 182 | break; 183 | case UIInterfaceOrientationPortraitUpsideDown: 184 | rotationAngle = M_PI; 185 | break; 186 | case UIInterfaceOrientationLandscapeLeft: 187 | rotationAngle = -M_PI/2.0f; 188 | break; 189 | case UIInterfaceOrientationLandscapeRight: 190 | rotationAngle = M_PI/2.0f; 191 | break; 192 | } 193 | 194 | self.transform = CGAffineTransformMakeRotation(rotationAngle); 195 | self.frame = self.superview.frame; 196 | } 197 | 198 | @end 199 | -------------------------------------------------------------------------------- /MDCFocusView/MDCSpotlightView.h: -------------------------------------------------------------------------------- 1 | // MDCSpotlightView.h 2 | // 3 | // Copyright (c) 2013 modocache 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining 6 | // a copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be 14 | // included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | 25 | 26 | #import "MDCFocalPointView.h" 27 | 28 | 29 | @interface MDCSpotlightView : MDCFocalPointView 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /MDCFocusView/MDCSpotlightView.m: -------------------------------------------------------------------------------- 1 | // MDCSpotlightView.m 2 | // 3 | // Copyright (c) 2013 modocache 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining 6 | // a copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be 14 | // included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | 25 | 26 | #import "MDCSpotlightView.h" 27 | 28 | 29 | @implementation MDCSpotlightView 30 | 31 | 32 | #pragma mark - MDCFocalPointView Overrides 33 | 34 | - (id)initWithFocalView:(UIView *)focalView { 35 | self = [super initWithFocalView:focalView]; 36 | if (self) { 37 | CGRect focalRect = focalView.frame; 38 | 39 | CGFloat margin = MAX(focalRect.size.width, focalRect.size.height); 40 | self.frame = CGRectMake(focalRect.origin.x - margin/2, 41 | focalRect.origin.y - margin/2, 42 | focalRect.size.width + margin, 43 | focalRect.size.height + margin); 44 | } 45 | return self; 46 | } 47 | 48 | 49 | #pragma mark - UIView Overrides 50 | 51 | - (void)drawRect:(CGRect)rect { 52 | CGContextRef context = UIGraphicsGetCurrentContext(); 53 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 54 | 55 | CGFloat locations[3] = { 0.0f, 0.5f, 1.0f }; 56 | CFArrayRef colors = (__bridge CFArrayRef)@[ 57 | (__bridge id)[UIColor clearColor].CGColor, 58 | (__bridge id)[UIColor clearColor].CGColor, 59 | (__bridge id)self.superview.backgroundColor.CGColor 60 | ]; 61 | 62 | CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, colors, locations); 63 | 64 | CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); 65 | CGFloat radius = MIN(rect.size.width/2, rect.size.height/2); 66 | CGContextDrawRadialGradient(context, gradient, center, 0, center, radius, kCGGradientDrawsAfterEndLocation); 67 | 68 | CGGradientRelease(gradient); 69 | CGColorSpaceRelease(colorSpace); 70 | } 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /MDCFocusViewTests/MDCFocalPointViewSpec.m: -------------------------------------------------------------------------------- 1 | // MDCFocalPointViewSpec.m 2 | // 3 | // Copyright (c) 2013 modocache 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining 6 | // a copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be 14 | // included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | 25 | 26 | #import 27 | #import "MDCFocalPointView.h" 28 | 29 | 30 | SPEC_BEGIN(MDCFocalPointViewSpec) 31 | 32 | describe(@"MDCFocalPointView", ^{ 33 | __block UIView *focalView = nil; 34 | __block MDCFocalPointView *focalPointView = nil; 35 | beforeEach(^{ 36 | focalView = [UIView new]; 37 | focalPointView = [[MDCFocalPointView alloc] initWithFocalView:focalView]; 38 | }); 39 | 40 | describe(@"-initWithFocalView:", ^{ 41 | it(@"sets the focalView", ^{ 42 | [[focalPointView.focalView should] equal:focalView]; 43 | }); 44 | 45 | it(@"does not swallow touches", ^{ 46 | [[theValue(focalPointView.userInteractionEnabled) should] beNo]; 47 | }); 48 | }); 49 | }); 50 | 51 | SPEC_END 52 | 53 | 54 | -------------------------------------------------------------------------------- /MDCFocusViewTests/MDCFocusViewSpec.m: -------------------------------------------------------------------------------- 1 | // MDCFocusViewSpec.m 2 | // 3 | // Copyright (c) 2013 modocache 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining 6 | // a copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be 14 | // included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | 25 | 26 | #import 27 | #import "MDCFocusView.h" 28 | 29 | #import "MDCFocalPointView.h" 30 | 31 | 32 | SPEC_BEGIN(MDCFocusViewSpec) 33 | 34 | describe(@"MDCFocusView", ^{ 35 | __block MDCFocusView *focusView = nil; 36 | beforeEach(^{ 37 | focusView = [MDCFocusView new]; 38 | }); 39 | 40 | describe(@"-init", ^{ 41 | it(@"is hidden", ^{ 42 | [[theValue(focusView.alpha) should] equal:theValue(0.0f)]; 43 | }); 44 | 45 | it(@"does not swallow touches", ^{ 46 | [[theValue(focusView.userInteractionEnabled) should] beNo]; 47 | }); 48 | }); 49 | 50 | describe(@"-focusDuration", ^{ 51 | it(@"determines the duration of animation used in -focus:", ^{ 52 | focusView.focusDuration = 10.0; 53 | 54 | [[UIView should] receive:@selector(animateWithDuration:animations:completion:) 55 | withArguments:theValue(10.0), [KWAny any], [KWAny any]]; 56 | 57 | [focusView focusOnViews:@[]]; 58 | }); 59 | }); 60 | 61 | describe(@"-focus:", ^{ 62 | it(@"applies a focus to the views in the list", ^{ 63 | UIView *view = [UIView new]; 64 | UIButton *button = [UIButton new]; 65 | 66 | [[focusView should] receive:@selector(focusOnViews:) withArguments:@[view, button]]; 67 | 68 | [focusView focus:view, button, nil]; 69 | }); 70 | }); 71 | 72 | describe(@"-focusOnViews:", ^{ 73 | context(@"views is nil", ^{ 74 | it(@"raises", ^{ 75 | [[theBlock(^{ 76 | [focusView focusOnViews:nil]; 77 | }) should] raiseWithName:NSInternalInconsistencyException 78 | reason:@"Invalid parameter not satisfying: views != nil"]; 79 | }); 80 | }); 81 | 82 | context(@"views is not nil", ^{ 83 | __block NSMutableArray *views = nil; 84 | beforeEach(^{ 85 | views = [NSMutableArray new]; 86 | }); 87 | 88 | it(@"becomes focused", ^{ 89 | [focusView focusOnViews:views]; 90 | [[theValue(focusView.isFocused) should] beYes]; 91 | }); 92 | 93 | it(@"requests to be redrawn", ^{ 94 | [[focusView should] receive:@selector(setNeedsDisplay)]; 95 | [focusView focusOnViews:views]; 96 | }); 97 | 98 | it(@"triggers an animation", ^{ 99 | [[UIView should] receive:@selector(animateWithDuration:animations:completion:)]; 100 | [focusView focusOnViews:views]; 101 | }); 102 | 103 | it(@"begins to swallow touches", ^{ 104 | [[focusView shouldEventually] receive:@selector(setUserInteractionEnabled:) 105 | withArguments:theValue(YES)]; 106 | [focusView focusOnViews:views]; 107 | }); 108 | }); 109 | }); 110 | 111 | describe(@"-dismiss", ^{ 112 | context(@"when not focused in the first place", ^{ 113 | it(@"raises", ^{ 114 | [[theBlock(^{ 115 | [focusView dismiss:nil]; 116 | }) should] raiseWithName:NSInternalInconsistencyException 117 | reason:@"Cannot dismiss when focus is not applied " 118 | @"in the first place."]; 119 | }); 120 | }); 121 | 122 | context(@"when in a focused state", ^{ 123 | beforeEach(^{ 124 | [focusView focusOnViews:@[]]; 125 | }); 126 | 127 | it(@"stops swallowing touches", ^{ 128 | [[focusView shouldEventually] receive:@selector(setUserInteractionEnabled:) 129 | withArguments:theValue(NO)]; 130 | [focusView dismiss:nil]; 131 | }); 132 | 133 | it(@"loses focus", ^{ 134 | [[focusView shouldEventually] receive:@selector(setFocused:) 135 | withArguments:theValue(NO)]; 136 | [focusView dismiss:nil]; 137 | }); 138 | }); 139 | }); 140 | }); 141 | 142 | SPEC_END 143 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '5.0' 2 | 3 | target :SampleAppTests, exclusive: true do 4 | pod 'Kiwi' 5 | end 6 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | 2 | PODS: 3 | - Kiwi (2.0.5) 4 | 5 | DEPENDENCIES: 6 | - Kiwi 7 | 8 | SPEC CHECKSUMS: 9 | Kiwi: 89ade77eb49c889178cb2779e02514a7b9b9449e 10 | 11 | COCOAPODS: 0.16.2 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MDCFocusView 2 | 3 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/modocache/MDCFocusView/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 4 | 5 | Apply a "tutorial screen" overlay to your application window. 6 | `MDCFocusView` can apply a focus on an arbitrary number of views, 7 | and prevents users from tapping views not currently focused on. 8 | 9 | ![](http://i.imgflip.com/og57.gif) 10 | 11 | Check out [a demo on Vimeo](http://vimeo.com/60418239). 12 | 13 | ## How to Use 14 | 15 | You can install the project using Cocoapods, by placing `pod MDCFocusView` 16 | in your `Podfile`. After installing: 17 | 18 | ```objc 19 | // Initialize MDCFocusView and customize its background color 20 | MDCFocusView *focusView = [MDCFocusView new]; 21 | focusView.backgroundColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f]; 22 | 23 | // Register a MDCFocalPointView subclass to "wrap" focal points 24 | focusView.focalPointViewClass = [MDCSpotlightView class]; 25 | 26 | // Add any number of custom views to MDCFocusView 27 | [focusView addSubview:[self buildLabel]]; 28 | 29 | // Present the focus view 30 | [self.focusView focus:someView, anotherView, nil]; 31 | ``` 32 | 33 | Please see the sample app for an example. 34 | 35 | ## Features 36 | 37 | - Because `MDCFocusView` uses `MDCFocalPointView` to wrap focal points, 38 | it is highly extensible--to create your own focus effect, simply subclass 39 | `MDCFocalPointView` and implement any custom drawing behavior in `drawRect:`. 40 | Please see `MDCSpotlightView` for an example. 41 | 42 | ## Limitations (or Ways to Contribute to this Project) 43 | 44 | - Currently `MDCFocusView` can only be applied to the entire application 45 | window. Ideally, any arbirary view should be able to add `MDCFocusView` as 46 | a subview. 47 | - Currently only `MDCFocalPointView` and `MDCSpotlightView` are available, 48 | although I would like to make more. It would be nice, for example, if 49 | a Gaussian blur could be applied everywhere but the focal points. 50 | - See the GitHub issues for more bugs/feature requests you can help with. 51 | 52 | -------------------------------------------------------------------------------- /SampleApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4DD2B36C6D36455F8E6027BA /* libPods-SampleAppTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4728F34AFD2545EE8094B2F9 /* libPods-SampleAppTests.a */; }; 11 | DA25503216D91EE500BB33EB /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA25503116D91EE500BB33EB /* UIKit.framework */; }; 12 | DA25503416D91EE500BB33EB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA25503316D91EE500BB33EB /* Foundation.framework */; }; 13 | DA25503616D91EE500BB33EB /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA25503516D91EE500BB33EB /* CoreGraphics.framework */; }; 14 | DA25503C16D91EE500BB33EB /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DA25503A16D91EE500BB33EB /* InfoPlist.strings */; }; 15 | DA25503E16D91EE500BB33EB /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DA25503D16D91EE500BB33EB /* main.m */; }; 16 | DA25504216D91EE500BB33EB /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DA25504116D91EE500BB33EB /* AppDelegate.m */; }; 17 | DA25505016D91EE600BB33EB /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA25504F16D91EE600BB33EB /* SenTestingKit.framework */; }; 18 | DA25505116D91EE600BB33EB /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA25503116D91EE500BB33EB /* UIKit.framework */; }; 19 | DA25505216D91EE600BB33EB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA25503316D91EE500BB33EB /* Foundation.framework */; }; 20 | DA25505A16D91EE600BB33EB /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DA25505816D91EE600BB33EB /* InfoPlist.strings */; }; 21 | DA25508416D921A900BB33EB /* RootTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA25508316D921A900BB33EB /* RootTableViewController.m */; }; 22 | DA25508716D9241400BB33EB /* SpotlightViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA25508616D9241400BB33EB /* SpotlightViewController.m */; }; 23 | DA25508F16D92E6700BB33EB /* MDCFocalPointView.m in Sources */ = {isa = PBXBuildFile; fileRef = DA25508A16D92E6700BB33EB /* MDCFocalPointView.m */; }; 24 | DA25509016D92E6700BB33EB /* MDCFocusView.m in Sources */ = {isa = PBXBuildFile; fileRef = DA25508C16D92E6700BB33EB /* MDCFocusView.m */; }; 25 | DA25509116D92E6700BB33EB /* MDCSpotlightView.m in Sources */ = {isa = PBXBuildFile; fileRef = DA25508E16D92E6700BB33EB /* MDCSpotlightView.m */; }; 26 | DA25509716D970BC00BB33EB /* MDCFocusViewSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = DA25509616D970BC00BB33EB /* MDCFocusViewSpec.m */; }; 27 | DA25509816D9772100BB33EB /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA25503516D91EE500BB33EB /* CoreGraphics.framework */; }; 28 | DA25509A16D97FB300BB33EB /* MDCFocalPointViewSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = DA25509916D97FB300BB33EB /* MDCFocalPointViewSpec.m */; }; 29 | /* End PBXBuildFile section */ 30 | 31 | /* Begin PBXContainerItemProxy section */ 32 | DA25505316D91EE600BB33EB /* PBXContainerItemProxy */ = { 33 | isa = PBXContainerItemProxy; 34 | containerPortal = DA25502416D91EE500BB33EB /* Project object */; 35 | proxyType = 1; 36 | remoteGlobalIDString = DA25502C16D91EE500BB33EB; 37 | remoteInfo = SampleApp; 38 | }; 39 | /* End PBXContainerItemProxy section */ 40 | 41 | /* Begin PBXFileReference section */ 42 | 4728F34AFD2545EE8094B2F9 /* libPods-SampleAppTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SampleAppTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | DA25502D16D91EE500BB33EB /* SampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | DA25503116D91EE500BB33EB /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 45 | DA25503316D91EE500BB33EB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 46 | DA25503516D91EE500BB33EB /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 47 | DA25503916D91EE500BB33EB /* SampleApp-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SampleApp-Info.plist"; sourceTree = ""; }; 48 | DA25503B16D91EE500BB33EB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 49 | DA25503D16D91EE500BB33EB /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 50 | DA25503F16D91EE500BB33EB /* SampleApp-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SampleApp-Prefix.pch"; sourceTree = ""; }; 51 | DA25504016D91EE500BB33EB /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 52 | DA25504116D91EE500BB33EB /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 53 | DA25504E16D91EE600BB33EB /* SampleAppTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SampleAppTests.octest; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | DA25504F16D91EE600BB33EB /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; 55 | DA25505716D91EE600BB33EB /* SampleAppTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SampleAppTests-Info.plist"; sourceTree = ""; }; 56 | DA25505916D91EE600BB33EB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 57 | DA25508216D921A900BB33EB /* RootTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RootTableViewController.h; sourceTree = ""; }; 58 | DA25508316D921A900BB33EB /* RootTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RootTableViewController.m; sourceTree = ""; }; 59 | DA25508516D9241400BB33EB /* SpotlightViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SpotlightViewController.h; sourceTree = ""; }; 60 | DA25508616D9241400BB33EB /* SpotlightViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SpotlightViewController.m; sourceTree = ""; }; 61 | DA25508916D92E6700BB33EB /* MDCFocalPointView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MDCFocalPointView.h; sourceTree = ""; }; 62 | DA25508A16D92E6700BB33EB /* MDCFocalPointView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MDCFocalPointView.m; sourceTree = ""; }; 63 | DA25508B16D92E6700BB33EB /* MDCFocusView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MDCFocusView.h; sourceTree = ""; }; 64 | DA25508C16D92E6700BB33EB /* MDCFocusView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MDCFocusView.m; sourceTree = ""; }; 65 | DA25508D16D92E6700BB33EB /* MDCSpotlightView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MDCSpotlightView.h; sourceTree = ""; }; 66 | DA25508E16D92E6700BB33EB /* MDCSpotlightView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MDCSpotlightView.m; sourceTree = ""; }; 67 | DA25509616D970BC00BB33EB /* MDCFocusViewSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MDCFocusViewSpec.m; sourceTree = ""; }; 68 | DA25509916D97FB300BB33EB /* MDCFocalPointViewSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MDCFocalPointViewSpec.m; sourceTree = ""; }; 69 | FF73F217AEFF401EB2608E7E /* Pods-SampleAppTests.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleAppTests.xcconfig"; path = "Pods/Pods-SampleAppTests.xcconfig"; sourceTree = SOURCE_ROOT; }; 70 | /* End PBXFileReference section */ 71 | 72 | /* Begin PBXFrameworksBuildPhase section */ 73 | DA25502A16D91EE500BB33EB /* Frameworks */ = { 74 | isa = PBXFrameworksBuildPhase; 75 | buildActionMask = 2147483647; 76 | files = ( 77 | DA25503216D91EE500BB33EB /* UIKit.framework in Frameworks */, 78 | DA25503416D91EE500BB33EB /* Foundation.framework in Frameworks */, 79 | DA25503616D91EE500BB33EB /* CoreGraphics.framework in Frameworks */, 80 | ); 81 | runOnlyForDeploymentPostprocessing = 0; 82 | }; 83 | DA25504A16D91EE600BB33EB /* Frameworks */ = { 84 | isa = PBXFrameworksBuildPhase; 85 | buildActionMask = 2147483647; 86 | files = ( 87 | DA25509816D9772100BB33EB /* CoreGraphics.framework in Frameworks */, 88 | DA25505016D91EE600BB33EB /* SenTestingKit.framework in Frameworks */, 89 | DA25505116D91EE600BB33EB /* UIKit.framework in Frameworks */, 90 | DA25505216D91EE600BB33EB /* Foundation.framework in Frameworks */, 91 | 4DD2B36C6D36455F8E6027BA /* libPods-SampleAppTests.a in Frameworks */, 92 | ); 93 | runOnlyForDeploymentPostprocessing = 0; 94 | }; 95 | /* End PBXFrameworksBuildPhase section */ 96 | 97 | /* Begin PBXGroup section */ 98 | DA25502216D91EE500BB33EB = { 99 | isa = PBXGroup; 100 | children = ( 101 | DA25503716D91EE500BB33EB /* SampleApp */, 102 | DA25505516D91EE600BB33EB /* SampleAppTests */, 103 | DA25503016D91EE500BB33EB /* Frameworks */, 104 | DA25502E16D91EE500BB33EB /* Products */, 105 | FF73F217AEFF401EB2608E7E /* Pods-SampleAppTests.xcconfig */, 106 | ); 107 | sourceTree = ""; 108 | }; 109 | DA25502E16D91EE500BB33EB /* Products */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | DA25502D16D91EE500BB33EB /* SampleApp.app */, 113 | DA25504E16D91EE600BB33EB /* SampleAppTests.octest */, 114 | ); 115 | name = Products; 116 | sourceTree = ""; 117 | }; 118 | DA25503016D91EE500BB33EB /* Frameworks */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | DA25503116D91EE500BB33EB /* UIKit.framework */, 122 | DA25503316D91EE500BB33EB /* Foundation.framework */, 123 | DA25503516D91EE500BB33EB /* CoreGraphics.framework */, 124 | DA25504F16D91EE600BB33EB /* SenTestingKit.framework */, 125 | 4728F34AFD2545EE8094B2F9 /* libPods-SampleAppTests.a */, 126 | ); 127 | name = Frameworks; 128 | sourceTree = ""; 129 | }; 130 | DA25503716D91EE500BB33EB /* SampleApp */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | DA25508816D92E6700BB33EB /* MDCFocusView */, 134 | DA25504016D91EE500BB33EB /* AppDelegate.h */, 135 | DA25504116D91EE500BB33EB /* AppDelegate.m */, 136 | DA25508116D9216100BB33EB /* Controllers */, 137 | DA25503816D91EE500BB33EB /* Supporting Files */, 138 | ); 139 | path = SampleApp; 140 | sourceTree = ""; 141 | }; 142 | DA25503816D91EE500BB33EB /* Supporting Files */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | DA25503916D91EE500BB33EB /* SampleApp-Info.plist */, 146 | DA25503A16D91EE500BB33EB /* InfoPlist.strings */, 147 | DA25503D16D91EE500BB33EB /* main.m */, 148 | DA25503F16D91EE500BB33EB /* SampleApp-Prefix.pch */, 149 | ); 150 | name = "Supporting Files"; 151 | sourceTree = ""; 152 | }; 153 | DA25505516D91EE600BB33EB /* SampleAppTests */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | DA25509516D970AF00BB33EB /* MDCFocusViewTests */, 157 | DA25505616D91EE600BB33EB /* Supporting Files */, 158 | ); 159 | path = SampleAppTests; 160 | sourceTree = ""; 161 | }; 162 | DA25505616D91EE600BB33EB /* Supporting Files */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | DA25505716D91EE600BB33EB /* SampleAppTests-Info.plist */, 166 | DA25505816D91EE600BB33EB /* InfoPlist.strings */, 167 | ); 168 | name = "Supporting Files"; 169 | sourceTree = ""; 170 | }; 171 | DA25508116D9216100BB33EB /* Controllers */ = { 172 | isa = PBXGroup; 173 | children = ( 174 | DA25508216D921A900BB33EB /* RootTableViewController.h */, 175 | DA25508316D921A900BB33EB /* RootTableViewController.m */, 176 | DA25508516D9241400BB33EB /* SpotlightViewController.h */, 177 | DA25508616D9241400BB33EB /* SpotlightViewController.m */, 178 | ); 179 | path = Controllers; 180 | sourceTree = ""; 181 | }; 182 | DA25508816D92E6700BB33EB /* MDCFocusView */ = { 183 | isa = PBXGroup; 184 | children = ( 185 | DA25508B16D92E6700BB33EB /* MDCFocusView.h */, 186 | DA25508C16D92E6700BB33EB /* MDCFocusView.m */, 187 | DA25508916D92E6700BB33EB /* MDCFocalPointView.h */, 188 | DA25508A16D92E6700BB33EB /* MDCFocalPointView.m */, 189 | DA25508D16D92E6700BB33EB /* MDCSpotlightView.h */, 190 | DA25508E16D92E6700BB33EB /* MDCSpotlightView.m */, 191 | ); 192 | path = MDCFocusView; 193 | sourceTree = SOURCE_ROOT; 194 | }; 195 | DA25509516D970AF00BB33EB /* MDCFocusViewTests */ = { 196 | isa = PBXGroup; 197 | children = ( 198 | DA25509616D970BC00BB33EB /* MDCFocusViewSpec.m */, 199 | DA25509916D97FB300BB33EB /* MDCFocalPointViewSpec.m */, 200 | ); 201 | path = MDCFocusViewTests; 202 | sourceTree = SOURCE_ROOT; 203 | }; 204 | /* End PBXGroup section */ 205 | 206 | /* Begin PBXNativeTarget section */ 207 | DA25502C16D91EE500BB33EB /* SampleApp */ = { 208 | isa = PBXNativeTarget; 209 | buildConfigurationList = DA25506016D91EE600BB33EB /* Build configuration list for PBXNativeTarget "SampleApp" */; 210 | buildPhases = ( 211 | DA25502916D91EE500BB33EB /* Sources */, 212 | DA25502A16D91EE500BB33EB /* Frameworks */, 213 | DA25502B16D91EE500BB33EB /* Resources */, 214 | ); 215 | buildRules = ( 216 | ); 217 | dependencies = ( 218 | ); 219 | name = SampleApp; 220 | productName = SampleApp; 221 | productReference = DA25502D16D91EE500BB33EB /* SampleApp.app */; 222 | productType = "com.apple.product-type.application"; 223 | }; 224 | DA25504D16D91EE600BB33EB /* SampleAppTests */ = { 225 | isa = PBXNativeTarget; 226 | buildConfigurationList = DA25506316D91EE600BB33EB /* Build configuration list for PBXNativeTarget "SampleAppTests" */; 227 | buildPhases = ( 228 | DA25504916D91EE600BB33EB /* Sources */, 229 | DA25504A16D91EE600BB33EB /* Frameworks */, 230 | DA25504B16D91EE600BB33EB /* Resources */, 231 | DA25504C16D91EE600BB33EB /* ShellScript */, 232 | 905A3E7E13BB460D9CBEE507 /* Copy Pods Resources */, 233 | ); 234 | buildRules = ( 235 | ); 236 | dependencies = ( 237 | DA25505416D91EE600BB33EB /* PBXTargetDependency */, 238 | ); 239 | name = SampleAppTests; 240 | productName = SampleAppTests; 241 | productReference = DA25504E16D91EE600BB33EB /* SampleAppTests.octest */; 242 | productType = "com.apple.product-type.bundle"; 243 | }; 244 | /* End PBXNativeTarget section */ 245 | 246 | /* Begin PBXProject section */ 247 | DA25502416D91EE500BB33EB /* Project object */ = { 248 | isa = PBXProject; 249 | attributes = { 250 | LastUpgradeCheck = 0450; 251 | ORGANIZATIONNAME = modocache; 252 | }; 253 | buildConfigurationList = DA25502716D91EE500BB33EB /* Build configuration list for PBXProject "SampleApp" */; 254 | compatibilityVersion = "Xcode 3.2"; 255 | developmentRegion = English; 256 | hasScannedForEncodings = 0; 257 | knownRegions = ( 258 | en, 259 | ); 260 | mainGroup = DA25502216D91EE500BB33EB; 261 | productRefGroup = DA25502E16D91EE500BB33EB /* Products */; 262 | projectDirPath = ""; 263 | projectRoot = ""; 264 | targets = ( 265 | DA25502C16D91EE500BB33EB /* SampleApp */, 266 | DA25504D16D91EE600BB33EB /* SampleAppTests */, 267 | ); 268 | }; 269 | /* End PBXProject section */ 270 | 271 | /* Begin PBXResourcesBuildPhase section */ 272 | DA25502B16D91EE500BB33EB /* Resources */ = { 273 | isa = PBXResourcesBuildPhase; 274 | buildActionMask = 2147483647; 275 | files = ( 276 | DA25503C16D91EE500BB33EB /* InfoPlist.strings in Resources */, 277 | ); 278 | runOnlyForDeploymentPostprocessing = 0; 279 | }; 280 | DA25504B16D91EE600BB33EB /* Resources */ = { 281 | isa = PBXResourcesBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | DA25505A16D91EE600BB33EB /* InfoPlist.strings in Resources */, 285 | ); 286 | runOnlyForDeploymentPostprocessing = 0; 287 | }; 288 | /* End PBXResourcesBuildPhase section */ 289 | 290 | /* Begin PBXShellScriptBuildPhase section */ 291 | 905A3E7E13BB460D9CBEE507 /* Copy Pods Resources */ = { 292 | isa = PBXShellScriptBuildPhase; 293 | buildActionMask = 2147483647; 294 | files = ( 295 | ); 296 | inputPaths = ( 297 | ); 298 | name = "Copy Pods Resources"; 299 | outputPaths = ( 300 | ); 301 | runOnlyForDeploymentPostprocessing = 0; 302 | shellPath = /bin/sh; 303 | shellScript = "\"${SRCROOT}/Pods/Pods-SampleAppTests-resources.sh\"\n"; 304 | }; 305 | DA25504C16D91EE600BB33EB /* ShellScript */ = { 306 | isa = PBXShellScriptBuildPhase; 307 | buildActionMask = 2147483647; 308 | files = ( 309 | ); 310 | inputPaths = ( 311 | ); 312 | outputPaths = ( 313 | ); 314 | runOnlyForDeploymentPostprocessing = 0; 315 | shellPath = /bin/sh; 316 | shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n"; 317 | }; 318 | /* End PBXShellScriptBuildPhase section */ 319 | 320 | /* Begin PBXSourcesBuildPhase section */ 321 | DA25502916D91EE500BB33EB /* Sources */ = { 322 | isa = PBXSourcesBuildPhase; 323 | buildActionMask = 2147483647; 324 | files = ( 325 | DA25503E16D91EE500BB33EB /* main.m in Sources */, 326 | DA25504216D91EE500BB33EB /* AppDelegate.m in Sources */, 327 | DA25508416D921A900BB33EB /* RootTableViewController.m in Sources */, 328 | DA25508716D9241400BB33EB /* SpotlightViewController.m in Sources */, 329 | DA25508F16D92E6700BB33EB /* MDCFocalPointView.m in Sources */, 330 | DA25509016D92E6700BB33EB /* MDCFocusView.m in Sources */, 331 | DA25509116D92E6700BB33EB /* MDCSpotlightView.m in Sources */, 332 | ); 333 | runOnlyForDeploymentPostprocessing = 0; 334 | }; 335 | DA25504916D91EE600BB33EB /* Sources */ = { 336 | isa = PBXSourcesBuildPhase; 337 | buildActionMask = 2147483647; 338 | files = ( 339 | DA25509716D970BC00BB33EB /* MDCFocusViewSpec.m in Sources */, 340 | DA25509A16D97FB300BB33EB /* MDCFocalPointViewSpec.m in Sources */, 341 | ); 342 | runOnlyForDeploymentPostprocessing = 0; 343 | }; 344 | /* End PBXSourcesBuildPhase section */ 345 | 346 | /* Begin PBXTargetDependency section */ 347 | DA25505416D91EE600BB33EB /* PBXTargetDependency */ = { 348 | isa = PBXTargetDependency; 349 | target = DA25502C16D91EE500BB33EB /* SampleApp */; 350 | targetProxy = DA25505316D91EE600BB33EB /* PBXContainerItemProxy */; 351 | }; 352 | /* End PBXTargetDependency section */ 353 | 354 | /* Begin PBXVariantGroup section */ 355 | DA25503A16D91EE500BB33EB /* InfoPlist.strings */ = { 356 | isa = PBXVariantGroup; 357 | children = ( 358 | DA25503B16D91EE500BB33EB /* en */, 359 | ); 360 | name = InfoPlist.strings; 361 | sourceTree = ""; 362 | }; 363 | DA25505816D91EE600BB33EB /* InfoPlist.strings */ = { 364 | isa = PBXVariantGroup; 365 | children = ( 366 | DA25505916D91EE600BB33EB /* en */, 367 | ); 368 | name = InfoPlist.strings; 369 | sourceTree = ""; 370 | }; 371 | /* End PBXVariantGroup section */ 372 | 373 | /* Begin XCBuildConfiguration section */ 374 | DA25505E16D91EE600BB33EB /* Debug */ = { 375 | isa = XCBuildConfiguration; 376 | buildSettings = { 377 | ALWAYS_SEARCH_USER_PATHS = NO; 378 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 379 | CLANG_CXX_LIBRARY = "libc++"; 380 | CLANG_ENABLE_OBJC_ARC = YES; 381 | CLANG_WARN_EMPTY_BODY = YES; 382 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 383 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 384 | COPY_PHASE_STRIP = NO; 385 | GCC_C_LANGUAGE_STANDARD = gnu99; 386 | GCC_DYNAMIC_NO_PIC = NO; 387 | GCC_OPTIMIZATION_LEVEL = 0; 388 | GCC_PREPROCESSOR_DEFINITIONS = ( 389 | "DEBUG=1", 390 | "$(inherited)", 391 | ); 392 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 393 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 394 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 395 | GCC_WARN_UNUSED_VARIABLE = YES; 396 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 397 | ONLY_ACTIVE_ARCH = YES; 398 | SDKROOT = iphoneos; 399 | TARGETED_DEVICE_FAMILY = "1,2"; 400 | }; 401 | name = Debug; 402 | }; 403 | DA25505F16D91EE600BB33EB /* Release */ = { 404 | isa = XCBuildConfiguration; 405 | buildSettings = { 406 | ALWAYS_SEARCH_USER_PATHS = NO; 407 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 408 | CLANG_CXX_LIBRARY = "libc++"; 409 | CLANG_ENABLE_OBJC_ARC = YES; 410 | CLANG_WARN_EMPTY_BODY = YES; 411 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 412 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 413 | COPY_PHASE_STRIP = YES; 414 | GCC_C_LANGUAGE_STANDARD = gnu99; 415 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 416 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 417 | GCC_WARN_UNUSED_VARIABLE = YES; 418 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 419 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 420 | SDKROOT = iphoneos; 421 | TARGETED_DEVICE_FAMILY = "1,2"; 422 | VALIDATE_PRODUCT = YES; 423 | }; 424 | name = Release; 425 | }; 426 | DA25506116D91EE600BB33EB /* Debug */ = { 427 | isa = XCBuildConfiguration; 428 | buildSettings = { 429 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 430 | GCC_PREFIX_HEADER = "SampleApp/SampleApp-Prefix.pch"; 431 | INFOPLIST_FILE = "SampleApp/SampleApp-Info.plist"; 432 | PRODUCT_NAME = "$(TARGET_NAME)"; 433 | TARGETED_DEVICE_FAMILY = 2; 434 | WRAPPER_EXTENSION = app; 435 | }; 436 | name = Debug; 437 | }; 438 | DA25506216D91EE600BB33EB /* Release */ = { 439 | isa = XCBuildConfiguration; 440 | buildSettings = { 441 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 442 | GCC_PREFIX_HEADER = "SampleApp/SampleApp-Prefix.pch"; 443 | INFOPLIST_FILE = "SampleApp/SampleApp-Info.plist"; 444 | PRODUCT_NAME = "$(TARGET_NAME)"; 445 | TARGETED_DEVICE_FAMILY = 2; 446 | WRAPPER_EXTENSION = app; 447 | }; 448 | name = Release; 449 | }; 450 | DA25506416D91EE600BB33EB /* Debug */ = { 451 | isa = XCBuildConfiguration; 452 | baseConfigurationReference = FF73F217AEFF401EB2608E7E /* Pods-SampleAppTests.xcconfig */; 453 | buildSettings = { 454 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SampleApp.app/SampleApp"; 455 | FRAMEWORK_SEARCH_PATHS = ( 456 | "\"$(SDKROOT)/Developer/Library/Frameworks\"", 457 | "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", 458 | ); 459 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 460 | GCC_PREFIX_HEADER = "SampleApp/SampleApp-Prefix.pch"; 461 | INFOPLIST_FILE = "SampleAppTests/SampleAppTests-Info.plist"; 462 | PRODUCT_NAME = "$(TARGET_NAME)"; 463 | TEST_HOST = "$(BUNDLE_LOADER)"; 464 | WRAPPER_EXTENSION = octest; 465 | }; 466 | name = Debug; 467 | }; 468 | DA25506516D91EE600BB33EB /* Release */ = { 469 | isa = XCBuildConfiguration; 470 | baseConfigurationReference = FF73F217AEFF401EB2608E7E /* Pods-SampleAppTests.xcconfig */; 471 | buildSettings = { 472 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SampleApp.app/SampleApp"; 473 | FRAMEWORK_SEARCH_PATHS = ( 474 | "\"$(SDKROOT)/Developer/Library/Frameworks\"", 475 | "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", 476 | ); 477 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 478 | GCC_PREFIX_HEADER = "SampleApp/SampleApp-Prefix.pch"; 479 | INFOPLIST_FILE = "SampleAppTests/SampleAppTests-Info.plist"; 480 | PRODUCT_NAME = "$(TARGET_NAME)"; 481 | TEST_HOST = "$(BUNDLE_LOADER)"; 482 | WRAPPER_EXTENSION = octest; 483 | }; 484 | name = Release; 485 | }; 486 | /* End XCBuildConfiguration section */ 487 | 488 | /* Begin XCConfigurationList section */ 489 | DA25502716D91EE500BB33EB /* Build configuration list for PBXProject "SampleApp" */ = { 490 | isa = XCConfigurationList; 491 | buildConfigurations = ( 492 | DA25505E16D91EE600BB33EB /* Debug */, 493 | DA25505F16D91EE600BB33EB /* Release */, 494 | ); 495 | defaultConfigurationIsVisible = 0; 496 | defaultConfigurationName = Release; 497 | }; 498 | DA25506016D91EE600BB33EB /* Build configuration list for PBXNativeTarget "SampleApp" */ = { 499 | isa = XCConfigurationList; 500 | buildConfigurations = ( 501 | DA25506116D91EE600BB33EB /* Debug */, 502 | DA25506216D91EE600BB33EB /* Release */, 503 | ); 504 | defaultConfigurationIsVisible = 0; 505 | defaultConfigurationName = Release; 506 | }; 507 | DA25506316D91EE600BB33EB /* Build configuration list for PBXNativeTarget "SampleAppTests" */ = { 508 | isa = XCConfigurationList; 509 | buildConfigurations = ( 510 | DA25506416D91EE600BB33EB /* Debug */, 511 | DA25506516D91EE600BB33EB /* Release */, 512 | ); 513 | defaultConfigurationIsVisible = 0; 514 | defaultConfigurationName = Release; 515 | }; 516 | /* End XCConfigurationList section */ 517 | }; 518 | rootObject = DA25502416D91EE500BB33EB /* Project object */; 519 | } 520 | -------------------------------------------------------------------------------- /SampleApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SampleApp.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /SampleApp/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // AppDelegate.h 2 | // 3 | // Copyright (c) 2013 modocache 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining 6 | // a copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be 14 | // included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | 25 | 26 | #import 27 | 28 | 29 | @interface AppDelegate : UIResponder 30 | 31 | @property (strong, nonatomic) UIWindow *window; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /SampleApp/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // AppDelegate.m 2 | // 3 | // Copyright (c) 2013 modocache 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining 6 | // a copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be 14 | // included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | 25 | 26 | #import "AppDelegate.h" 27 | #import "RootTableViewController.h" 28 | 29 | 30 | @implementation AppDelegate 31 | 32 | 33 | #pragma mark - UIApplicationDelegate Protocol Methods 34 | 35 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 36 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 37 | self.window.backgroundColor = [UIColor whiteColor]; 38 | 39 | UINavigationController *navigationController = 40 | [[UINavigationController alloc] initWithRootViewController:[RootTableViewController new]]; 41 | self.window.rootViewController = navigationController; 42 | 43 | [self.window makeKeyAndVisible]; 44 | return YES; 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /SampleApp/Controllers/RootTableViewController.h: -------------------------------------------------------------------------------- 1 | // FocalViewsTableViewController.h 2 | // 3 | // Copyright (c) 2013 modocache 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining 6 | // a copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be 14 | // included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | 25 | 26 | #import 27 | 28 | 29 | @interface RootTableViewController : UITableViewController 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /SampleApp/Controllers/RootTableViewController.m: -------------------------------------------------------------------------------- 1 | // FocalViewsTableViewController.m 2 | // 3 | // Copyright (c) 2013 modocache 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining 6 | // a copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be 14 | // included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | 25 | 26 | #import "RootTableViewController.h" 27 | 28 | #import "SpotlightViewController.h" 29 | 30 | 31 | static NSString * kRootTableViewCellIdentifier = @"RootTableViewCellIdentifier"; 32 | 33 | 34 | @interface RootTableViewController () 35 | @property (nonatomic, strong) NSArray *viewControllers; 36 | @end 37 | 38 | 39 | @implementation RootTableViewController 40 | 41 | 42 | #pragma mark - Object Initialization 43 | 44 | - (id)init { 45 | self = [super initWithStyle:UITableViewStylePlain]; 46 | if (self) { 47 | _viewControllers = @[ [SpotlightViewController class] ]; 48 | } 49 | return self; 50 | } 51 | 52 | 53 | #pragma mark - UIViewController Overrides 54 | 55 | - (void)viewDidAppear:(BOOL)animated { 56 | [super viewDidAppear:animated]; 57 | 58 | for (NSIndexPath *indexPath in [self.tableView indexPathsForSelectedRows]) { 59 | [self.tableView deselectRowAtIndexPath:indexPath animated:YES]; 60 | } 61 | } 62 | 63 | 64 | #pragma mark - UITableViewDataSource Protocol Methods 65 | 66 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 67 | return 1; 68 | } 69 | 70 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 71 | return [self.viewControllers count]; 72 | } 73 | 74 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 75 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kRootTableViewCellIdentifier]; 76 | if (!cell) { 77 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 78 | reuseIdentifier:kRootTableViewCellIdentifier]; 79 | } 80 | 81 | cell.textLabel.text = NSStringFromClass([self.viewControllers objectAtIndex:indexPath.row]); 82 | 83 | return cell; 84 | } 85 | 86 | 87 | #pragma mark - UITableViewDelegate Protocol Methods 88 | 89 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 90 | id viewController = [[self.viewControllers objectAtIndex:indexPath.row] new]; 91 | [self.navigationController pushViewController:viewController animated:YES]; 92 | } 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /SampleApp/Controllers/SpotlightViewController.h: -------------------------------------------------------------------------------- 1 | // SpotlightViewController.h 2 | // 3 | // Copyright (c) 2013 modocache 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining 6 | // a copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be 14 | // included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | 25 | 26 | #import 27 | 28 | 29 | @interface SpotlightViewController : UIViewController 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /SampleApp/Controllers/SpotlightViewController.m: -------------------------------------------------------------------------------- 1 | // SpotlightViewController.m 2 | // 3 | // Copyright (c) 2013 modocache 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining 6 | // a copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be 14 | // included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | 25 | 26 | #import "SpotlightViewController.h" 27 | 28 | #import "MDCFocusView.h" 29 | #import "MDCSpotlightView.h" 30 | 31 | 32 | static CGFloat const kSpotlightViewControllerButtonMargin = 70.0f; 33 | static CGFloat const kSpotlightViewControllerButtonHeight = 50.0f; 34 | static CGFloat const kSpotlightViewControllerButtonWidth = 150.0f; 35 | 36 | 37 | @interface SpotlightViewController () 38 | @property (nonatomic, strong) MDCFocusView *focusView; 39 | @property (nonatomic, strong) UIButton *button; 40 | @property (nonatomic, strong) UIButton *bottomButton; 41 | @property (nonatomic, strong) UISegmentedControl *segmentedControl; 42 | @end 43 | 44 | 45 | @implementation SpotlightViewController 46 | 47 | 48 | #pragma mark - UIViewController Overrides 49 | 50 | - (void)viewDidLoad { 51 | [super viewDidLoad]; 52 | 53 | [self setupButtons]; 54 | [self setupSegmentedControl]; 55 | 56 | // Initialize MDCFocusView and customize its background color 57 | self.focusView = [MDCFocusView new]; 58 | self.focusView.backgroundColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f]; 59 | 60 | // Register a MDCFocalPointView subclass to "wrap" focal points 61 | self.focusView.focalPointViewClass = [MDCSpotlightView class]; 62 | 63 | // Add any number of custom views to MDCFocusView 64 | [self.focusView addSubview:[self buildLabel]]; 65 | } 66 | 67 | - (void)viewDidAppear:(BOOL)animated { 68 | [super viewDidAppear:animated]; 69 | [self.focusView focus:self.button, self.bottomButton, self.segmentedControl, nil]; 70 | } 71 | 72 | - (void)viewWillDisappear:(BOOL)animated { 73 | [super viewWillDisappear:animated]; 74 | 75 | if (self.focusView.isFocused) { 76 | [self.focusView dismiss:nil]; 77 | } 78 | } 79 | 80 | 81 | #pragma mark - Internal Methods 82 | 83 | - (void)setupButtons { 84 | self.button = [self buildButton:0]; 85 | [self.view addSubview:self.button]; 86 | 87 | [self.view addSubview:[self buildButton:1]]; 88 | 89 | self.bottomButton = [self buildButton:2]; 90 | [self.view addSubview:self.bottomButton]; 91 | } 92 | 93 | - (void)setupSegmentedControl { 94 | self.segmentedControl = [[UISegmentedControl alloc] initWithItems:@[ @"Hello", @"Focus" ]]; 95 | self.segmentedControl.frame = CGRectMake(self.bottomButton.frame.origin.x - 25.0f, 96 | self.bottomButton.frame.origin.y + self.bottomButton.frame.size.height + 200, 97 | 200.0f, 50.0f); 98 | self.segmentedControl.selectedSegmentIndex = 0; 99 | [self.view addSubview:self.segmentedControl]; 100 | } 101 | 102 | - (UIButton *)buildButton:(NSUInteger)buttonIndex { 103 | CGFloat buttonOriginY = kSpotlightViewControllerButtonMargin + 104 | (buttonIndex * (kSpotlightViewControllerButtonHeight + kSpotlightViewControllerButtonMargin)); 105 | 106 | UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 107 | button.frame = CGRectMake(floorf((self.view.frame.size.width - kSpotlightViewControllerButtonWidth)/2), 108 | buttonOriginY, 109 | kSpotlightViewControllerButtonWidth, 110 | kSpotlightViewControllerButtonHeight); 111 | 112 | [button setTitle:@"Press Me!" forState:UIControlStateNormal]; 113 | [button addTarget:self 114 | action:@selector(onButtonTapped:) 115 | forControlEvents:UIControlEventTouchUpInside]; 116 | 117 | return button; 118 | } 119 | 120 | - (UILabel *)buildLabel { 121 | UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(20, 100, 200, 300)]; 122 | label.numberOfLines = 10; 123 | label.font = [UIFont boldSystemFontOfSize:16.0f]; 124 | label.shadowColor = [UIColor grayColor]; 125 | label.shadowOffset = CGSizeMake(0, 1); 126 | label.text = @"Press one of the buttons. All controls outside of the highlighted " 127 | @"buttons cannot be tapped."; 128 | label.textColor = [UIColor whiteColor]; 129 | label.backgroundColor = [UIColor clearColor]; 130 | 131 | return label; 132 | } 133 | 134 | - (void)onButtonTapped:(UIButton *)sender { 135 | if (self.focusView.isFocused) { 136 | [self.focusView dismiss:nil]; 137 | } 138 | } 139 | 140 | @end 141 | -------------------------------------------------------------------------------- /SampleApp/SampleApp-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.modocache.ios.${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 | -------------------------------------------------------------------------------- /SampleApp/SampleApp-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'SampleApp' target in the 'SampleApp' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_3_0 8 | #warning "This project uses features only available in iOS SDK 3.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /SampleApp/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /SampleApp/main.m: -------------------------------------------------------------------------------- 1 | // main.m 2 | // 3 | // Copyright (c) 2013 modocache 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining 6 | // a copy of this software and associated documentation files (the 7 | // "Software"), to deal in the Software without restriction, including 8 | // without limitation the rights to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to 10 | // permit persons to whom the Software is furnished to do so, subject to 11 | // the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be 14 | // included in all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | 25 | 26 | #import 27 | 28 | #import "AppDelegate.h" 29 | 30 | 31 | int main(int argc, char *argv[]) { 32 | @autoreleasepool { 33 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /SampleAppTests/SampleAppTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.modocache.ios.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SampleAppTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | --------------------------------------------------------------------------------