🍁

Swift: NSViewのconvertによる座標変換

2021/09/22に公開

viewが入れ子になっている時にconvertを使ってうまい具合に座標変換をしたいと思うのに、convertの挙動がいまいちわからなかったので検証してみました。

ViewControllerのルートViewの中にViewが二つ入っている状態。

ここで、view.frameview1.frameview2.frameをそれぞれ確認するとこんな感じ。

Swift.print("view", view.frame)
Swift.print("view1", view1.frame) // viewから見たview1の座標
Swift.print("view2", view2.frame) // view1から見たview2の座標

frameは親ビューから見た座標を返す。
ではviewから見たview2の座標はどのようにして取得できるのか?

検証してみる

全パターン試してみる。

Swift.print( view.convert(view2.frame, to: nil) )      // view2.frameと変わらない
Swift.print( view.convert(view2.frame, from: nil) )    // view2.frameと変わらない
Swift.print( view.convert(view2.frame, to: view) )     // view2.frameと変わらない
Swift.print( view.convert(view2.frame, from: view) )   // view2.frameと変わらない
Swift.print( view.convert(view2.frame, to: view1) )    // ❌ わけわからん座標1
Swift.print( view.convert(view2.frame, from: view1) )  // ⭕️
Swift.print( view.convert(view2.frame, to: view2) )    // ❌ わけわからん座標2
Swift.print( view.convert(view2.frame, from: view2) )  // ❌ わけわからん座標3

Swift.print( view1.convert(view2.frame, to: nil) )     // ⭕️
Swift.print( view1.convert(view2.frame, from: nil) )   // ❌ わけわからん座標1
Swift.print( view1.convert(view2.frame, to: view) )    // ⭕️
Swift.print( view1.convert(view2.frame, from: view) )  // ❌ わけわからん座標1
Swift.print( view1.convert(view2.frame, to: view1) )   // view2.frameと変わらない
Swift.print( view1.convert(view2.frame, from: view1) ) // view2.frameと変わらない
Swift.print( view1.convert(view2.frame, to: view2) )   // (0.0, 0.0) になった
Swift.print( view1.convert(view2.frame, from: view2) ) // ❌ わけわからん座標4

Swift.print( view2.convert(view2.frame, to: nil) )     // ❌ わけわからん座標3
Swift.print( view2.convert(view2.frame, from: nil) )   // ❌ わけわからん座標2
Swift.print( view2.convert(view2.frame, to: view) )    // ❌ わけわからん座標3
Swift.print( view2.convert(view2.frame, from: view) )  // ❌ わけわからん座標2
Swift.print( view2.convert(view2.frame, to: view1) )   // ❌ わけわからん座標4
Swift.print( view2.convert(view2.frame, from: view1) ) // (0.0, 0.0) になった
Swift.print( view2.convert(view2.frame, to: view2) )   // view2.frameと変わらない
Swift.print( view2.convert(view2.frame, from: view2) ) // view2.frameと変わらない

何パターンかに分かれたので、同じものでまとめてみてみる。

成功パターン(viewから見たview2の座標)
view.convert(view2.frame, from: view1)
view1.convert(view2.frame, to: nil)
view1.convert(view2.frame, to: view)
失敗パターン1(view2.frameと変わらない)
view.convert(view2.frame, to: nil)
view.convert(view2.frame, from: nil)
view.convert(view2.frame, to: view)
view.convert(view2.frame, from: view)
view1.convert(view2.frame, to: view1)
view1.convert(view2.frame, from: view1)
view2.convert(view2.frame, to: view2)
view2.convert(view2.frame, from: view2)
失敗パターン2(原点ゼロになった)
view1.convert(view2.frame, to: view2)
view2.convert(view2.frame, from: view1)
失敗パターン3
view.convert(view2.frame, to: view1)
view1.convert(view2.frame, from: nil)
view1.convert(view2.frame, from: view)
失敗パターン4
view.convert(view2.frame, to: view2)
view2.convert(view2.frame, from: nil)
view2.convert(view2.frame, from: view)
失敗パターン5
view.convert(view2.frame, from: view2)
view2.convert(view2.frame, to: nil)
view2.convert(view2.frame, to: view)
失敗パターン6
view1.convert(view2.frame, from: view2)
view2.convert(view2.frame, to: view1)

検証結果からわかること

  • 子ViewA.convert(子ViewB.frame, to: nil)子ViewB.convert(子ViewB.frame, to: 親View)。つまり、convert(_:to:)では親Viewとnilは同じ扱い。
  • 親View.convert(子ViewB.frame, from: 子ViewA)子ViewA.convert(子ViewB.frame, to: 親View)。つまり、fromtoを入れ替えた時、対応するViewも入れ替えれば同値。

これ、本当かな??

さらに入れ子にした場合で検証する

view2の中にさらにview3を入れて検証しました。

// viewからみたview3の座標
Swift.print( view.convert(view3.frame, from: view2) )
Swift.print( view2.convert(view3.frame, to: nil) )
Swift.print( view2.convert(view3.frame, to: view) )

// view1から見たview3の座標
Swift.print( view1.convert(view3.frame, from: view2) )
Swift.print( view2.convert(view3.frame, to: view1) )

という感じになった。
上で述べた「convert(_:to:)では親Viewとnilは同じ扱い。」というのは捉え方によっては間違い。あるViewにとって直属の親のViewを親Viewと捉えた場合は法則に沿わない。あくまでルートViewの時の動作。実際に挙動を確認してみても、欲しい座標への変換を自在に行うのは難しそうに感じる。実験してみて確信を持たないと動きが読みづらい。

Discussion