2017年2月16日木曜日
CMMotionManagerのサンプル(1)
AppStoreに載せているPanViewerは、パノラマ写真をiPhone/iPadを水平方向に回転させて見るアプリです。
このアプリはCMMotionManagerをからGyroデータ取得し、デバイスの回転角度に応じて画像を移動させています。その方法について紹介します。
今回はロジックを単純にし、肝心な部分をわかりやすくしています。
ポイントは次の3点です。
・CMMotionManagerからdeviceMotionのデータを取得する。
・attitude.quaternionのx、yの値からatan2関数でデバイスの水平方向の角度を得る。
・画像を角度に応じて水平方向に移動させる。
正直なところ、attitude.quaternionの値とatan2関数を使う方法は試行錯誤の結果たどり着いたものです。このアプリではデバイスを自分の前に構えて体を回転させ、それに応じて画像を動かすことを意図しています。attitude.rollでは体は動かさず手で回転させた場合は近い動作になりますが、体を回転させた場合は期待通りになりません。また、回転軸がデバイスの縦軸のため、横にした場合はattitude.pitchの方が近い動作になり、対応困難になります。
このサンプルでは次の問題があります。
・画像移動がギクシャクする。
・画像が画面からはみ出すとその部分が空白になる。
これらについては次回のサンプルで対処します。
サンプルアプリのプロジェクトはここからダウンロードできます。
以下はプロジェクト中のViewControllerのコード(一部を省略)です。このサンプルでは画像移動に関するコードがあるのはこのクラスだけです。
//CoreMotionをインポート
import UIKit
import CoreMotion
//このサンプルでは表示画面はViewControllerだけ
class ViewController: UIViewController {
//CMMotionManagerのインスタンスをセットする。
var motionManager: CMMotionManager?
//画像を表示するUIImageView。画像の左右の端が画面内にある時に画像を繋げて表示するために3つ使う。
@IBOutlet weak var imageView: UIImageView? //Center
//3つのUIImageViewのsuperview。
//画像移動はimageViewContainerのlayerを用いて行う。
//位置判定が容易になるよう、frame.originは(0,0)とし、サイズはimageViewと同じにする。
@IBOutlet weak var imageViewContainer: UIView?
//motionManagerの値から計算した位置を画像の画像位置に合わせるための調整値。
var adjX: CGFloat = 0
//画像視野角。iPhoneで撮影したパノラマ写真の場合は概ね180度。
let angle = 180.0
//motionManagerから位置情報を取得するタイマーイベント間隔。
let interval = 0.1
//viewWillAppearで画像設定
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.imageView!.frame = self.view.frame
self.imageViewContainer!.frame = self.view.frame
self.setImage(imageName: "ashinoko.jpg");
self.setImageViewSize(viewSize: self.view.frame.size)
}
//画面表示後にmoveImageを呼ぶ。moveImageはタイマーイベントでループ実行する。
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
moveImage()
}
//デバイス回転時に縦、横の画面サイズに合わせてImageViewのサイズを変更する。
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
self.setImageViewSize(viewSize: size)
}
//motionManagerをスタートさせる。
func startMotionManger() {
if self.motionManager == nil {
self.motionManager = CMMotionManager()
}
self.motionManager!.startDeviceMotionUpdates()
}
//motionManagerをストップさせる。
func stopMotionManger() {
NSObject.cancelPreviousPerformRequests(withTarget:self);
if let motionManager = self.motionManager {
motionManager.stopDeviceMotionUpdates()
}
self.motionManager = nil;
}
//imageViewに画像をセットする。
func setImage(imageName: String) {
if let image = UIImage(named: imageName) {
if self.imageView!.image != image {
self.imageView!.image = image;
}
}
}
//3つのUIImageViewとimageViewContainerが画面にフィットするようにサイズ、位置を設定する。
func setImageViewSize(viewSize: CGSize) {
self.adjX = 0
//3つのimageView及びimageViewContainerのサイズを設定する。
if let imageView = self.imageView {
if let image = imageView.image {
//imageViewが画面にフィットするようにサイズを設定する。
imageView.frame.size.width = image.size.width * viewSize.height / image.size.height
imageView.frame.size.height = viewSize.height
//imageViewに合わせてimageViewContainerのサイズを設定。
self.imageViewContainer!.frame.size = imageView.frame.size
self.imageViewContainer!.layer.position.x = 0
}
}
}
//タイマーイベントで実行し、デバイスの回転に応じて画像を水平方向に移動する。
func moveImage() {
if self.motionManager != nil {
self.moveImageLayer()
} else {
self.startMotionManger();
}
self.perform(#selector(moveImage), with: nil, afterDelay: interval)
}
//self.imageViewContainer.layerの位置を変えることで画像を水平方向に移動する。
//animationなし。ロジックはシンプル。
func moveImageLayer() {
let to = self.newPosition()
self.imageViewContainer!.layer.position = to
}
func newPosition() -> CGPoint {
let from = self.imageViewContainer!.layer.position
//deviceMotionのquaternionの値から回転角を求め、水平方向の移動量を計算する。
if let deviceMotion = self.motionManager?.deviceMotion {
let attitude: CMAttitude = deviceMotion.attitude
let q: CMQuaternion = attitude.quaternion
//angleはimageの視野角。360、180など。iPhoneで撮影したパノラマ写真なら180。
let tx = (CGFloat)(atan2(q.y, q.x) / Double.pi * (360 / angle))
let w = self.imageView!.frame.size.width
var x = w * tx + adjX;
//初期状態ではadjX=0。この場合、画像表示位置と計算上の位置が一致するようにadjXを設定する。
if adjX == 0 {
adjX = from.x - x
x = from.x
}
return CGPoint(x:x, y:from.y)
}
return from
}
}
登録:
コメントの投稿 (Atom)
0 件のコメント:
コメントを投稿