7

I am making a wallpaper app and would like to check if a user has parallax enabled on his iOS 7 device. Is there a way in objective-C that I can check that? Has Apple granted us developers access to check this boolean?

i.e. if parallax is enabled do step1 otherwise do step2

Sam B
  • 27,273
  • 15
  • 84
  • 121

3 Answers3

13

As of iOS 8:

// Returns whether the system preference for reduce motion is enabled
UIKIT_EXTERN BOOL UIAccessibilityIsReduceMotionEnabled() NS_AVAILABLE_IOS(8_0);
UIKIT_EXTERN NSString *const UIAccessibilityReduceMotionStatusDidChangeNotification NS_AVAILABLE_IOS(8_0);

For anything earlier than iOS 8, I don't think there's a legit way to tell.

John Scalo
  • 3,194
  • 1
  • 27
  • 35
5

As per Gabriele's answer there seems to be no way to read the value directly.

As a workaround, you could exploit the fact that UIInterpolatingMotionEffect does something if motion is as default but does nothing if reduce motion is enabled.

So use a custom UIView class and attach an instance of UIInterpolatingMotionEffect immediately at application start. Set a flag if the property is changed. Check that flag later.

There may be some other empirical side effects you can rely on but that prima facie assumes your user will move the device while using your app. So you'll know for certain if they have motion on and have moved their device but otherwise you won't know whether they have motion switched off or have just not moved their device.

Maybe someone smarter can come up with something better?

EDIT: sample code. Issues faced are as discussed in the comments: the property has to be animatable which has a net effect of requiring a manual polling loop. Add an instance of this view somewhere in your app, ideally at launch and so that it stays on screen for the entire lifetime of your app, subject to your view controller hierarchy allowing it, of course. Then watch the parallaxHasOccurred property. It's KVO compliant, or you can poll. As discussed, it may generate false negatives but should never generate false positives.

@interface PTParallaxTestView : UIView

// this key is KVO compliant
@property (nonatomic, assign) BOOL parallaxHasOccurred;

@end


@implementation PTParallaxTestView
{
    CGPoint _basePosition;
    UIMotionEffectGroup *_effectGroup;
}

- (void)didMoveToSuperview
{
    // cancel any detection loop we may have ongoing
    [NSObject cancelPreviousPerformRequestsWithTarget:self];

    // if anything still in doubt and we're on a view then start the
    // detection loop
    if(!self.parallaxHasOccurred && self.superview)
    {
        // add motion effects if they're not already attached; attach both to the centre property
        if(!_effectGroup)
        {
            UIInterpolatingMotionEffect *horizontalMotionEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
            horizontalMotionEffect.minimumRelativeValue = @(0);
            horizontalMotionEffect.maximumRelativeValue = @(100);

            UIInterpolatingMotionEffect *verticalMotionEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
            verticalMotionEffect.minimumRelativeValue = @(0);
            verticalMotionEffect.maximumRelativeValue = @(100);

            _effectGroup = [[UIMotionEffectGroup alloc] init];
            _effectGroup.motionEffects = @[verticalMotionEffect, horizontalMotionEffect];
            [self addMotionEffect:_effectGroup];
        }

        // kick off inspection in 0.1 seconds; we'll subsequently inspect
        // every 0.5 seconds
        [self performSelector:@selector(beginCheckingPresentationPosition) withObject:nil afterDelay:0.1];
    }
}

- (void)beginCheckingPresentationPosition
{
    // set the base position and do the first check in 0.5 seconds
    _basePosition = [[[self layer] presentationLayer] position];
    [self performSelector:@selector(checkPresentationPosition) withObject:nil afterDelay:0.5];
}

- (void)checkPresentationPosition
{
    // quick note on presentationLayer:
    //
    //  The property supplied to UIInterpolatingMotionEffect must be animatable. So we can't just create our own.
    //  UIKit will then apply effects directly to the layer. Furthermore, the layer itself will act as if in a
    //  perpetual animation so its properties won't directly be affected. We'll have to query the presentationLayer.
    //  (and that's also why we're pulling rather than using KVO or a suitable subclass to push)
    //
    CGPoint newPosition = [[[self layer] presentationLayer] position];

    // if the position has changed since the original test then things are in motion
    if(fabs(newPosition.x - _basePosition.x) > 0.125 || fabs(newPosition.y - _basePosition.y) > 0.125)
        self.parallaxHasOccurred = YES;

    // check again in 0.5 seconds only if we don't already know the answer
    if(!self.parallaxHasOccurred)
        [self performSelector:@selector(checkPresentationPosition) withObject:nil afterDelay:0.5];
}

@end
Tommy
  • 99,986
  • 12
  • 185
  • 204
  • nice finding about the `UIInterpolatingMotionEffect` trick. +1 – Gabriele Petronella Dec 03 '13 at 21:15
  • I will have to test this out though I don't know how to check UIInterpolatingMotionEffect and what values do I get? Any sample code? – Sam B Dec 03 '13 at 21:27
  • @SamBudda you don't check it, it pushes values to whatever key path you gave it. That's why you set up a dummy property that just sets a flag if it has any value set to it (or, probably, the second time it has a value set to it, to be safe). I'll try to add some sample code later but I'm at work right now (shush!) – Tommy Dec 03 '13 at 23:05
  • @Tommy i hope you didn't forget me :-) – Sam B Dec 04 '13 at 21:02
  • Sorry; we're trying to get something into the App Store for Christmas so there's a rush on, and after I got home I did forget. I'll try to remember tonight... – Tommy Dec 05 '13 at 20:41
  • Thanks for writing this up: I was brainstorming ways to detect it through the exact same technique, and it's nice to find someone beat me to it. – cbowns Feb 27 '14 at 23:41
1

For devices not supporting the parallax (i.e. any iPhone model before iPhone 5) you can just check the model and be sure that no parallax is on.

For the device supporting it you should programmatically check the Reduce Motion accessibility setting, but apparently there's no public API for checking whether that option is on.

According to the UIKit Function Reference, the only checks you can perform are the following

  • UIAccessibilityPostNotification
  • UIAccessibilityIsVoiceOverRunning
  • UIAccessibilityIsClosedCaptioningEnabled
  • UIAccessibilityRequestGuidedAccessSession
  • UIAccessibilityIsGuidedAccessEnabled
  • UIAccessibilityIsInvertColorsEnabled
  • UIAccessibilityIsMonoAudioEnabled
  • UIAccessibilityZoomFocusChanged
  • UIAccessibilityRegisterGestureConflictWithZoom
  • UIAccessibilityConvertFrameToScreenCoordinates
  • UIAccessibilityConvertPathToScreenCoordinates
Community
  • 1
  • 1
Gabriele Petronella
  • 106,943
  • 21
  • 217
  • 235
  • The problem is that a user could be on newer iPhon4S & 5 and still have parallax disabled. My wallpaper app depends heavily on this check otherwise the whole wallpaper looks goofy in parallax vs non-parallax situations i.e. I will get a lot of compliant reviews – Sam B Dec 03 '13 at 20:38
  • @SamBudda I got that, but I said there's no way of detecting it directly. Tommy's answer, however, looks hackish but promising. – Gabriele Petronella Dec 03 '13 at 21:16