SwiftでUIView
が画面領域(UIWindow
)内にあるかどうかを判定する方法です。
色々と状況を考慮するとチェックすべきポイントがいくつかあります。
- 対象の
UIView
自身のisHidden
プロパティとalpha
プロパティ - 対象の
UIView
から親にあたる階層全てのsuperview
のisHidden
プロパティとalpha
プロパティ - 対象の
UIView
と対象のUIView
の親にあたるUIWindow
の交差領域とその面積比
更に正確に考慮を入れると対象の
UIView
の上に覆いかぶさるViewも考える必要があるのですが、それについては出来ていません。
1と2のisHidden
とalpha
を親も含めて再帰的にチェックするのは下のような関数を考えました。
func isVisibleAllViewHierarchy(of view: UIView, alpha: CGFloat) -> Bool {
var view: UIView? = view
while view != nil, let v = view {
let isHidden = v.isHidden
let isTransparent = v.alpha < alpha
if isHidden || isTransparent {
return false;
}
view = v.superview
}
return true
}
引数に対象のUIView
と許容する透過率(alpha
)を渡しtrue
が返れば可視状態にあるという判定になります。
次に対象のUIView
とUIWindow
を渡し、UIWindow
の領域内に対象のUIView
が入っているかどうかを判定する関数は以下のようになりました。
func isIntersectRect(target: UIView, parent: UIView, ratio: CGFloat) -> Bool {
// convert(_:to:)でtoで渡したUIViewからの相対座標を取得
let coordinates = target.convert(target.bounds, to: parent)
// intersection(_:)で交差領域を取得
let intersection = coordinates.intersection(parent.frame)
// 交差領域の面積を計算
let intersectionArea = floor(intersection.width * intersection.height)
guard intersectionArea > 0 else {
return false
}
// 対象UIViewの面積を計算
let targetArea = floor(target.bounds.width * target.bounds.height)
guard targetArea > 0 else {
return false
}
// 引数の比率を計算して、対象UIViewが交差領域内かどうかを判定
return intersectionArea >= (targetArea * ratio)
}
ポイントは上記のコメントに書いたように、CGRectのそれぞれの交差領域を割り出して判定するというところです。
ある機能を持ったViewコンポーネントが画面内に入ったら何かのアクションを行うといったデザインは結構あるんじゃないかなぁと思います。
ということで、それらを考慮したライブラリを作成しました。
https://github.com/darquro/Visibility
swiftのTargeted Extensionsを用いて、UIViewのプロパティとしてアクセスすることができます。
// start handling visibility
targetView.visibility
.changed { state in
switch state {
case .visible:
// visible process here
case .invisible:
// invisible process here
}
}
// stop handling visibility
targetView.visibility.invalidate()
CocoaPods、Carthageでのインストールが可能ですので、是非試してみて頂けたら幸いです。
(ついでにスターも頂けると喜びます)