記録。

めも。

viewを画面からはみ出ないように移動させたり、拡大させる

久しぶりの更新になってしまいました。。。

今日は、お題に書いてある通りです。 移動や拡大させる方法は書いてあるんですけど、実際にアプリに処理を組み込むことになったら、 はみ出ちゃいかんだろーと思いはみ出ないようにする処理を実装したんで、メモメモ。

イメージはこんな感じになります。 f:id:jksdaba:20180616171203g:plain

移動時にはみ出さないようにする

そんなに大したことはありません。 判定させるだけです。以下のように。

  //はみ出していないか判定する
        if frame.origin.x < 0 {
            frame.origin.x = 0
        }
        if frame.origin.y < 0 {
            frame.origin.y = 0
        }
        if frame.maxX > UIScreen.main.bounds.width {
            frame.origin.x = UIScreen.main.bounds.width - frame.width
        }
        if frame.maxY > UIScreen.main.bounds.height {
            frame.origin.y = UIScreen.main.bounds.height - frame.height
        }

上記の処理をtouchesMovedやtouchesEndedに組み込めば移動時にはみ出すことは無くなります。

拡大時にはみ出さないようにする

これは移動の時より若干厄介でした。

    //現在のscaleを保持する
    private var currentScale:CGFloat = 1.0
    
    //最大のscaleを保持する
    private var maxScale: CGFloat!

 ...

    @objc
    private func pinchAction(_ sender: UIPinchGestureRecognizer) {
        //アクション中にどんどん更新されるscaleを保持するための配列
        var scaleArray: [CGFloat] = []
        
        switch sender.state {
        case .began:
            break
        case .changed:
            // senderのscaleは、指を動かしていない状態が1.0となる
            // 現在の拡大率に、(scaleから1を引いたもの) / 10(補正率)を加算する
            currentScale = currentScale + (sender.scale - 1) / 10
            // ピンチ中の拡大率は0.3〜2.5倍、指を離した時の拡大率は0.5〜2.0倍とする
            // 拡大率が基準から外れる場合は、補正する
            if currentScale < 0.3 {
                currentScale = 0.3
            } else if currentScale > 2.5 {
                currentScale = 2.5
            }
            
            //拡大、縮小を適用する前に画面の境界に位置していないか確認する
            //境界に位置していた場合は、処理を終える
            guard self.frame.origin.x != 0 else {
                return
            }
            guard self.frame.origin.y != 0 else {
                return
            }
            guard self.frame.maxX != UIScreen.main.bounds.width else {
                return
            }
            guard self.frame.maxY != UIScreen.main.bounds.height else {
                return
            }
            
            //拡大、縮小
            self.transform = CGAffineTransform(scaleX: currentScale, y: currentScale)
            //適用していくscaleを配列で保持していく
            scaleArray.append(currentScale)
            
            //拡大、縮小して画面からはみ出ていないか確認する
            //はみ出ていなければ、処理を終える
            if self.frame.origin.x < 0 || self.frame.origin.y < 0 || self.frame.maxX > UIScreen.main.bounds.width || self.frame.maxY > UIScreen.main.bounds.height {
                //拡大、縮小してはみ出してしまった場合
                if maxScale == nil {
                   //はみ出る直前のscaleを取得
                   maxScale = scaleArray[scaleArray.count - 1]
                }
                //はみ出ないようにサイズを更新
                self.transform = CGAffineTransform(scaleX: maxScale, y: maxScale)
            }
            
        default:
            if currentScale < 0.5 {
                UIView.animate(withDuration: 0.2, animations: {
                    self.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
                }, completion: nil)
            }
            //アクション終了時で初期化をする
            deinitScale()
        }
    }
    
    private func deinitScale() {
        //currentScaleを初期の1.0に戻す
        //特に指が画面から離れた時に初期に戻しておかないと次、拡大・縮小する際におかしくなる
        currentScale = 1.0
        //maxScaleもいったん初期化学
        //2回目以降の拡大、縮小時中途半端なサイズが最大サイズになってしまう
        maxScale = nil
    }

まずはguard文にあるようにviewが画面の境界に位置していないか確認します。 境界に位置していたら、処理を終了させて拡大できないようにしました。

境界に位置していなければ、その後拡大させるのですが、 その際にscaleArrayと宣言していた配列にcurrentScaleを保持できるように追加しています。(この配列については後述)

拡大した時に画面からはみ出ないか再度チェックを行なっています。 拡大してはみ出てしまった時、先ほどの配列(scaleArray)からはみ出る直前のscaleを取得します。(つまり取得したscaleがそのイベントでの最大サイズになる) これを改めてviewに適用することで拡大時に画面からはみ出てしまうことを防ぐことができます。

最後にdefaultの部分に記載されているのですが、currentScaleとmaxScaleを初期化させています。 currentScaleを初期化していないと 例えば、次に拡大しようとした時、前の拡大アクションで残っていたcurrentScaleを引き継いでしまい急に画面からはみ出るように拡大してしまったりします。

maxScaleも初期化していないと 前の拡大アクションの最大scaleを引き継ぎ、拡大しようとしているのに引き継いだscaleが適用されてしまい、中途半端なサイズまでしか拡大できなくなってしまいます。 これは例えば、拡大したけれども、viewの位置的にすぐに境界にぶつかってしまった時、大きいscaleではないのにmaxScaleとして保持されます。 これが引き継がれると、その中途半端なscaleが適用されて「拡大しているのに拡大できない!」みたいな事態になってしまいます。 (これって自分が書いたコードが悪いのかも。。。)

なので、次のアクションのために初期化しました。

ということで以上です。

https://github.com/2takaanthony85/gestureSample