From b00850925cd312430c0f716cde53cd6053f214d4 Mon Sep 17 00:00:00 2001 From: Joe Blau Date: Sat, 2 Dec 2017 14:47:36 -0500 Subject: [PATCH] Refactor code to modern obj-c --- Classes/COSOverlayVisualizerWindow.h | 15 + Classes/COSOverlayVisualizerWindow.m | 31 ++ Classes/COSTouchConfig.h | 26 ++ Classes/COSTouchConfig.m | 49 +++ Classes/COSTouchImageFactory.h | 19 + Classes/COSTouchImageFactory.m | 48 +++ Classes/COSTouchImageView.h | 17 + Classes/COSTouchImageView.m | 13 + .../project.pbxproj | 32 ++ Classes/COSTouchVisualizerWindow.h | 44 +-- Classes/COSTouchVisualizerWindow.m | 348 +++++++----------- ExampleObjC/Podfile.lock | 2 +- .../TouchVisualizer.xcodeproj/project.pbxproj | 5 +- ExampleObjC/TouchVisualizer/COSAppDelegate.m | 38 +- 14 files changed, 424 insertions(+), 263 deletions(-) create mode 100644 Classes/COSOverlayVisualizerWindow.h create mode 100644 Classes/COSOverlayVisualizerWindow.m create mode 100644 Classes/COSTouchConfig.h create mode 100644 Classes/COSTouchConfig.m create mode 100644 Classes/COSTouchImageFactory.h create mode 100644 Classes/COSTouchImageFactory.m create mode 100644 Classes/COSTouchImageView.h create mode 100644 Classes/COSTouchImageView.m diff --git a/Classes/COSOverlayVisualizerWindow.h b/Classes/COSOverlayVisualizerWindow.h new file mode 100644 index 0000000..3da8c95 --- /dev/null +++ b/Classes/COSOverlayVisualizerWindow.h @@ -0,0 +1,15 @@ +// +// COSOverlayVisualizerWindow.h +// COSTouchVisualizer +// +// Created by Joseph Blau on 11/30/17. +// Copyright © 2017 conopsys. All rights reserved. +// + +#import + +@interface COSOverlayVisualizerWindow : UIWindow + +- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE; + +@end diff --git a/Classes/COSOverlayVisualizerWindow.m b/Classes/COSOverlayVisualizerWindow.m new file mode 100644 index 0000000..d85421a --- /dev/null +++ b/Classes/COSOverlayVisualizerWindow.m @@ -0,0 +1,31 @@ +// +// COSOverlayVisualizerWindow.m +// COSTouchVisualizer +// +// Created by Joseph Blau on 11/30/17. +// Copyright © 2017 conopsys. All rights reserved. +// + +#import "COSOverlayVisualizerWindow.h" + +@implementation COSOverlayVisualizerWindow + +// UIKit tries to get the rootViewController from the overlay window. +// Instead, try to find the rootViewController on some other +// application window. +// Fixes problems with status bar hiding, because it considers the +// overlay window a candidate for controlling the status bar. +- (UIViewController *)rootViewController { + + for (UIWindow *window in [UIApplication sharedApplication].windows) { + if (self == window) { + continue; + } + if (window.rootViewController != nil) { + return window.rootViewController; + } + } + return [super rootViewController]; +} + +@end diff --git a/Classes/COSTouchConfig.h b/Classes/COSTouchConfig.h new file mode 100644 index 0000000..6fb2deb --- /dev/null +++ b/Classes/COSTouchConfig.h @@ -0,0 +1,26 @@ +// +// COSTouchConfig.h +// COSTouchVisualizer +// +// Created by Joseph Blau on 12/2/17. +// Copyright © 2017 conopsys. All rights reserved. +// + +#import + +typedef NS_ENUM(NSUInteger, COSTouchConfigTpye) { + COSTouchConfigTpyeContact, + COSTouchConfigTpyeRipple, +}; + +@interface COSTouchConfig : NSObject + +@property (nonatomic) CGFloat alpha; +@property (nonatomic) NSTimeInterval fadeDuration; +@property (nonatomic, nullable) UIColor *strokeColor; +@property (nonatomic, nullable) UIColor *fillColor; + +-(nonnull instancetype)initWithTouchConfigType:(COSTouchConfigTpye)configType NS_DESIGNATED_INITIALIZER; +-(nonnull instancetype)init NS_UNAVAILABLE; + +@end diff --git a/Classes/COSTouchConfig.m b/Classes/COSTouchConfig.m new file mode 100644 index 0000000..81f1c5a --- /dev/null +++ b/Classes/COSTouchConfig.m @@ -0,0 +1,49 @@ +// +// COSTouchConfig.m +// COSTouchVisualizer +// +// Created by Joseph Blau on 12/2/17. +// Copyright © 2017 conopsys. All rights reserved. +// + +#import "COSTouchConfig.h" + +static const CGFloat COSTouchConfigContactAlpha = 0.5f; +static const CGFloat COSTouchConfigRippleAlpha = 0.2f; +static const NSTimeInterval COSTouchConfigContactFadeDuration = 0.3; +static const NSTimeInterval COSTouchConfigRippleFadeDuration = 0.2; + +@implementation COSTouchConfig + +-(instancetype)initWithTouchConfigType:(COSTouchConfigTpye)configType { + self = [super init]; + if (self) { + switch (configType) { + case COSTouchConfigTpyeContact: + [self _configureContact]; + break; + case COSTouchConfigTpyeRipple: + [self _configureRipple]; + break; + } + } + return self; +} + +#pragma mark - Private + +-(void)_configureContact { + self.strokeColor = [UIColor blackColor]; + self.fillColor = [UIColor blackColor]; + self.alpha = COSTouchConfigContactAlpha; + self.fadeDuration = COSTouchConfigContactFadeDuration; +} + +- (void)_configureRipple { + self.strokeColor = [UIColor whiteColor]; + self.fillColor = [UIColor blueColor]; + self.alpha = COSTouchConfigRippleAlpha; + self.fadeDuration = COSTouchConfigRippleFadeDuration; +} + +@end diff --git a/Classes/COSTouchImageFactory.h b/Classes/COSTouchImageFactory.h new file mode 100644 index 0000000..0e02060 --- /dev/null +++ b/Classes/COSTouchImageFactory.h @@ -0,0 +1,19 @@ +// +// COSTouchImageFactory.h +// COSTouchVisualizer +// +// Created by Joseph Blau on 12/2/17. +// Copyright © 2017 conopsys. All rights reserved. +// + +#import + +@class COSTouchConfig; + +@interface COSTouchImageFactory : NSObject + ++(nonnull UIImage *)imageWithTouchConfig:(nonnull COSTouchConfig*)touchConfig; + +-(nonnull instancetype)init NS_UNAVAILABLE; + +@end diff --git a/Classes/COSTouchImageFactory.m b/Classes/COSTouchImageFactory.m new file mode 100644 index 0000000..03ee49b --- /dev/null +++ b/Classes/COSTouchImageFactory.m @@ -0,0 +1,48 @@ +// +// COSTouchImageFactory.m +// COSTouchVisualizer +// +// Created by Joseph Blau on 12/2/17. +// Copyright © 2017 conopsys. All rights reserved. +// + +#import "COSTouchImageFactory.h" +#import "COSTouchConfig.h" + +static const CGFloat COSTouchImageFactorySideSize = 50.0f; + +@implementation COSTouchImageFactory + ++(UIImage *)imageWithTouchConfig:(COSTouchConfig *)touchConfig { + UIImage *touchImage = ({ + UIImage *image = [UIImage new]; + UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:CGRectMake(0.0f, + 0.0f, + COSTouchImageFactorySideSize, + COSTouchImageFactorySideSize)]; + UIGraphicsBeginImageContextWithOptions(clipPath.bounds.size, NO, 0); + CGPoint center = CGPointMake(COSTouchImageFactorySideSize / 2.0f, + COSTouchImageFactorySideSize / 2.0f); + + UIBezierPath *drawPath = [UIBezierPath bezierPathWithArcCenter:center + radius:22.0 + startAngle:0 + endAngle:2 * M_PI + clockwise:YES]; + drawPath.lineWidth = 2.0; + + [touchConfig.strokeColor setStroke]; + [touchConfig.fillColor setFill]; + + [drawPath stroke]; + [drawPath fill]; + [clipPath addClip]; + image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + image; + }); + return touchImage; +} + +@end diff --git a/Classes/COSTouchImageView.h b/Classes/COSTouchImageView.h new file mode 100644 index 0000000..3c3987a --- /dev/null +++ b/Classes/COSTouchImageView.h @@ -0,0 +1,17 @@ +// +// COSTouchSpotView.h +// COSTouchVisualizer +// +// Created by Joseph Blau on 11/30/17. +// Copyright © 2017 conopsys. All rights reserved. +// + +#import + +@interface COSTouchImageView : UIImageView + +@property (nonatomic) NSTimeInterval timestamp; +@property (nonatomic) BOOL shouldAutomaticallyRemoveAfterTimeout; +@property (nonatomic, getter=isFadingOut) BOOL fadingOut; + +@end diff --git a/Classes/COSTouchImageView.m b/Classes/COSTouchImageView.m new file mode 100644 index 0000000..9ae9596 --- /dev/null +++ b/Classes/COSTouchImageView.m @@ -0,0 +1,13 @@ +// +// COSTouchSpotView.m +// COSTouchVisualizer +// +// Created by Joseph Blau on 11/30/17. +// Copyright © 2017 conopsys. All rights reserved. +// + +#import "COSTouchImageView.h" + +@implementation COSTouchImageView + +@end diff --git a/Classes/COSTouchVisualizer.xcodeproj/project.pbxproj b/Classes/COSTouchVisualizer.xcodeproj/project.pbxproj index 275ee32..1301998 100644 --- a/Classes/COSTouchVisualizer.xcodeproj/project.pbxproj +++ b/Classes/COSTouchVisualizer.xcodeproj/project.pbxproj @@ -12,6 +12,14 @@ A71946C61C7EFB1F003B7C4A /* COSTouchVisualizerWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = A71946C21C7EFB1F003B7C4A /* COSTouchVisualizerWindow.m */; }; A71946C81C7EFB69003B7C4A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E6F67FD018DE12B4001C954A /* UIKit.framework */; }; A71946C91C7EFB74003B7C4A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E6F67FCC18DE12B4001C954A /* Foundation.framework */; }; + E6C633A91FD0FB9D000DA40C /* COSOverlayVisualizerWindow.h in Headers */ = {isa = PBXBuildFile; fileRef = E6C633A71FD0FB9D000DA40C /* COSOverlayVisualizerWindow.h */; }; + E6C633AA1FD0FB9D000DA40C /* COSOverlayVisualizerWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = E6C633A81FD0FB9D000DA40C /* COSOverlayVisualizerWindow.m */; }; + E6C633AD1FD0FC8E000DA40C /* COSTouchImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = E6C633AB1FD0FC8E000DA40C /* COSTouchImageView.h */; }; + E6C633AE1FD0FC8E000DA40C /* COSTouchImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = E6C633AC1FD0FC8E000DA40C /* COSTouchImageView.m */; }; + E6C633B11FD2CCB2000DA40C /* COSTouchConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = E6C633AF1FD2CCB2000DA40C /* COSTouchConfig.h */; }; + E6C633B21FD2CCB2000DA40C /* COSTouchConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = E6C633B01FD2CCB2000DA40C /* COSTouchConfig.m */; }; + E6C633B51FD2D4FD000DA40C /* COSTouchImageFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = E6C633B31FD2D4FD000DA40C /* COSTouchImageFactory.h */; }; + E6C633B61FD2D4FD000DA40C /* COSTouchImageFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = E6C633B41FD2D4FD000DA40C /* COSTouchImageFactory.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -20,6 +28,14 @@ A71946C11C7EFB1F003B7C4A /* COSTouchVisualizerWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = COSTouchVisualizerWindow.h; sourceTree = ""; }; A71946C21C7EFB1F003B7C4A /* COSTouchVisualizerWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = COSTouchVisualizerWindow.m; sourceTree = ""; }; A71946C31C7EFB1F003B7C4A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E6C633A71FD0FB9D000DA40C /* COSOverlayVisualizerWindow.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = COSOverlayVisualizerWindow.h; sourceTree = ""; }; + E6C633A81FD0FB9D000DA40C /* COSOverlayVisualizerWindow.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = COSOverlayVisualizerWindow.m; sourceTree = ""; }; + E6C633AB1FD0FC8E000DA40C /* COSTouchImageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = COSTouchImageView.h; sourceTree = ""; }; + E6C633AC1FD0FC8E000DA40C /* COSTouchImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = COSTouchImageView.m; sourceTree = ""; }; + E6C633AF1FD2CCB2000DA40C /* COSTouchConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = COSTouchConfig.h; sourceTree = ""; }; + E6C633B01FD2CCB2000DA40C /* COSTouchConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = COSTouchConfig.m; sourceTree = ""; }; + E6C633B31FD2D4FD000DA40C /* COSTouchImageFactory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = COSTouchImageFactory.h; sourceTree = ""; }; + E6C633B41FD2D4FD000DA40C /* COSTouchImageFactory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = COSTouchImageFactory.m; sourceTree = ""; }; E6F67FCC18DE12B4001C954A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; E6F67FD018DE12B4001C954A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -44,6 +60,14 @@ A71946C11C7EFB1F003B7C4A /* COSTouchVisualizerWindow.h */, A71946C21C7EFB1F003B7C4A /* COSTouchVisualizerWindow.m */, A71946C31C7EFB1F003B7C4A /* Info.plist */, + E6C633A71FD0FB9D000DA40C /* COSOverlayVisualizerWindow.h */, + E6C633A81FD0FB9D000DA40C /* COSOverlayVisualizerWindow.m */, + E6C633AB1FD0FC8E000DA40C /* COSTouchImageView.h */, + E6C633AC1FD0FC8E000DA40C /* COSTouchImageView.m */, + E6C633AF1FD2CCB2000DA40C /* COSTouchConfig.h */, + E6C633B01FD2CCB2000DA40C /* COSTouchConfig.m */, + E6C633B31FD2D4FD000DA40C /* COSTouchImageFactory.h */, + E6C633B41FD2D4FD000DA40C /* COSTouchImageFactory.m */, ); name = Source; sourceTree = ""; @@ -81,7 +105,11 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + E6C633A91FD0FB9D000DA40C /* COSOverlayVisualizerWindow.h in Headers */, + E6C633B51FD2D4FD000DA40C /* COSTouchImageFactory.h in Headers */, + E6C633AD1FD0FC8E000DA40C /* COSTouchImageView.h in Headers */, A71946C51C7EFB1F003B7C4A /* COSTouchVisualizerWindow.h in Headers */, + E6C633B11FD2CCB2000DA40C /* COSTouchConfig.h in Headers */, A71946C41C7EFB1F003B7C4A /* COSTouchVisualizer.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -155,6 +183,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + E6C633AA1FD0FB9D000DA40C /* COSOverlayVisualizerWindow.m in Sources */, + E6C633B61FD2D4FD000DA40C /* COSTouchImageFactory.m in Sources */, + E6C633B21FD2CCB2000DA40C /* COSTouchConfig.m in Sources */, + E6C633AE1FD0FC8E000DA40C /* COSTouchImageView.m in Sources */, A71946C61C7EFB1F003B7C4A /* COSTouchVisualizerWindow.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Classes/COSTouchVisualizerWindow.h b/Classes/COSTouchVisualizerWindow.h index a216590..dac629a 100755 --- a/Classes/COSTouchVisualizerWindow.h +++ b/Classes/COSTouchVisualizerWindow.h @@ -7,36 +7,32 @@ // #include -@protocol COSTouchVisualizerWindowDelegate; +typedef NS_ENUM(NSUInteger, COSTouchVisualizerWindowTouchVisibility) { + COSTouchVisualizerWindowTouchVisibilityNever, + COSTouchVisualizerWindowTouchVisibilityRemoteOnly, + COSTouchVisualizerWindowTouchVisibilityRemoteAndLocal, +}; + +@class COSTouchVisualizerWindow; +@class COSTouchConfig; @interface COSTouchVisualizerWindow : UIWindow -@property (nonatomic, readonly, getter=isActive) BOOL active; -@property (nonatomic, weak) id touchVisualizerWindowDelegate; +@property (nonatomic, readonly, getter=isMorphEnabled) BOOL morphEnabled; +@property (nonatomic, readonly, nonnull) COSTouchConfig *touchContactConfig; +@property (nonatomic, readonly, nonnull) COSTouchConfig *touchRippleConfig; +@property (nonatomic, readonly) COSTouchVisualizerWindowTouchVisibility touchVisibility; -// Touch effects -@property (nonatomic) UIImage *touchImage; -@property (nonatomic) CGFloat touchAlpha; -@property (nonatomic) NSTimeInterval fadeDuration; -@property (nonatomic) UIColor *strokeColor; -@property (nonatomic) UIColor *fillColor; +-(nonnull instancetype)initWithFrame:(CGRect)frame + morphEnabled:(BOOL)morphEnabled + touchVisibility:(COSTouchVisualizerWindowTouchVisibility)touchVisibility + contactConfig:(nullable COSTouchConfig*)contactConfig + rippleConfig:(nullable COSTouchConfig*)rippleConfig NS_DESIGNATED_INITIALIZER; +-(nonnull instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE; +-(nonnull instancetype)init NS_UNAVAILABLE; +-(nonnull instancetype)initWithCoder:(nonnull NSCoder *)aDecoder NS_UNAVAILABLE; -// Ripple Effects -@property (nonatomic) UIImage *rippleImage; -@property (nonatomic) CGFloat rippleAlpha; -@property (nonatomic) NSTimeInterval rippleFadeDuration; -@property (nonatomic) UIColor *rippleStrokeColor; -@property (nonatomic) UIColor *rippleFillColor; - -@property (nonatomic) BOOL stationaryMorphEnabled; // default: YES @end -@protocol COSTouchVisualizerWindowDelegate -@optional - -- (BOOL)touchVisualizerWindowShouldShowFingertip:(COSTouchVisualizerWindow *)window; -- (BOOL)touchVisualizerWindowShouldAlwaysShowFingertip:(COSTouchVisualizerWindow *)window; - -@end diff --git a/Classes/COSTouchVisualizerWindow.m b/Classes/COSTouchVisualizerWindow.m index 010d0b9..9ebde77 100755 --- a/Classes/COSTouchVisualizerWindow.m +++ b/Classes/COSTouchVisualizerWindow.m @@ -7,46 +7,12 @@ // #import "COSTouchVisualizerWindow.h" +#import "COSOverlayVisualizerWindow.h" +#import "COSTouchImageView.h" +#import "COSTouchConfig.h" +#import "COSTouchImageFactory.h" -#pragma mark - Touch Visualizer Window - -@interface TouchVisualizerWindow : UIWindow -@end - -@implementation TouchVisualizerWindow - -// UIKit tries to get the rootViewController from the overlay window. -// Instead, try to find the rootViewController on some other -// application window. -// Fixes problems with status bar hiding, because it considers the -// overlay window a candidate for controlling the status bar. -- (UIViewController *)rootViewController { - for (UIWindow *window in [[UIApplication sharedApplication] windows]) { - if (self == window) - continue; - UIViewController *realRootViewController = window.rootViewController; - if (realRootViewController != nil) - return realRootViewController; - } - return [super rootViewController]; -} - -@end - -#pragma mark - Conopsys Touch Spot View - -@interface COSTouchSpotView : UIImageView - -@property (nonatomic) NSTimeInterval timestamp; -@property (nonatomic) BOOL shouldAutomaticallyRemoveAfterTimeout; -@property (nonatomic, getter=isFadingOut) BOOL fadingOut; - -@end - -@implementation COSTouchSpotView -@end - -#pragma mark - Conopsys Touch Visualizer Window +static const NSTimeInterval COSTouchVisualizerWindowRemoveDelay = 0.2; @interface COSTouchVisualizerWindow () @@ -56,97 +22,43 @@ @property (nonatomic) NSTimer *timer; @property (nonatomic) NSSet *allTouches; -- (void)COSTouchVisualizerWindow_commonInit; -- (void)scheduleFingerTipRemoval; -- (void)cancelScheduledFingerTipRemoval; -- (void)removeInactiveFingerTips; -- (void)removeFingerTipWithHash:(NSUInteger)hash animated:(BOOL)animated; -- (BOOL)shouldAutomaticallyRemoveFingerTipForTouch:(UITouch *)touch; +@property (nonatomic) UIImage *touchImage; +@property (nonatomic) UIImage *rippleImage; + +@property (nonatomic) COSTouchConfig *touchContactConfig; +@property (nonatomic) COSTouchConfig *touchRippleConfig; @end @implementation COSTouchVisualizerWindow -- (id)initWithCoder:(NSCoder *)decoder { - // This covers NIB-loaded windows. - if (self = [super initWithCoder:decoder]) - [self COSTouchVisualizerWindow_commonInit]; +- (instancetype)initWithFrame:(CGRect)frame + morphEnabled:(BOOL)morphEnabled + touchVisibility:(COSTouchVisualizerWindowTouchVisibility)touchVisibility + contactConfig:(COSTouchConfig *)contactConfig + rippleConfig:(COSTouchConfig *)rippleConfig { + self = [super initWithFrame:frame]; + if (self) { + _morphEnabled = morphEnabled; + _touchVisibility = touchVisibility; + _touchContactConfig = contactConfig ?: [[COSTouchConfig alloc] initWithTouchConfigType:COSTouchConfigTpyeContact]; + _touchRippleConfig = rippleConfig ?: [[COSTouchConfig alloc] initWithTouchConfigType:COSTouchConfigTpyeRipple]; + } return self; } -- (id)initWithFrame:(CGRect)rect { - // This covers programmatically-created windows. - if (self = [super initWithFrame:rect]) - [self COSTouchVisualizerWindow_commonInit]; - return self; -} - -- (void)COSTouchVisualizerWindow_commonInit { - self.strokeColor = [UIColor blackColor]; - self.fillColor = [UIColor whiteColor]; - self.rippleStrokeColor = [UIColor whiteColor]; - self.rippleFillColor = [UIColor blueColor]; - self.touchAlpha = 0.5; - self.fadeDuration = 0.3; - self.rippleAlpha = 0.2; - self.rippleFadeDuration = 0.2; - self.stationaryMorphEnabled = YES; -} - #pragma mark - Touch / Ripple and Images - (UIImage *)touchImage { if (!_touchImage) { - UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 50.0, 50.0)]; - - UIGraphicsBeginImageContextWithOptions(clipPath.bounds.size, NO, 0); - - UIBezierPath *drawPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(25.0, 25.0) - radius:22.0 - startAngle:0 - endAngle:2 * M_PI - clockwise:YES]; - - drawPath.lineWidth = 2.0; - - [self.strokeColor setStroke]; - [self.fillColor setFill]; - - [drawPath stroke]; - [drawPath fill]; - - [clipPath addClip]; - - _touchImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + _touchImage = [COSTouchImageFactory imageWithTouchConfig:self.touchContactConfig]; } return _touchImage; } - (UIImage *)rippleImage { if (!_rippleImage) { - UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 50.0, 50.0)]; - - UIGraphicsBeginImageContextWithOptions(clipPath.bounds.size, NO, 0); - - UIBezierPath *drawPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(25.0, 25.0) - radius:22.0 - startAngle:0 - endAngle:2 * M_PI - clockwise:YES]; - - drawPath.lineWidth = 2.0; - - [self.rippleStrokeColor setStroke]; - [self.rippleFillColor setFill]; - - [drawPath stroke]; - [drawPath fill]; - - [clipPath addClip]; - - _rippleImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + _rippleImage = [COSTouchImageFactory imageWithTouchConfig:self.touchRippleConfig]; } return _rippleImage; } @@ -165,98 +77,94 @@ return NO; } -- (BOOL)isActive { - // should show fingertip or not - if (![self.touchVisualizerWindowDelegate respondsToSelector:@selector(touchVisualizerWindowShouldShowFingertip:)] || - [self.touchVisualizerWindowDelegate touchVisualizerWindowShouldShowFingertip:self]) { - // should always show or only when any screen is mirrored. - return (([self.touchVisualizerWindowDelegate respondsToSelector:@selector(touchVisualizerWindowShouldAlwaysShowFingertip:)] && - [self.touchVisualizerWindowDelegate touchVisualizerWindowShouldAlwaysShowFingertip:self]) || - [self anyScreenIsMirrored]); - } else { - return NO; - } -} - #pragma mark - UIWindow overrides - (void)sendEvent:(UIEvent *)event { - if (self.active) { - self.allTouches = [event allTouches]; - for (UITouch *touch in [self.allTouches allObjects]) { - switch (touch.phase) { - case UITouchPhaseBegan: - case UITouchPhaseMoved: { - // Generate ripples - COSTouchSpotView *rippleView = [[COSTouchSpotView alloc] initWithImage:self.rippleImage]; - [self.overlayWindow addSubview:rippleView]; - - rippleView.alpha = self.rippleAlpha; - rippleView.center = [touch locationInView:self.overlayWindow]; - - [UIView animateWithDuration:self.rippleFadeDuration - delay:0.0 - options:UIViewAnimationOptionCurveEaseIn // See other - // options - animations:^{ - [rippleView setAlpha:0.0]; - [rippleView setFrame:CGRectInset(rippleView.frame, 25, 25)]; - } completion:^(BOOL finished) { - [rippleView removeFromSuperview]; - }]; - } - case UITouchPhaseStationary: { - COSTouchSpotView *touchView = (COSTouchSpotView *)[self.overlayWindow viewWithTag:touch.hash]; - - if (touch.phase != UITouchPhaseStationary && touchView != nil && [touchView isFadingOut]) { - [self.timer invalidate]; - [touchView removeFromSuperview]; - touchView = nil; + [super sendEvent:event]; + + switch (self.touchVisibility) { + case COSTouchVisualizerWindowTouchVisibilityNever: + return; + break; + + case COSTouchVisualizerWindowTouchVisibilityRemoteOnly: + case COSTouchVisualizerWindowTouchVisibilityRemoteAndLocal: { + self.allTouches = [event allTouches]; + for (UITouch *touch in [self.allTouches allObjects]) { + switch (touch.phase) { + case UITouchPhaseBegan: + case UITouchPhaseMoved: { + // Generate ripples + COSTouchImageView *rippleView = [[COSTouchImageView alloc] initWithImage:self.rippleImage]; + [self.overlayWindow addSubview:rippleView]; + + rippleView.alpha = self.touchRippleConfig.alpha; + rippleView.center = [touch locationInView:self.overlayWindow]; + + [UIView animateWithDuration:self.touchRippleConfig.fadeDuration + delay:0.0 + options:UIViewAnimationOptionCurveEaseIn + animations:^{ + [rippleView setAlpha:0.0]; + [rippleView setFrame:CGRectInset(rippleView.frame, 25, 25)]; + } completion:^(BOOL finished) { + [rippleView removeFromSuperview]; + }]; } - - if (touchView == nil && touch.phase != UITouchPhaseStationary) { - touchView = [[COSTouchSpotView alloc] initWithImage:self.touchImage]; - [self.overlayWindow addSubview:touchView]; - - if (self.stationaryMorphEnabled) { - if (self.timer) { - [self.timer invalidate]; - } - - self.timer = [NSTimer scheduledTimerWithTimeInterval:0.6 - target:self - selector:@selector(performMorph:) - userInfo:touchView - repeats:YES]; + case UITouchPhaseStationary: { + COSTouchImageView *touchView = (COSTouchImageView *)[self.overlayWindow viewWithTag:touch.hash]; + + if (touch.phase != UITouchPhaseStationary && touchView != nil && [touchView isFadingOut]) { + [self.timer invalidate]; + [touchView removeFromSuperview]; + touchView = nil; } + + if (touchView == nil && touch.phase != UITouchPhaseStationary) { + touchView = [[COSTouchImageView alloc] initWithImage:self.touchImage]; + [self.overlayWindow addSubview:touchView]; + + if (self.morphEnabled) { + if (self.timer) { + [self.timer invalidate]; + } + + self.timer = [NSTimer scheduledTimerWithTimeInterval:0.6 + target:self + selector:@selector(performMorphWithTouchView:) + userInfo:touchView + repeats:YES]; + } + } + if (![touchView isFadingOut]) { + touchView.alpha = self.touchContactConfig.alpha; + touchView.center = [touch locationInView:self.overlayWindow]; + touchView.tag = touch.hash; + touchView.timestamp = touch.timestamp; + touchView.shouldAutomaticallyRemoveAfterTimeout = [self shouldAutomaticallyRemoveFingerTipForTouch:touch]; + } + break; } - if (![touchView isFadingOut]) { - touchView.alpha = self.touchAlpha; - touchView.center = [touch locationInView:self.overlayWindow]; - touchView.tag = touch.hash; - touchView.timestamp = touch.timestamp; - touchView.shouldAutomaticallyRemoveAfterTimeout = [self shouldAutomaticallyRemoveFingerTipForTouch:touch]; + case UITouchPhaseEnded: + case UITouchPhaseCancelled: { + [self removeFingerTipWithHash:touch.hash animated:YES]; + break; } - break; - } - case UITouchPhaseEnded: - case UITouchPhaseCancelled: { - [self removeFingerTipWithHash:touch.hash animated:YES]; - break; } } + + + break; } } - - [super sendEvent:event]; + [self scheduleFingerTipRemoval]; // We may not see all UITouchPhaseEnded/UITouchPhaseCancelled events. } -#pragma mark - Private - (UIWindow *)overlayWindow { if (!_overlayWindow) { - _overlayWindow = [[TouchVisualizerWindow alloc] initWithFrame:self.frame]; + _overlayWindow = [[COSOverlayVisualizerWindow alloc] initWithFrame:self.frame]; _overlayWindow.userInteractionEnabled = NO; _overlayWindow.windowLevel = UIWindowLevelStatusBar; _overlayWindow.backgroundColor = [UIColor clearColor]; @@ -264,11 +172,12 @@ } return _overlayWindow; } +#pragma mark - Private - (void)scheduleFingerTipRemoval { - - if (self.fingerTipRemovalScheduled) + if (self.fingerTipRemovalScheduled) { return; + } self.fingerTipRemovalScheduled = YES; [self performSelector:@selector(removeInactiveFingerTips) withObject:nil @@ -286,33 +195,34 @@ self.fingerTipRemovalScheduled = NO; NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime]; - const CGFloat REMOVAL_DELAY = 0.2; - for (COSTouchSpotView *touchView in [self.overlayWindow subviews]) { - if (![touchView isKindOfClass:[COSTouchSpotView class]]) + + for (COSTouchImageView *touchView in [self.overlayWindow subviews]) { + if (![touchView isKindOfClass:[COSTouchImageView class]]) { continue; + } - if (touchView.shouldAutomaticallyRemoveAfterTimeout && now > touchView.timestamp + REMOVAL_DELAY) + if (touchView.shouldAutomaticallyRemoveAfterTimeout && now > touchView.timestamp + COSTouchVisualizerWindowRemoveDelay) { [self removeFingerTipWithHash:touchView.tag animated:YES]; + } } - if ([[self.overlayWindow subviews] count]) + if ([[self.overlayWindow subviews] count]) { [self scheduleFingerTipRemoval]; + } } - (void)removeFingerTipWithHash:(NSUInteger)hash animated:(BOOL)animated { - COSTouchSpotView *touchView = (COSTouchSpotView *)[self.overlayWindow viewWithTag:hash]; - if (touchView == nil) - return; - - if ([touchView isFadingOut]) + COSTouchImageView *touchView = (COSTouchImageView *)[self.overlayWindow viewWithTag:hash]; + if (touchView == nil || [touchView isFadingOut]) { return; + } BOOL animationsWereEnabled = [UIView areAnimationsEnabled]; if (animated) { [UIView setAnimationsEnabled:YES]; [UIView beginAnimations:nil context:nil]; - [UIView setAnimationDuration:self.fadeDuration]; + [UIView setAnimationDuration:self.touchContactConfig.fadeDuration]; } touchView.frame = CGRectMake(touchView.center.x - touchView.frame.size.width, @@ -329,7 +239,7 @@ touchView.fadingOut = YES; [touchView performSelector:@selector(removeFromSuperview) withObject:nil - afterDelay:self.fadeDuration]; + afterDelay:self.touchContactConfig.fadeDuration]; } - (BOOL)shouldAutomaticallyRemoveFingerTipForTouch:(UITouch *)touch; @@ -346,40 +256,39 @@ // don't use UITouchPhaseStationary touches for those. *sigh*). So we // end up with this more complicated setup. - UIView *view = [touch view]; - view = [view hitTest:[touch locationInView:view] withEvent:nil]; + UIView *touchView = [touch view]; + touchView = [touchView hitTest:[touch locationInView:touchView] withEvent:nil]; - while (view != nil) { - if ([view isKindOfClass:[UITableViewCell class]]) { + while (touchView != nil) { + if ([touchView isKindOfClass:[UITableViewCell class]]) { for (UIGestureRecognizer *recognizer in [touch gestureRecognizers]) { if ([recognizer isKindOfClass:[UISwipeGestureRecognizer class]]) return YES; } } - if ([view isKindOfClass:[UITableView class]]) { - if ([[touch gestureRecognizers] count] == 0) - return YES; + if ([touchView isKindOfClass:[UITableView class]] && + [[touch gestureRecognizers] count] == 0) { + return YES; } - view = view.superview; + touchView = touchView.superview; } return NO; } -- (void)performMorph:(NSTimer *)theTimer { - UIView *view = (UIView *)[theTimer userInfo]; +- (void)performMorphWithTouchView:(COSTouchImageView *)touchView { NSTimeInterval duration = .4; NSTimeInterval delay = 0; // Start - view.alpha = self.touchAlpha; - view.transform = CGAffineTransformMakeScale(1, 1); + touchView.alpha = self.touchContactConfig.alpha; + touchView.transform = CGAffineTransformMakeScale(1, 1); [UIView animateWithDuration:duration / 4 delay:delay options:0 animations:^{ // End - view.transform = CGAffineTransformMakeScale(1, 1.2); + touchView.transform = CGAffineTransformMakeScale(1, 1.2); } completion:^(BOOL finished) { [UIView animateWithDuration:duration / 4 @@ -387,7 +296,7 @@ options:0 animations:^{ // End - view.transform = CGAffineTransformMakeScale(1.2, 0.9); + touchView.transform = CGAffineTransformMakeScale(1.2, 0.9); } completion:^(BOOL finished) { [UIView animateWithDuration:duration / 4 @@ -395,7 +304,7 @@ options:0 animations:^{ // End - view.transform = CGAffineTransformMakeScale(0.9, 0.9); + touchView.transform = CGAffineTransformMakeScale(0.9, 0.9); } completion:^(BOOL finished) { [UIView animateWithDuration:duration / 4 @@ -403,12 +312,13 @@ options:0 animations:^{ // End - view.transform = CGAffineTransformMakeScale(1, 1); + touchView.transform = CGAffineTransformMakeScale(1, 1); } completion:^(BOOL finished){ // If there are no touches, remove this morping touch - if (self.allTouches.count == 0) - [view removeFromSuperview]; + if (self.allTouches.count == 0) { + [touchView removeFromSuperview]; + } }]; }]; }]; diff --git a/ExampleObjC/Podfile.lock b/ExampleObjC/Podfile.lock index 749eab6..fe6d7ee 100644 --- a/ExampleObjC/Podfile.lock +++ b/ExampleObjC/Podfile.lock @@ -13,4 +13,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 9c382a6853b1ccb79b91a8268f272eb3ffaff4be -COCOAPODS: 1.1.1 +COCOAPODS: 1.3.1 diff --git a/ExampleObjC/TouchVisualizer.xcodeproj/project.pbxproj b/ExampleObjC/TouchVisualizer.xcodeproj/project.pbxproj index 39ca344..e48aeeb 100644 --- a/ExampleObjC/TouchVisualizer.xcodeproj/project.pbxproj +++ b/ExampleObjC/TouchVisualizer.xcodeproj/project.pbxproj @@ -314,13 +314,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-TouchVisualizer-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ diff --git a/ExampleObjC/TouchVisualizer/COSAppDelegate.m b/ExampleObjC/TouchVisualizer/COSAppDelegate.m index c67150f..56f2fc1 100644 --- a/ExampleObjC/TouchVisualizer/COSAppDelegate.m +++ b/ExampleObjC/TouchVisualizer/COSAppDelegate.m @@ -8,9 +8,7 @@ #import "COSAppDelegate.h" #import - -@interface COSAppDelegate () -@end +#import @implementation COSAppDelegate @@ -22,25 +20,29 @@ - (COSTouchVisualizerWindow *)window { static COSTouchVisualizerWindow *customWindow = nil; if (!customWindow) { - customWindow = [[COSTouchVisualizerWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + COSTouchConfig *contactConfig = ({ + COSTouchConfig *config = [[COSTouchConfig alloc] initWithTouchConfigType:COSTouchConfigTpyeContact]; + config.fillColor = [UIColor purpleColor]; + config.strokeColor = [UIColor blueColor]; + config.alpha = 0.4; + config; + }); - [customWindow setFillColor:[UIColor purpleColor]]; - [customWindow setStrokeColor:[UIColor blueColor]]; - [customWindow setTouchAlpha:0.4]; - - [customWindow setRippleFillColor:[UIColor purpleColor]]; - [customWindow setRippleStrokeColor:[UIColor blueColor]]; - [customWindow setRippleAlpha:0.1]; + COSTouchConfig *riippleConfig = ({ + COSTouchConfig *config = [[COSTouchConfig alloc] initWithTouchConfigType:COSTouchConfigTpyeRipple]; + config.fillColor = [UIColor purpleColor]; + config.strokeColor = [UIColor blueColor]; + config.alpha = 0.1; + config; + }); - [customWindow setTouchVisualizerWindowDelegate:self]; + customWindow = [[COSTouchVisualizerWindow alloc] initWithFrame:[UIScreen mainScreen].bounds + morphEnabled:YES + touchVisibility:COSTouchVisualizerWindowTouchVisibilityRemoteAndLocal + contactConfig:contactConfig + rippleConfig:riippleConfig]; } return customWindow; } -#pragma mark - COSTouchVisualizerWindowDelegate - -- (BOOL)touchVisualizerWindowShouldAlwaysShowFingertip:(COSTouchVisualizerWindow *)window { - return YES; -} - @end