2017年6月21日水曜日

swift エラーメッセージ - 型変換(Type Casting)

Cannot downcast from 'UIView' to a more optional type 'UITableViewHeaderFooterView?'
  • エラーが発生するコード
     if let headerView = view as! UITableViewHeaderFooterView {
       ...
     }
  • 原因:viewがOptionalでないため、UITableViewHeaderFooterViewとして扱えない。
  • 修正
    let headerView = view as! UITableViewHeaderFooterView
    ...

Initializer for conditional binding must have Optional type, not 'UITableViewCell'
  • エラーが発生するコード
    if let cell = sender as! UITableViewCell {
       ....
    }
  • 原因:senderがOptionalなのでUITableViewCellとして扱えない。
  • 修正
    if let cell = sender as! UITableViewCell? {
        ....
    }
Cannot convert value of type 'Any?' to expected argument type 'UITableViewCell'
  • エラーが発生するコード
    if let indexPath = self.tableView.indexPath(for: sender) {
        ...
    }
  • 原因:senderの型がAny?なのでキャストが必要。
  • 修正
    if let indexPath = self.tableView.indexPath(for: sender as! UITableViewCell) {
        ...
    }



2017年6月18日日曜日

swift エラーメッセージ


メッセージ:Editor placeholder in source file
Xcodeの自動修正でコードを置き換えた場合に、適切な値に置き換えるべきplaceholderがセットされることがある。
これをそのままにしてビルドした場合に発生する。

例: 
self.navigationController!.setNavigationBarHidden(true, animated: Bool)

Boolがplaceholder。次のように修正する。
self.navigationController!.setNavigationBarHidden(true, animated: true)



メッセージ:Result of call to 'someFunc()' is unused" 
someFuncの戻り値を変数にセットしてが、それを使用しない場合に発生する警告。

例:
let val = someFunc()

valを使用しない場合に発生する。次のように変更する
let _ = someFunc()


メッセージ:Cannot use mutating member on immutable value: '...' is a 'let' constant
パラメータの変数はletで宣言されたConstant扱いなので代入不可のため発生する警告。

例:
func someFunc(someParam: SomeType) {
    someParam = someObj
}
letかvarで別の変数に代入して使用する。代入後に値を変更するかどうかでlet, varを使い分ける。名前は同じでも良いが、別オブジェクトになる。
func someFunc(someParam: SomeType) {
    var someParam = someObj
}

呼び出し元の値を変更したい場合は新規オブジェクトをreturnし、呼び出し元でオブジェクトを入れ替える。
func someFunc(someParam: SomeType) {
    var newParam = someObj
    ....
    return newParam
}

呼び出し元にあるオブジェクト (ArrayやDictionanayなど)を置換せず、そのプロパティーや要素の一部を変更したいような場合はsomeParamをinoutで宣言する。
varで宣言されていれば、someParamそのものの置換も可能。
func someFunc(someParaminout SomeType) {
    someParam.someProp = someVal
}

この場合は、呼び出し側を次のようにする。
someFunc(&someObj)



メッセージ:Unknown class SomeClass in Interface Builder file.
IBで設定したクラスが見つからない場合に発生する。
ソースコードでクラスを追加してからIBでインスタンスにそのクラウを設定していれば、このエラーは発生しない。原因は様々と思われるが、次の場合が含まれる。
  • IBで設定した後で、ソースコードでクラス名を変更した。
  • 他のプロジェクトからIBで部品のコピー/ペーストを行った。
当該の部品をIBで選択、インスペクターでクラス設定をチェックする。
前者の場合は変更前のクラス名のままになっているので、プルダウンで新しい名前のものに変更する。
後者の場合はそのクラス名しか表示されない。プルダウンで変更できないので、一旦手入力でスーパークラスの名前に書き換える。その後プルダウンを開くとそのサブクラスの一覧が表示されるので、適切なものに設定する。






2017年5月6日土曜日

PhotoLibrayにある画像、ビデオのファイルの名前、作成日時(アップロード日時)を取得する方法(iCloud共有含む)

PhotoLibrayにある画像、ビデオのファイルの名前、作成日時(アップロード日時)を取得する方法(iCloud共有含む)


iCloud共有にアップロードされたファイルをダウンロードすると、jpegの場合はExifデータから撮影日がわかるが、ビデオは撮影日時を調べるのに写真アプリで情報をチェックする必要があり、面倒だった。そこでプログラムでファイル名、CreateionDateを取得しようとすてみると、これもまた意外と面倒でした。

次のサンプルコードはPhotoLibray内のiCould、ローカル、画像、ビデオ、ライブフォトを含めてファイル名を取得するようにしたものです。

iPhoneで撮影したvideoをそのデバイスからiCloud共有にアップロードした場合は作成日時=撮影日時と考えて良いと思いますが、それ以外で撮影し、Macからアップロードした場合は作成日時=アップロード日時になるでしょう。また、その場合に拡張子がMP4以外の場合、iCloud共有からイクスポートしたファイルはもとの拡張子(M4Vなど)ですが、以下のプログラムで取得するファイル名の拡張子は全てMP4になります。

iPhoneとMacでiCloud共有の同じ写真やビデオのファイル名を比べてみると、同じものが多いものの、異なるものが結構あります。デバイス毎にファイル名が異なりますね。

MyAssetListViewControllerを呼び出す遷移元で、次の要領でPHAssetをフェッチする。


let sharedAlbums = PHAssetCollection.fetchAssetCollections(
      with: PHAssetCollectionType.album,
      subtype: PHAssetCollectionSubtype.albumCloudShared,

      options: fetchOptions)

let assetsFetchResults = PHAsset.fetchAssets(in: sharedAlbums, options: nil)


画面遷移時にMyAssetListViewControllerにassetsFetchResultsをセットする。



    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let assetGridViewController = segue.destination as! MyAssetListViewController                      assetGridViewController.assetsFetchResults = assetsFetchResults
    }


//---- MyAssetListViewController ----


import UIKit
import Photos

/// UITableViewCellにプロパティーを追加。IBでコネクトする。
class MyTableViewCell: UITableViewCell {
    @IBOutlet var thumbnail: UIImageView?
    @IBOutlet var fileName: UILabel?
    @IBOutlet var creationDate: UILabel?
    @IBOutlet var resolution: UILabel?
    @IBOutlet var duration: UILabel?
}

class MyAssetListViewController: UITableViewController {

    // MyRootListViewControllerから画面遷移する時にセットするインスタンス変数
    var assetsFetchResults: PHFetchResult<PHAsset>?
    var albumTitle: String?
    
    var assetToFileName = [PHAsset: String]()
    let reuseIdentifier = "Cell"
    let imageManager = PHCachingImageManager()
    let dateFormatter = DateFormatter()
    let timeFormatter = DateComponentsFormatter()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.navigationItem.title = albumTitle
        // Formatterの設定
        dateFormatter.timeZone = TimeZone.current
        dateFormatter.dateFormat = "yyyy/MM/dd HH:mm:ss"
        timeFormatter.allowedUnits = [NSCalendar.Unit.minute, NSCalendar.Unit.second]
        timeFormatter.zeroFormattingBehavior = .pad
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        guard let assets = self.assetsFetchResults else { return 0 }
        return assets.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyTableViewCell
        guard let assets = self.assetsFetchResults else { return cell }
        
        let asset = assets[indexPath.item]
        cell.tag = asset.localIdentifier.hash
        //iCloudへアクセスするようにOptionを設定
        let options = PHImageRequestOptions()
        options.isNetworkAccessAllowed = true
        self.imageManager.requestImage(for: asset,
                         targetSize: CGSize(width:80, height:80),
                         contentMode: PHImageContentMode.aspectFit,
                         options: options,
                         resultHandler: {(result, info)->Void in
                         //非同期処理ためcellの再利用により異なるassetの内容を表示している場合がある。
                         //localIdentifierが異なる場合は何もしない。
                         guard cell.tag == asset.localIdentifier.hash,
                               let result = result else { return }
                         cell.thumbnail!.image = result
        })
        // assetから取得できるデータをセット
        cell.fileName!.text = nil
        cell.creationDate!.text = asset.creationDate == nil ? "(unknown)" : dateFormatter.string(from:asset.creationDate!)
        cell.resolution!.text = "\(asset.pixelWidth) x \(asset.pixelHeight)"
        cell.duration!.text = timeFormatter.string(from: asset.duration)
        
