Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Programmatically scroll scroll views in the iOS 7 Simulator.
In the iOS 7 Simulator, scroll views' pan gesture recognizers fail to cause those views to scroll in response to UIAutomation drag gestures: a scroll view (and its pan gesture recognizer) receives the touches involved in the gesture, but something inscrutable goes wrong in the view's response to that gesture. Subliminal uses a private UIAccessibility API to programmatically scroll the views (with a higher degree of fidelity than `-setContentOffset:` would afford, e.g. the private API notifies the delegate of appropriate scroll events as if the view was being dragged). This method's usage need not be obfuscated because it is compiled for the Simulator alone.
- Loading branch information
Jeff Wear
committed
Oct 15, 2013
1 parent
5caeacc
commit 90e6d1c
Showing
8 changed files
with
198 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
Sources/Classes/UIAutomation/User Interface Elements/UIScrollView+SLProgrammaticScrolling.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// | ||
// UIScrollView+SLProgrammaticScrolling.h | ||
// Subliminal | ||
// | ||
// Created by Jeffrey Wear on 9/19/13. | ||
// Copyright (c) 2013 Inkling. All rights reserved. | ||
// | ||
|
||
#import <UIKit/UIKit.h> | ||
|
||
#if TARGET_IPHONE_SIMULATOR | ||
|
||
/** | ||
The methods in the `UIScrollView(SLProgrammaticScrolling)` category allow Subliminal | ||
to programmatically scroll a `UIScrollView`. Subliminal requires this ability because, | ||
in the iOS 7 Simulator, scroll views' pan gesture recognizers fail to respond to UIAutomation drag gestures. | ||
NOTE: The implementation of this category uses a private API. _However_, this poses no risk | ||
of discovery by Apple's review team (to projects linking Subliminal) because this category | ||
is only compiled for the Simulator. | ||
*/ | ||
@interface UIScrollView (SLProgrammaticScrolling) | ||
|
||
/** | ||
Scrolls the receiver by applying a relative content offset. | ||
This method is to be used, instead of `-setContentOffset:animated:`, | ||
because it notifies the receiver's delegate of scroll events as if a user was dragging the receiver. | ||
Each offset specified as argument to this method describes a pair of _x_ and _y_ values, | ||
each ranging from `0.0` to `1.0`. These values represent, respectively, relative horizontal | ||
and vertical positions within the receiver's `-accessibilityFrame`, with `{0.0, 0.0}` | ||
as the top left and `{1.0, 1.0}` as the bottom right. | ||
This method will scroll with animation, but the duration of that animation | ||
is not known or subject to Subliminal's control. | ||
@warning This method uses an API which is only available as of iOS 7 and so must not be called | ||
when running on iOS 6.1 or below. (It cannot be conditionally compiled for the iOS 7 SDK | ||
because it is required by applications built using older SDKs but running on iOS 7). | ||
@warning This method does not deliver touch events to the receiver, | ||
thus UIAutomation's `UIAElement.dragInsideWithOptions` should be used concurrently. | ||
@warning This method disables the receiver's `panGestureRecognizer`. (This method should be only used | ||
in circumstances where that recognizer is non-functional anyway.) | ||
@param startOffset The offset, within the element's accessibility frame, at which to begin dragging. | ||
@param endOffset The offset, within the element's accessibility frame, at which to end dragging. | ||
@exception NSInternalInconsistencyException if this method is called when running on iOS 6.1 or below. | ||
*/ | ||
- (void)slScrollWithStartOffset:(CGPoint)startOffset endOffset:(CGPoint)endOffset; | ||
|
||
@end | ||
|
||
#endif |
63 changes: 63 additions & 0 deletions
63
Sources/Classes/UIAutomation/User Interface Elements/UIScrollView+SLProgrammaticScrolling.m
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
// | ||
// UIScrollView+SLProgrammaticScrolling.m | ||
// Subliminal | ||
// | ||
// Created by Jeffrey Wear on 9/19/13. | ||
// Copyright (c) 2013 Inkling. All rights reserved. | ||
// | ||
|
||
#import "UIScrollView+SLProgrammaticScrolling.h" | ||
|
||
#if TARGET_IPHONE_SIMULATOR | ||
|
||
/* | ||
Unlike `-[UIScrollView setContentOffset]`, | ||
`-[UIScrollViewAccessibility(SafeCategory) accessibilityApplyScrollContent:sendScrollStatus:animated:]` | ||
(loaded onto `UIScrollView` at runtime in iOS 7) notifies the delegate of scroll events as if the scroll view | ||
was actually being dragged. We need to declare the method because it's a private API, | ||
but we don't need to obscure the use of this API because this is only compiled for the Simulator. | ||
*/ | ||
@interface UIScrollView (SLProgrammaticScrolling_Internal) | ||
|
||
- (void)accessibilityApplyScrollContent:(CGPoint)contentOffset sendScrollStatus:(BOOL)sendStatus animated:(BOOL)animated; | ||
|
||
@end | ||
|
||
@implementation UIScrollView (SLProgrammaticScrolling) | ||
|
||
- (void)slScrollWithStartOffset:(CGPoint)startOffset endOffset:(CGPoint)endOffset { | ||
// `-[UIScrollViewAccessibility(SafeCategory) accessibilityApplyScrollContent:sendScrollStatus:animated:]` | ||
// is only present in iOS 7 | ||
// we conditionally define `kCFCoreFoundationVersionNumber_iOS_6_1` so that Subliminal | ||
// can be continue to be built using the iOS 6.1 SDK until Travis is updated | ||
// (https://github.com/travis-ci/travis-ci/issues/1422) | ||
#ifndef kCFCoreFoundationVersionNumber_iOS_6_1 | ||
#define kCFCoreFoundationVersionNumber_iOS_6_1 793.00 | ||
#endif | ||
NSAssert(kCFCoreFoundationVersionNumber > kCFCoreFoundationVersionNumber_iOS_6_1, | ||
@"%s is only supported on iOS 7.", __PRETTY_FUNCTION__); | ||
|
||
CGRect accessibilityFrame = self.accessibilityFrame; | ||
CGPoint currentContentOffset = self.contentOffset; | ||
CGPoint newContentOffset = { | ||
.x = currentContentOffset.x + ((startOffset.x - endOffset.x) * CGRectGetWidth(accessibilityFrame)), | ||
.y = currentContentOffset.y + ((startOffset.y - endOffset.y) * CGRectGetHeight(accessibilityFrame)) | ||
}; | ||
|
||
// despite not ultimately causing the scroll view to scroll, the scroll view's pan gesture recognizer | ||
// does appear to receive touches (i.e. those concurrently delivered by `UIAElement.dragInsideWithOptions`) | ||
// and the scroll view then gets as far as calling `-[UIScrollView(UIScrollViewInternal) _scrollViewWillBeginDragging]` | ||
// ...and then some conflict between that flow, and `-accessibilityApplyScrollContent:sendScrollStatus:animated:` | ||
// having been called, causes an occasional crash | ||
// so, since the gesture recognizer's not going to do the job anyway, we disable it | ||
self.panGestureRecognizer.enabled = NO; | ||
|
||
// I don't know what the second parameter does--either `NO` or `YES` appears to work in the Simulator | ||
// --but it is `NO` when scrolling with VoiceOver on in an iOS device; | ||
// we pass animated:`YES` because a user would drag with some duration. | ||
[self accessibilityApplyScrollContent:newContentOffset sendScrollStatus:NO animated:YES]; | ||
} | ||
|
||
@end | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters