Skip to content

Instantly share code, notes, and snippets.

@Shilo
Last active December 22, 2015 12:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Shilo/6476241 to your computer and use it in GitHub Desktop.
Save Shilo/6476241 to your computer and use it in GitHub Desktop.
A UIScrollView subclass that will allow a specified GLKViewController to continue rendering, while scrolling. It will also allow touches to be passed to a Sparrow view if property 'passTouchEventsToSparrow' is YES.
//
// XDSPScrollView.h
// A scroll view that will allow a specified GLKViewController to continue rendering, while scrolling.
// It will also allow touches to be passed to a Sparrow view if property 'passTouchEventsToSparrow' is YES.
//
// Created by Shilo White on 9/7/13.
//
#import <UIKit/UIKit.h>
@class GLKViewController;
@interface XDSPScrollView : UIScrollView
@property (nonatomic, weak) GLKViewController *glkViewController;
@property (nonatomic, assign) BOOL passTouchEventsToSparrow;
@property (nonatomic, readonly) BOOL rendering;
@property (nonatomic, readonly) BOOL active;
@end
//
// XDSPScrollView.m
// A scroll view that will allow a specified GLKViewController to continue rendering, while scrolling.
// It will also allow touches to be passed to a Sparrow view if property 'passTouchEventsToSparrow' is YES.
//
// Created by Shilo White on 9/7/13.
//
#import "XDSPScrollView.h"
#import <GLKit/GLKit.h>
#import <QuartzCore/QuartzCore.h>
#import <objc/runtime.h>
#import "SPViewController.h"
#import "SPTouch_Internal.h"
#import "SPTouchProcessor.h"
static NSMutableArray *sActiveScrollViews;
@interface GLKViewController (XDSPScrollView)
@property (nonatomic, weak) XDSPScrollView *_activeScrollView;
@end
@implementation GLKViewController (XDSPScrollView)
- (void)set_activeScrollView:(XDSPScrollView *)_activeScrollView
{
objc_setAssociatedObject(self, @selector(_activeScrollView), _activeScrollView, OBJC_ASSOCIATION_ASSIGN);
}
- (XDSPScrollView *)_activeScrollView
{
return objc_getAssociatedObject(self, @selector(_activeScrollView));
}
@end
@interface XDSPScrollView () <UIScrollViewDelegate>
@property (nonatomic, assign) BOOL rendering;
@property (nonatomic, assign) BOOL active;
+ (NSMutableArray *)activeScrollViews;
+ (void)addActiveScrollView:(XDSPScrollView *)scrollView;
+ (void)removeActiveScrollView:(XDSPScrollView *)scrollView;
+ (XDSPScrollView *)activeScrollViewWithGLKViewController:(GLKViewController *)glkViewController;
@end
@implementation XDSPScrollView {
id<UIScrollViewDelegate> __unsafe_unretained _delegate;
GLKViewController __weak *_glkViewController;
CADisplayLink *_displayLink;
BOOL _passTouchEventsToSparrow;
BOOL _active;
}
@synthesize delegate = _delegate;
@synthesize glkViewController = _glkViewController;
@synthesize passTouchEventsToSparrow = _passTouchEventsToSparrow;
@synthesize active = _active;
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame]))
{
[self setup];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if ((self = [super initWithCoder:aDecoder]))
{
[self setup];
}
return self;
}
- (id)init
{
if ((self = [super init]))
{
[self setup];
}
return self;
}
- (void)setup
{
super.delegate = self;
_glkViewController = nil;
_displayLink = nil;
_active = NO;
}
- (void)setRendering:(BOOL)rendering
{
if (rendering != self.rendering)
{
[_displayLink invalidate];
if (rendering)
{
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render)];
_displayLink.frameInterval = (60/_glkViewController.preferredFramesPerSecond);
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
else
_displayLink = nil;
}
}
- (BOOL)rendering
{
return (_displayLink != nil);
}
- (void)setActive:(BOOL)active
{
if (active != _active)
{
_active = active;
if (_active)
[XDSPScrollView addActiveScrollView:self];
else
[XDSPScrollView removeActiveScrollView:self];
}
}
- (void)setPassTouchEventsToSparrow:(BOOL)passTouchEventsToSparrow
{
if (passTouchEventsToSparrow != _passTouchEventsToSparrow)
{
_passTouchEventsToSparrow = passTouchEventsToSparrow;
self.delaysContentTouches = !passTouchEventsToSparrow;
}
}
- (void)render
{
[_glkViewController performSelector:@selector(_updateAndDraw)];
}
+ (NSMutableArray *)activeScrollViews
{
if (!sActiveScrollViews)
sActiveScrollViews = [[NSMutableArray alloc] init];
return sActiveScrollViews;
}
+ (void)addActiveScrollView:(XDSPScrollView *)scrollView
{
GLKViewController *glkViewController = scrollView.glkViewController;
if (glkViewController && glkViewController._activeScrollView == nil)
{
glkViewController._activeScrollView = scrollView;
scrollView.rendering = YES;
}
[[self activeScrollViews] addObject:scrollView];
}
+ (void)removeActiveScrollView:(XDSPScrollView *)scrollView
{
[[self activeScrollViews] removeObject:scrollView];
GLKViewController *glkViewController = scrollView.glkViewController;
if (glkViewController._activeScrollView == scrollView)
{
scrollView.rendering = NO;
XDSPScrollView *activeScrollView = [self activeScrollViewWithGLKViewController:glkViewController];
glkViewController._activeScrollView = activeScrollView;
activeScrollView.rendering = YES;
}
}
+ (XDSPScrollView *)activeScrollViewWithGLKViewController:(GLKViewController *)glkViewController
{
for (XDSPScrollView *activeScrollView in sActiveScrollViews)
if (activeScrollView.glkViewController == glkViewController)
return activeScrollView;
return nil;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if ([_delegate respondsToSelector:@selector(scrollViewDidScroll:)])
[_delegate scrollViewDidScroll:scrollView];
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
self.active = YES;
if ([_delegate respondsToSelector:@selector(scrollViewWillBeginDragging:)])
[_delegate scrollViewWillBeginDragging:scrollView];
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
if ([_delegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)])
[_delegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (!decelerate) self.active = NO;
if ([_delegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)])
[_delegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
}
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView
{
if ([_delegate respondsToSelector:@selector(scrollViewShouldScrollToTop:)])
return [_delegate scrollViewShouldScrollToTop:scrollView];
else
return YES;
}
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView
{
if ([_delegate respondsToSelector:@selector(scrollViewDidScrollToTop:)])
[_delegate scrollViewDidScrollToTop:scrollView];
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
if ([_delegate respondsToSelector:@selector(scrollViewWillBeginDecelerating:)])
[_delegate scrollViewWillBeginDecelerating:scrollView];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
self.active = NO;
if ([_delegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)])
[_delegate scrollViewDidEndDecelerating:scrollView];
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
if ([_delegate respondsToSelector:@selector(viewForZoomingInScrollView:)])
return [_delegate viewForZoomingInScrollView:scrollView];
else
return nil;
}
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
{
if ([_delegate respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)])
[_delegate scrollViewWillBeginZooming:scrollView withView:view];
}
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
{
if ([_delegate respondsToSelector:@selector(scrollViewDidEndZooming:withView:atScale:)])
[_delegate scrollViewDidEndZooming:scrollView withView:view atScale:scale];
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
if ([_delegate respondsToSelector:@selector(scrollViewDidZoom:)])
[_delegate scrollViewDidZoom:scrollView];
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
if ([_delegate respondsToSelector:@selector(scrollViewDidEndScrollingAnimation:)])
[_delegate scrollViewDidEndScrollingAnimation:scrollView];
}
@end
@interface SPViewController () {
@public
SPTouchProcessor *_touchProcessor;
double _lastTouchTimestamp;
}
@end
@implementation UIScrollView (PassTouchEventsToSparrow)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self processTouchEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[self processTouchEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self processTouchEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self processTouchEvent:event cancelled:YES];
}
- (void)processTouchEvent:(UIEvent*)event
{
[self processTouchEvent:event cancelled:NO];
}
- (void)processTouchEvent:(UIEvent*)event cancelled:(BOOL)cancelled
{
XDSPScrollView *Self = nil;
if ([self isKindOfClass:[XDSPScrollView class]])
{
Self = (XDSPScrollView *)self;
if (Self.passTouchEventsToSparrow && [Self.glkViewController isKindOfClass:[SPViewController class]])
{
SPViewController *sparrowController = (SPViewController *)Self.glkViewController;
if (cancelled)
{
Sparrow.currentController->_lastTouchTimestamp -= 0.0001f; // cancelled touch events have an old timestamp -> workaround
}
if (!sparrowController.paused && sparrowController->_lastTouchTimestamp != event.timestamp)
{
@autoreleasepool
{
UIView *sparrowView = sparrowController.view;
CGSize viewSize = sparrowView.bounds.size;
float xConversion = Sparrow.stage.width / viewSize.width;
float yConversion = Sparrow.stage.height / viewSize.height;
// convert to SPTouches and forward to stage
NSMutableSet *touches = [NSMutableSet set];
double now = CACurrentMediaTime();
for (UITouch *uiTouch in [event allTouches])
{
UIView *touchedView = uiTouch.view;
CGPoint location = [sparrowView convertPoint:[uiTouch locationInView:touchedView] fromView:touchedView];
CGPoint previousLocation = [sparrowView convertPoint:[uiTouch previousLocationInView:touchedView] fromView:touchedView];
SPTouch *touch = [SPTouch touch];
touch.timestamp = now; // timestamp of uiTouch not compatible to Sparrow timestamp
touch.globalX = location.x * xConversion;
touch.globalY = location.y * yConversion;
touch.previousGlobalX = previousLocation.x * xConversion;
touch.previousGlobalY = previousLocation.y * yConversion;
touch.tapCount = uiTouch.tapCount;
touch.phase = (SPTouchPhase)uiTouch.phase;
[touches addObject:touch];
}
[sparrowController->_touchProcessor processTouches:touches];
sparrowController->_lastTouchTimestamp = event.timestamp;
}
}
}
}
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment