2017年2月18日土曜日
CMMotionManagerのサンプル(2)
AppStoreに載せているPanViewerは、パノラマ写真をiPhone/iPadを水平方向に回転させて見るアプリです。
このアプリはCMMotionManagerをからGyroデータ取得し、デバイスの回転角度に応じて画像を移動させています。
その方法について紹介ですが、今回は前回のサンプルの次の問題点に対処します。
・画像移動がギクシャクする。
・画像が画面からはみ出すとその部分が空白になる。
前者についてはanimationを利用して画像移動をスムーズにします。
→ func moveImageLayer 参照
後者については同じ画像をセットしたUIImageViewを3つ並べ、空白が生じないようにします。
→ func setImageViewSize 参照
また、移動後の位置が画面からはみ出す場合に位置調整を行います。
→ func newPosition 参照
今回のサンプルアプリのプロジェクトはここからダウンロードできます。
以下、前回との変更点だけ載せます。
//画像を表示するUIImageView。画像が画面からはみ出した時に画像を繋げて表示するために3つ使う。
@IBOutlet weak var imageViewL: UIImageView? //Left
@IBOutlet weak var imageViewC: UIImageView? //Center
@IBOutlet weak var imageViewR: UIImageView? //Right
//3つのUIImageViewのsuperview。
//画像移動はimageViewContainerのlayerを用いて行う。
//位置判定が容易になるよう、frame.originは(0,0)とし、サイズはimageViewCと同じにする。
@IBOutlet weak var imageViewContainer: UIView?
//中央のUIImageViewの左右の端が画面に入ると画像外の部分が空白になる。
//その空白を埋めるために左右にもUIImageViewを置き、同じ画像をセットする。
func setImage(imageName: String) {
if let image = UIImage(named: imageName) {
if self.imageViewC!.image != image {
self.imageViewL!.image = image; //左側
self.imageViewC!.image = image; //中央
self.imageViewR!.image = image; //右側
}
}
}
//3つのUIImageViewとimageViewContainerが画面にフィットするようにサイズ、位置を設定する。
func setImageViewSize(viewSize: CGSize) {
self.adjX = 0
//originX: 3つのUIImageViewのorigin.xを設定するために用いる。
var originX: CGFloat = 0;
//3つのimageView及びimageViewContainerのサイズを設定する。
for imageView in [self.imageViewL!, self.imageViewC!, self.imageViewR!] {
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.frame.origin.y = 0;
//origin.xを-imageView.frame.size.width, 0, imageView.frame.size.widthの順にセット。
imageView.frame.origin.x = originX - imageView.frame.size.width;
originX += imageView.frame.size.width;
//imageViewCに合わせてimageViewContainerのサイズを設定。
if imageView == self.imageViewC {
self.imageViewContainer!.frame.size = self.imageViewC!.frame.size
self.imageViewContainer!.layer.position.x = 0
}
}
}
}
//self.imageViewContainer.layerの位置を変えることで画像を水平方向に移動する。
//画像移動をスムーズにするためanimationを使用する。
func moveImageLayer() {
let (from, to) = self.newPosition()
let anim: CABasicAnimation = CABasicAnimation(keyPath: "position")
anim.fromValue = from
anim.toValue = to
anim.duration = self.interval
//animation終了後に初期位置に戻るので、layer.positionを移動後の位置にしておく。
self.imageViewContainer!.layer.position = to
self.imageViewContainer!.layer.add(anim, forKey:"move-layer")
}
//デバイスの回転から画像の表示位置を計算する。
func newPosition() -> (CGPoint, CGPoint) {
var 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.imageViewC!.frame.size.width
var x = w * tx + adjX;
//初期状態ではadjX=0。この場合、画像表示位置と計算上の位置が一致するようにadjXを設定する。
if adjX == 0 {
adjX = from.x - x
x = from.x
}
let maxX = w / 2
var posAdjusted = false
//mageViewCの移動後、画面に欠ける部分ができる場合画面を覆う位置に調整する。
//atan2の値はpiから-pi、またはその逆へ非連続に変化する。
//視野角により画像のwidthより大きな変化(180度の場合はwidth*2前後)になるため、whileで適正位置に移動するまでループする。
if x > maxX {
//mageViewCが画面の右端から画像が消える位置に移動する場合。
while(x > maxX) {
adjX -= w
x -= w
}
posAdjusted = true
} else if x < -maxX {
//mageViewCが画面の左端から画像が消える位置に移動する場合。
while(x < -maxX) {
adjX += w
x += w
}
posAdjusted = true
}
if posAdjusted {
//位置調整が行われた場合、layerの移動量がmaxX以上になったら調整後の位置に合わせてlayerのanimation開始位置も調整する。
//少々いい加減な判断だが、よほど激しく回転させなけばチラつきは発生しない。
let dx = x - self.imageViewContainer!.layer.position.x
if dx > maxX {
from.x += w;
} else if dx < -maxX {
from.x -= w;
}
//位置調整時はanimationを使用しない。
self.imageViewContainer!.layer.position.x = from.x
}
return (from, CGPoint(x:x, y:from.y))
}
return (from, from)
}
登録:
コメントの投稿 (Atom)
0 件のコメント:
コメントを投稿