        // ファイル名セット
        // 既にassetResourcesがキャッシュされている場合はそれからファイル名を取得する。
        // iOS10時点では、LivePhotoの場合にassetResourcesJPGMOVの対がセットされている。
        let assetResources = PHAssetResource.assetResources(for: asset)
        if setFileName(from: assetResources, to: cell) == false {
            // assetResourcesが空の場合はfalseが返される。
            // LivePhoto以外のimagevideoの場合は空。LivePhotoでもキャッシュされていない場合は空。
            // assetResourcesがない場合はrequestContentEditingInputを取得する。
            setFileNameFromContentEditingInput(with: asset, to: cell)

            // AVURLAssetを使う場合はこちらに変更。
            //setFileNameFromAVAsset(with: asset, to: cell)
        }
        return cell
    }
    
    /// assetResource配列の各要素のoriginalFilenameを結合してlabelにセットする。
    /// assetResourcesが空の場合は"(searching)"をセットする。
    /// - parameter from: PHAssetResource.assetResources(asset)
    /// - parameter cell: MyTableViewCell
    func setFileName(from assetResources:[PHAssetResource], to cell: MyTableViewCell) -> Bool {
        let label = cell.fileName!
        if assetResources.count > 0 {
            label.text = nil
            for assetResource in assetResources {
                let fileName = assetResource.originalFilename
                if label.text == nil {
                    label.text = fileName
                } else {

                    //assetResourcesが複数あるので同じfileNameが重複する可能性がある。
                    label.text = label.text! + ", " + fileName
                }
            }
            return true
        } else {
            //assetResourcesが空の場合は"(searching)"をセット。
            label.text = "(searching)"
            return false
        }
    }
    
    /// assetからContentEditingInputを得て、livePhotoimagevideoの種類に応じてfileNameをセットする。
    /// - parameter with: PHAsset
    /// - parameter to: MyTableViewCell
    func setFileNameFromContentEditingInput(with asset: PHAsset, to cell: MyTableViewCell) {
        let options = PHContentEditingInputRequestOptions()
        options.isNetworkAccessAllowed = true
        asset.requestContentEditingInput(with: options,
           completionHandler: { (contentEditingInput, info) -> Void in
           guard cell.tag == asset.localIdentifier.hash else { return }
           
           if let livePhoto = contentEditingInput?.livePhoto {
               // live photoの場合はassetResourcesを取得してファイル名をセットする。
               let _ = self.setFileName(from: PHAssetResource.assetResources(for: livePhoto), to: cell)
           } else if let url = contentEditingInput?.fullSizeImageURL {
               // imageの場合
               cell.fileName!.text = url.path.components(separatedBy: "/").last
           } else if let urlAsset = contentEditingInput?.audiovisualAsset as? AVURLAsset {
               // videoの場合
               cell.fileName!.text = urlAsset.url.path.components(separatedBy: "/").last
               cell.duration!.text = self.timeFormatter.string(from: asset.duration)
           } else {
               cell.fileName!.text = "(not found)"
           }
        })
    }
}

-----------------------------

Live Photoの場合にJPG、MOVの対を取得するにはContentEditingInputを使わないとダメそうですが、MOVだけでよければ次のようなコードでAVURLAssetからファイル名を取得することができます。
上のコードのsetFileNameFromContentEditingInput呼び出しをこれの呼び出しに変更します。

func setFileNameFromAVAsset(with asset: PHAsset, to cell: MyTableViewCell) {
    if asset.mediaType == PHAssetMediaType.video {
        //iCloudへアクセスするようにOptionを設定
        let options = PHVideoRequestOptions()
        options.isNetworkAccessAllowed = true
        //PHAssetに対応するAVPlayerItemを取得
        let _ = self.imageManager.requestAVAsset(forVideo: asset,
                       options: options,
                       resultHandler: { (avAsset, avAudioMix, info) -> Void in
                           guard let urlAsset = avAsset asAVURLAsset,
                                 let fileName =   urlAsset.url.path.components(separatedBy: "/").last
                           else { return; }
                       cell.fileName!.text = fileName
                    })
        }
    }

-----------------------------

PHAssetからvalue(for: "filename")でファイル名を取得する方法もありました。
ビデオの場合も拡張子JPGのファイル名が返されますが、用途によってはこれで十分な場合もあるでしょう。
なお、PHAssetの公開仕様ではないので、今後のバージョンで同じ方法が使い続けれらるかはわかりません。


cell.fileName!.text = asset.value(forKey: "filename") as? String ?? "<no filename>"


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)
    }




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
    }
}