CALayer の anchorPoint を変更しつつ表示位置は維持する
わかりにくい表題になってしまっていますが、CALayer の基準点である anchorPoint をずらすと表示位置もずれてしまうのをなんとかするための方法です。回転軸をずらしたいけど表示位置は維持したい、とかそういった場面に向いています。anchorPoint あるあるなのでわかる方にはわかるかと。
Stackoverflow 先生で以下の素晴らしいコードを見つけました。
func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView) {
var newPoint = CGPointMake(view.bounds.size.width * anchorPoint.x, view.bounds.size.height * anchorPoint.y)
var oldPoint = CGPointMake(view.bounds.size.width * view.layer.anchorPoint.x, view.bounds.size.height * view.layer.anchorPoint.y)
newPoint = CGPointApplyAffineTransform(newPoint, view.transform)
oldPoint = CGPointApplyAffineTransform(oldPoint, view.transform)
var position = view.layer.position
position.x -= oldPoint.x
position.x += newPoint.x
position.y -= oldPoint.y
position.y += newPoint.y
view.layer.position = position
view.layer.anchorPoint = anchorPoint
}
このように使えます。
setAnchorPoint(CGPointMake(1.0, 1.0), forView: aView)
aView
の anchorPoint を (1.0, 1.0)
に移動させますが、例えば回転アニメーションを適用すると表示位置はそのままで(※)右下を中心に動くようになります。求めていたのはコレでした。
※実際には anchorPoint の移動に合わせて相応の量を position で移動させて相殺しています。
追記:extension にした
extension CALayer {
func setAnchorPoint(newAnchorPoint: CGPoint, forView view: UIView) {
var newPoint = CGPointMake(self.bounds.size.width * newAnchorPoint.x, self.bounds.size.height * newAnchorPoint.y)
var oldPoint = CGPointMake(self.bounds.size.width * self.anchorPoint.x, self.bounds.size.height * self.anchorPoint.y)
newPoint = CGPointApplyAffineTransform(newPoint, view.transform)
oldPoint = CGPointApplyAffineTransform(oldPoint, view.transform)
var position = self.position
position.x -= oldPoint.x
position.x += newPoint.x
position.y -= oldPoint.y
position.y += newPoint.y
position.x = newPoint.x
position.y = newPoint.y
self.anchorPoint = newAnchorPoint
self.position = position
}
}
追記:position がリセットされる謎現象
とあるビューのサブビューにこのアンカーポイントずらし技を適用したまでは良かったのですが、同一階層の別ビューで何か Auto Layout が実行されるとこっちのレイヤーの position がなぜかリセットされるという、説明するのも一苦労な謎現象に悩まされました。
結果的にはコードを以下のように改変してから、『とあるビュー』で layoutSubviews()
をオーバーライド、そこで『サブビュー』に対してアンカーポイントを再設定する、という荒技でなんとか解決しました。
extension CALayer {
func setAnchorPoint(newAnchorPoint: CGPoint, forView view: UIView) {
var newPoint = CGPointMake(self.bounds.size.width * newAnchorPoint.x, self.bounds.size.height * newAnchorPoint.y)
newPoint = CGPointApplyAffineTransform(newPoint, view.transform)
var position = self.position
position.x = newPoint.x
position.y = newPoint.y
self.anchorPoint = newAnchorPoint
self.position = position
}
}
override func layoutSubviews() {
super.layoutSubviews()
// ここでアンカーポイントを再設定しないとたまに position がずれてしまう
subView.layer.setAnchorPoint(anchorPoint, forView: subView)
}
// レイアウトが変わりそうな場面でこれを実行
layoutIfNeeded()