はじめに
ごくまれに UIView
の convert 系のメソッドを使って座標を変換したいときがありますがいつもどっちに何指定するのかな?と忘れるのでまとめておきます。
View 準備
下記のような構成で座標変換をしてみます。
ScrollView を ViewController の View と同じサイズで配置してその中に contentView
(緑色)を幅は ScrollView と同じ高さは 1200 pt で配置しています(StackView は左端のメモリ用です)。contentView
の中に縦横 120 pt の targetView
(白色)を contentView
の Top 900pt、横は中央寄せで配置して、その targetView
(白色)の中心にボタンを配置しています。
座標変換
座標変換には UIView
に下記のメソッドが用意されており、これを使って変換します。
どちらのメソッドも対象の View は同一 Window にある必要があります。引数の view
が nil
の場合は Window の座標系に変換されます。
convert(_: to:)
point
はレシーバーの座標系を指定し、view
には変換したい座標系の View を指定します。
func convert(_ point: CGPoint, to view: UIView?) -> CGPoint
// ex.
aView.convert(CGPoint.zero, to: bView)
例だと aView
の左上の座標を bView
の座標系に変換しています。
convert(_: from:)
point
は view
の座標系を指定し、レシーバーの座標系に変換します。
func convert(_ point: CGPoint, from view: UIView?) -> CGPoint
// ex.
aView.convert(CGPoint.zero, from: bView)
例だと bView
の左上の座標を aView
の座標系に変換しています。
実践
ボタン押下時に targetView
の座標系から scrollView
と ViewController の view
の座標系に変換してみます!
@IBAction private func convertPoints(_ sender: Any) {
// targetViewの左上の座標をscrollViewの座標系に変換
let p1 = targetView.convert(CGPoint.zero, to: scrollView)
print("p1: \(p1)") // p1: (127.5, 900.0)
// targetViewの左上の座標をviewの座標系に変換
let p2 = targetView.convert(CGPoint.zero, to: view)
print("p2: \(p2)") // p2: (127.5, 367.0) * scrollViewのoffsetで変わる
// targetViewの左上の座標をscrollViewの座標系に変換
let p3 = scrollView.convert(CGPoint.zero, from: targetView)
print("p3: \(p3)") // p3: (127.5, 900.0)
// targetViewの左上の座標をviewの座標系に変換
let p4 = view.convert(CGPoint.zero, from: targetView)
print("p4: \(p4)") // p4: (127.5, 367.0) * scrollViewのoffsetで変わる
}
上記を見てもらうと分かる通り、p1 と p3、p2 と p4 は同じ結果になります。
これを使ってボタン押下時に targetView
が画面外にある場合にスクロールのオフセットを調節してみます。
@IBAction private func convertPoints(_ sender: Any) {
// targetViewの左下の座標をviewの座標系に変換
let p1 = targetView.convert(CGPoint(x: 0, y: targetView.frame.height), to: view)
// targetViewの左下がviewの範囲内か確認
if !view.frame.contains(p1) {
// targetViewが画面内におさまるようスクロール
let p2 = targetView.convert(CGPoint(x: 0, y: targetView.frame.height), to: scrollView)
scrollView.setContentOffset(.init(x: 0, y: p2.y - scrollView.frame.height), animated: true)
}
}
こんな感じです
おわりに
to と from の意味を考えたらわかるんですが point
はどの座標系のやついれればいいんだっけ?とよく迷います。。。
もうこれで迷わない!!(たぶん)
ちなみに Rect の変換メソッドもあります。
SwiftUI になるとこういうの使うこともなくなるのかな