问题
Suppose you are holding an iphone/ipad vertically in front of you with the screen facing you, in portrait orientation. You tilt the device to one side, keeping the screen facing you. How do you measure that static tilt angle using CMMotionManager? It seems a simple question which should have a simple answer, yet I cannot find any method that does not disappear into quaternions and rotation matrices.
Can anyone point me to a worked example?
回答1:
Look at gravity
:
self.deviceQueue = [[NSOperationQueue alloc] init];
self.motionManager = [[CMMotionManager alloc] init];
self.motionManager.deviceMotionUpdateInterval = 5.0 / 60.0;
// UIDevice *device = [UIDevice currentDevice];
[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryZVertical
toQueue:self.deviceQueue
withHandler:^(CMDeviceMotion *motion, NSError *error)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
CGFloat x = motion.gravity.x;
CGFloat y = motion.gravity.y;
CGFloat z = motion.gravity.z;
}];
}];
With this reference frame (CMAttitudeReferenceFrameXArbitraryZVertical
), if z
is near zero, you're holding it on a plane perpendicular with the ground (e.g. as if you were holding it against a wall) and as you rotate it on that plane, x
and y
values change. Vertical is where x
is near zero and y
is near -1.
Looking at this post, I notice that if you want to convert this vector into angles, you can use the following algorithms.
If you want to calculate how many degrees from vertical the device is rotated (where positive is clockwise, negative is counter-clockwise), you can calculate this as:
// how much is it rotated around the z axis
CGFloat angle = atan2(y, x) + M_PI_2; // in radians
CGFloat angleDegrees = angle * 180.0f / M_PI; // in degrees
You can use this to figure out how much to rotate the view via the Quartz 2D transform
property:
self.view.layer.transform = CATransform3DRotate(CATransform3DIdentity, -rotateRadians, 0, 0, 1);
(Personally, I update the rotation angle in the startDeviceMotionUpdates
method, and update this transform
in a CADisplayLink
, which decouples the screen updates from the angle updates.)
You can see how far you've tilted it backward/forward via:
// how far it it tilted forward and backward
CGFloat r = sqrtf(x*x + y*y + z*z);
CGFloat tiltForwardBackward = acosf(z/r) * 180.0f / M_PI - 90.0f;
回答2:
It is kind of a late answer but you can found a working example on github and the blog article that comes with it.
To summarize the article mentioned above, you can use quaternions to avoid the gimbal lock problem that you are probably facing when holding the iPhone vertically.
Here is the coding part that compute the tilt (or yaw) :
CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
double yaw = asin(2*(quat.x*quat.z - quat.w*quat.y));
// use the yaw value
// ...
You can even add a simple Kalman filter to ease the yaw :
CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
double yaw = asin(2*(quat.x*quat.z - quat.w*quat.y));
if (self.motionLastYaw == 0) {
self.motionLastYaw = yaw;
}
// kalman filtering
static float q = 0.1; // process noise
static float r = 0.1; // sensor noise
static float p = 0.1; // estimated error
static float k = 0.5; // kalman filter gain
float x = self.motionLastYaw;
p = p + q;
k = p / (p + r);
x = x + k*(yaw - x);
p = (1 - k)*p;
self.motionLastYaw = x;
// use the x value as the "updated and smooth" yaw
// ...
回答3:
Here is an example that rotates a UIView self.horizon
to keep it level with the horizon as you tilt the device.
- (void)startDeviceMotionUpdates
{
CMMotionManager* coreMotionManager = [[CMMotionManager alloc] init];
NSOperationQueue* motionQueue = [[NSOperationQueue alloc] init]
CGFloat updateInterval = 1/60.0;
CMAttitudeReferenceFrame frame = CMAttitudeReferenceFrameXArbitraryCorrectedZVertical;
[coreMotionManager setDeviceMotionUpdateInterval:updateInterval];
[coreMotionManager startDeviceMotionUpdatesUsingReferenceFrame:frame
toQueue:motionQueue
withHandler:
^(CMDeviceMotion* motion, NSError* error){
CGFloat angle = atan2( motion.gravity.x, motion.gravity.y );
CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
self.horizon.transform = transform;
}];
}
This is a little oversimplified - you should be sure to have only one instance of CMMotionManager in your app so you want to pre-initialise this and access it via a property.
回答4:
Since iOS8 CoreMotion also returns you a CMAttitude
object, which contains pitch
, roll
and yaw
properties, as well as the quaternion. Using this will mean you don't have to do the manual maths to convert acceleration to orientation.
来源:https://stackoverflow.com/questions/15646433/measuring-tilt-angle-with-cmmotionmanager