Potato
์•ˆ๋…•ํ•˜์„ธ์š”, ๊ฐ์žก๋‹ˆ๋‹ค?๐Ÿฅ” ^___^ ๐Ÿ˜บ github ๋ฐ”๋กœ๊ฐ€๊ธฐ ๐Ÿ‘‰๐Ÿป

Swift/UIKit

[UIKit] hitTest(_: with: ) - ํ„ฐ์น˜์ด๋ฒคํŠธ์— ๋ฐ˜์‘ํ•  view ์ฐพ๊ธฐ, first responder

๊ฐ์ž ๐Ÿฅ” 2022. 7. 12. 19:20
๋ฐ˜์‘ํ˜•


์ด์ „๊ธ€์—์„œ UIResponder์— ๋Œ€ํ•ด์„œ ๊ณต๋ถ€ํ•˜๋‹ค๊ฐ€ hitTest์— ๋Œ€ํ•œ ๊ฐœ๋…์ด ๋“ฑ์žฅํ–ˆ๋‹ค.
https://didu-story.tistory.com/286

 

[UIKit] UIResponder (feat. Responder, Responder Chain, FirstResponder)

์˜ค๋Š˜๋ถ€ํ„ฐ ๋ฏธ๋ผํด ๋ชจ๋‹ ์‹œ๊ฐ„์— 1์ผ 1์œ ์•„์ดํ‚ท ํฌ์ŠคํŒ…์„ ํ•ด๋ณด๋ ค๊ณ ํ•œ๋‹ค. ๊ตฌํ˜„์œ„์ฃผ์˜ ๊ณต๋ถ€๋ฅผ ํ•˜๋‹ค๋ณด๋‹ˆ, ๊ธฐ์ดˆ์ง€์‹์ด ๋„ˆ๋ฌด ๋ถ€์กฑํ•˜๋‹ค๊ณ  ๋Š๊ปด์กŒ๊ณ , UIKit์˜ ๊ตฌ์กฐ๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ํŒŒ์•…ํ•˜๊ณ  ์‹ถ์–ด์„œ ์‹œ์ž‘! ๐Ÿ”ต UIR

didu-story.tistory.com

โœ… UIKit์€ ๋ทฐ ๊ธฐ๋ฐ˜์˜ hit-testing์„ ์‚ฌ์šฉํ•˜์—ฌ ํ„ฐ์น˜์ด๋ฒคํŠธ๊ฐ€ ์–ด๋””์„œ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ๊ฒฐ์ •ํ•œ๋‹ค. UIView์˜ hitTest(_:with: ) ๋ฉ”์„œ๋“œ๋Š” view ๊ณ„์ธต์„ ๋Œ์•„๋‹ค๋‹ˆ๋ฉด์„œ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ ํ„ฐ์น˜ ์ง€์ ์„ ํฌํ•จํ•˜๊ณ  ์žˆ๋Š” view์˜ ๊ณ„์ธต ๊ฐ€์žฅ ์ตœํ•˜๋‹จ subView๋ฅผ ์ฐพ๊ณ , ๊ทธ๊ฒƒ์„ touch์ด๋ฒคํŠธ์˜ first responder๋กœ ์ง€์ •ํ•œ๋‹ค. ๋งŒ์•ฝ ํ„ฐ์น˜ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ ์œ„์น˜๊ฐ€ view์˜ bounds ๋ฐ–์— ์žˆ๋‹ค๋ฉด, hitTest(_:with: )์€ ํ•ด๋‹น ๋ทฐ์™€ ์„œ๋ธŒ๋ทฐ๋ฅผ ๋ชจ๋‘ ๋ฌด์‹œํ•œ๋‹ค.

์•„, ํ„ฐ์น˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด hitTest๊ฐ€ ๊ทธ ํ„ฐ์น˜์ด๋ฒคํŠธ์— ๋ฐ›์•„์„œ ๋ฐ˜์‘ํ•  ๋ทฐ๋ฅผ ์ฐพ์•„์ฃผ๋Š”๊ตฌ๋‚˜? ๋ผ๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ๋‹ค. ๋” ๊ตฌ์ฒด์ ์œผ๋กœ ํ†บ์•„๋ณด์ž. (ํ†บ์•„๋ณด๋‹ค: ์ƒ…์ƒ…์ด ํ†บ์•„ ๋‚˜๊ฐ€๋ฉด์„œ ์‚ดํ”ผ๋‹ค. ํฌํฌํฌํฌ ์ƒˆ๋กœ์šด ๋‹จ์–ด๋ฅผ ์•Œ๊ฒŒ๋˜์—ˆ๋‹ค ^__^ )


 

๐Ÿ”ต hitTest(_: with: ) ๊ฐœ๋… ์‚ดํŽด๋ณด๊ธฐ

๐Ÿ”น ๊ณต์‹๋ฌธ์„œ

์šฐ์„  ๊ณต์‹๋ฌธ์„œ๋ฅผ ์‚ดํŽด๋ณด์ž.
https://developer.apple.com/documentation/uikit/uiview/1622469-hittest

 

Apple Developer Documentation

developer.apple.com

 

UIView์˜ ์ธ์Šคํ„ด์Šค ๋ฉ”์†Œ๋“œ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜์ฒ˜๋Ÿผ ์ •์˜ํ•˜๊ณ  ์žˆ๋‹ค.

โœ… Returns the farthest descendant of the receiver in the view hierarchy (including itself) that contains a specified point.

→ ์ง€์ •๋œ ์ ์„ ํฌํ•จํ•˜๋Š” ๋ณด๊ธฐ ๊ณ„์ธต ๊ตฌ์กฐ์—์„œ receiver์˜ ๊ฐ€์žฅ ๋จผ ํ•˜์œ„ ํ•ญ๋ชฉ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
๊ทธ๋ƒฅ ํ„ฐ์น˜์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์•„๋“ค์ด๋Š” ์ธ์Šคํ„ด์Šค์ด๊ตฌ๋‚˜ ~ ๋ผ๊ณ ์ƒ๊ฐ๋งŒ ๋˜์ง€๋งŒ, ์ œ๋Œ€๋กœ๋œ ์ดํ•ด๊ฐ€ ๋˜์ง€ ์•Š๋Š”๋‹ค. ์™œ ๊ฐ€์žฅ ๋จผ ํ•˜์œ„ ํ•ญ๋ชฉ์„ ๋ฐ˜ํ™˜ํ•˜์ง€??

 

๐Ÿ”น ๊ฐœ๋… ์‚ดํŽด๋ณด๊ธฐ

app ๋‚ด๋ถ€์—์„œ ํ„ฐ์น˜ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ? ๊ทธ ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์•„์„œ ๋ฐ˜์‘ํ•  ๊ฐ์ฒด์— 'ํ„ฐ์น˜์ด๋ฒคํŠธ'๊ฐ€ ์ „๋‹ฌ๋˜์–ด์•ผํ•  ๊ฒƒ์ด๋‹ค. Hit-Testing์€ point๊ฐ€ ํ™”๋ฉด์— ๊ทธ๋ ค์ง„ ๊ฐ์ฒด (UIVIEW ๋“ฑ)์™€ ๋งŒ๋‚˜๋Š”์ง€์— ๋Œ€ํ•œ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ํ”„๋กœ์„ธ์Šค์ด๋‹ค.
์ฆ‰, ๊ฐ„๋‹จํ•˜๊ฒŒ ๋งํ•ด์„œ ํ„ฐ์น˜ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ ๋ทฐ๋ฅผ ์ฐพ๋Š” ๊ฒƒ์ธ๋ฐ, ํ„ฐ์น˜ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ ํฌ์ธํŠธ์— ์žˆ๋Š” ๋ทฐ๋“ค ์ค‘ ์ตœ์ƒ๋‹จ๋ทฐ๋ฅผ ์ฐพ๋Š” ๊ฒƒ์ด๋‹ค. ๋ณดํ†ต ๊ทธ๋ ‡๊ฒŒ ์ฐพ์€ ๋ทฐ๊ฐ€ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” First Responder๊ฐ€ ๋œ๋‹ค.

๐Ÿ”น ๋ฉ”์†Œ๋“œ

hitTest์˜ ๋ฉ”์†Œ๋“œ๋Š” CGPointํƒ€์ž…์˜ ํŒŒ๋ผ๋ฏธํ„ฐ point์™€ UIEvent ํƒ€์ž…์˜ Event๋ฅผ ๋ฐ›๋Š” ๋ฉ”์†Œ๋“œ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  UIView๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค. (์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ทฐ๋ฅผ ์ฐพ๋Š”๋‹ค๊ณ  ํ–ˆ์œผ๋‹ˆ๊นŒ ๋‹น์—ฐํžˆ UIView๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฒ ์ง€!)

  • point : receiver์˜ ๋กœ์ปฌ bounds(์ขŒํ‘œ๊ณ„)๋กœ ์ง€์ •๋œ point(์ )
  • event : ์ด ๋ฉ”์†Œ๋“œ์— ๋Œ€ํ•œ ํ˜ธ์ถœ์„ ๋‹ด๋‹น(?)ํ•˜๋Š” ์ด๋ฒคํŠธ์ด๋‹ค. ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ์ฝ”๋“œ ์™ธ๋ถ€์—์„œ ์ด ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒฝ์šฐ์— nil ์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. (์˜ต์…”๋„)
  • ๋ฆฌํ„ด๊ฐ’ UIView : ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์•„์„œ ์ฒ˜๋ฆฌํ•˜๋Š” view๊ฐ€ ์žˆ์„ ์ˆ˜๋„ ์—†์„์ˆ˜๋„ ์žˆ์œผ๋‹ˆ ์˜ต์…”๋„๊ฐ’์œผ๋กœ ๋ฆฌํ„ดํ•œ๋‹ค.

์ง€๊ธˆ๊นŒ์ง€ ์ด๋ ‡๊ฒŒ ๊ณต์‹๋ฌธ์„œ์— ๋‚˜์˜จ ๊ฐœ๋…๋“ค๋งŒ ์‚ดํŽด๋ดค๋Š”๋ฐ ์ „ํ˜€ ์ดํ•ด๊ฐ€ ๋˜์ง€ ์•Š๋Š”๋‹ค. ์˜ˆ์ œ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์‚ดํŽด๋ณด์ž (์ถœ์ฒ˜๋Š” ๋งจ ๋งˆ์ง€๋ง‰์— ์ ์–ด๋‘ )

๐Ÿ”น ๊ณต์‹๋ฌธ์„œ์˜ Discussion ํ•ด์„

This method traverses the view hierarchy by calling the point(inside:with:) method of each subview to determine which subview should receive a touch event. If point(inside:with:) returns true, then the subview’s hierarchy is similarly traversed until the frontmost view containing the specified point is found. If a view does not contain the point, its branch of the view hierarchy is ignored. You rarely need to call this method yourself, but you might override it to hide touch events from subviews.

This method ignores view objects that are hidden, that have disabled user interactions, or have an alpha level less than 0.01. This method does not take the view’s content into account when determining a hit. Thus, a view can still be returned even if the specified point is in a transparent portion of that view’s content.
Points that lie outside the receiver’s bounds are never reported as hits, even if they actually lie within one of the receiver’s subviews. This can occur if the current view’s clipsToBounds property is set to false and the affected subview extends beyond the view’s bounds.

์ด ๋ฉ”์†Œ๋“œ์˜ ๊ฐ ํ•˜์œ„ view์˜ point(inside: with: )๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ View๊ณ„์ธต์„ ํƒ์ƒ‰ํ•˜๊ณ  ์–ด๋–ค ํ•˜์œ„ View๊ฐ€ ํ„ฐ์น˜์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์•„์•ผํ•˜๋Š”์ง€ ๊ฒฐ์ •ํ•œ๋‹ค.

๐Ÿ‘‰ point(inside: with: ) ?????

UIView์˜ ์ธ์Šคํ„ด์Šค ๋ฉ”์„œ๋“œ์ด๊ณ , ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ณด๋ฉด point์™€ event๋กœ 'hitTest'์™€ ๋™์ผํ•˜๋‹ค. ๋‹ค๋ฅธ ์ ์€ ๋ฐ˜ํ™˜๊ฐ’์ธ๋ฐ, ๋ฆฌ์‹œ๋ฒ„ bounds(์ขŒํ‘œ๊ณ„)๋‚ด๋ถ€์— ์ง€์ •๋œ point๊ฐ€ ํฌํ•จ๋˜์–ด์žˆ๋Š”์ง€์— ๋Œ€ํ•œ ์—ฌ๋ถ€๋ฅผ boolean๊ฐ’์œผ๋กœ ๋ฆฌํ„ดํ•ด์ฃผ๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค.

๋‹ค์‹œ hitTest๋กœ

point(inside: with: )๊ฐ€ true๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด, ํ•˜์œ„ view๊ณ„์ธต๊ตฌ์กฐ๋Š” ์ง€์ •๋œ ํฌ์ธํŠธ๋ฅผ ํฌํ•จํ•˜๋Š” ๊ฐ€์žฅ ์•ž์— ์žˆ๋Š” View (frontest view)๊ฐ€ ๋ฐœ๊ฒฌ๋  ๋•Œ๊นŒ์ง€ ํ•˜์œ„ View๊ณ„์ธต๊ตฌ์กฐ๊ฐ€ ๋น„์Šทํ•˜๊ฒŒ ๊ฐ€๋กœ์ง€๋ฅธ๋‹ค. (similarly traversed) View์— ์ ์ด ์—†์œผ๋ฉด, View๊ณ„์ธต๊ตฌ์กฐ์˜ ํ•ด๋‹น ๋ถ„๊ธฐ๊ฐ€ ๋ฌด์‹œ๋œ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” ์ง์ ‘ ํ˜ธ์ถœํ•  ํ•„์š”๊ฐ€ ์—†์ง€๋งŒ, ํ•˜์œ„ View์—์„œ ํ„ฐ์น˜์ด๋ฒคํŠธ๋ฅผ ์ˆจ๊ธฐ๋ ค๋ฉด ์ด ๋ฉ”์„œ๋“œ๋ฅผ ์žฌ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.
์ด ๋ฉ”์„œ๋“œ๋Š” hidden์ด๊ฑฐ๋‚˜, ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ์„ ๋น„ํ™œ์„ฑํ™” ํ•˜๊ฑฐ๋‚˜, ์•ŒํŒŒ๊ฐ’์ด 0.01 ๋ฏธ๋งŒ์ธ view๊ฐ์ฒด๋ฅผ ๋ฌด์‹œํ•œ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” 'hit'์„ ๊ฒฐ์ •ํ•  ๋•Œ view์˜ ์ฝ˜ํ…์ธ ๋ฅผ ๊ณ ๋ คํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋”ฐ๋ผ์„œ ์ง€์ •๋œ point๊ฐ€ ํ•ด๋‹น view์ปจํ…์ธ ์˜ ํˆฌ๋ช…ํ•œ ๋ถ€๋ถ„์— ์žˆ์–ด๋„ view๊ฐ€ ๊ณ„์† ๋ฐ˜ํ™˜ ๋  ์ˆ˜ ์žˆ๋‹ค.
Receiver bounds ์™ธ๋ถ€์— ์žˆ๋Š” point๋Š” ์‹ค์ œ๋กœ receiverํ•˜์œ„ view ์ค‘ ํ•˜๋‚˜์— ์†ํ•˜๋”๋ผ๋„, hit์ด๋ผ๊ณ  ๋ณด์ง€ ์•Š๋Š”๋‹ค. ์ด ๋ฌธ์ œ๋Š” ํ˜„์žฌ view์˜ clipsToBounds ํ”„๋กœํผํ‹ฐ๊ฐ€ false๋กœ ์„ค์ •๋˜๊ณ , ์˜ํ–ฅใ…‡๋ฅด ๋ฐ›๋Š” ํ•˜์œ„ View๊ฐ€ ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋Š” ๊ฒฝ์šฐ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ญ... ๋Œ€์ถฉ ๋‚ด๊ฐ€ ์ƒ๊ฐํ•˜๊ธฐ๋กœ๋Š” ํ„ฐ์น˜์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ ๋”ฑ ๊ทธ๋ถ€๋ถ„, ๊ทธ ํฌ์ธํŠธ ๊ทธ์ง€์ ์—์„œ์˜ ๊ฐ€์žฅ ์ตœ์ƒ๋‹จ๋ทฐ๋ฅผ ์ฐพ์•„์ฃผ๋Š” ๊ฒƒ์ด hitTest์ธ ๊ฒƒ ๊ฐ™๋‹ค..๋ผ๊ณ  ๋Œ€์ถฉ ์ดํ•ด๊ฐ€...๋˜๋Š”๋ฐ... ๋„๋Œ€์ฒด ์–ด๋–ค ์ƒํ™ฉ์„ ์˜ˆ์‹œ๋ฅผ ๋“ค์–ด์„œ ์ดํ•ดํ•ด์•ผํ• ์ง€ ๊ฐ์ด ์˜ค์ง€ ์•Š์•˜๋‹ค.

๐Ÿ”ต Hit Test๋Š” ์–ธ์ œ ์‹คํ–‰๋ ๊นŒ

์ถœ์ฒ˜: http://smnh.me/hit-testing-in-ios/

์œ„ ๊ทธ๋ฆผ์—์„œ ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์— Hit-testing์ด ์™„๋ฃŒ๋˜๊ณ , ์ตœ์ƒ๋‹จ๋ทฐ๊ฐ€ ๊ฒฐ์ •๋˜์—ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ์ฐพ์œผ๋ฉด ๊ทธ ๋‹ค์Œ์—” ๋ญ˜ํ• ๊นŒ?
touch event ์‹œํ€€์Šค์˜ ๋ชจ๋“  ๋‹จ๊ณ„ (began, moved, ended, canceled)์— ๋Œ€ํ•œ UITouch ๊ฐ์ฒด์™€ ์—ฐ๋™๋˜๊ณ , hit-test view๊ฐ€ ์ผ๋ จ์˜ touch event๋ฅผ ๋ฐ›๊ธฐ ์‹œ์ž‘ํ•œ๋‹ค. (๊ทธ๋‹ˆ๊นŒ ๊ทธ ๋ทฐ์™€ ํ„ฐ์น˜๊ฐ€ ์—ฐ๊ฒฐ๋œ๋‹ค๋Š” ๋œป!!!)
์ฐธ๊ณ ํ•ด์•ผํ•  ๊ฒƒ์€, ์†๊ฐ€๋ฝ์ด hit-test view์˜ bound ๋ฐ–์œผ๋กœ ๋‹ค๋ฅธ view๋กœ ์ด๋™ํ•˜๋”๋ผ๋„, hit-test view๋Š” touch event ์‹œํ€€์Šค๊ฐ€ ใ„ฒํŠธ๋‚ ๋•Œ๊นŒ์ง€ (ended๊ฐ€ ์˜ฌ๋•Œ๊นŒ์ง€!!) ๋ชจ๋“  ํ„ฐ์น˜๋ฅผ ๋ฐ›๋Š”๋‹ค.
(์†๊ฐ€๋ฝ์œผ๋กœ A์œ„์น˜๋ฅผ ํ„ฐ์น˜ํ•˜๊ณ , ์†๊ฐ€๋ฝ์„ ๋–ผ์ง€์•Š๊ณ  ๋™๊ทธ๋ผ๋ฏธ๋ฅผ ๊ทธ๋ฆฌ๊ณ , A๊ฐ€ ํ„ฐ์น˜์ด๋ฒคํŠธ๋ฅผ ๋ฐ›๋Š” UIView๊ฐ์ฒด์ผ๋•Œ A์˜ ์†๊ฐ€๋ฝ ์œ„์น˜๋Š” ๋ฒ—์–ด๋‚ฌ์ง€๋งŒ, A๋Š” ๊ณ„์† ๊ทธ ํ„ฐ์น˜์ด๋ฒคํŠธ๋ฅผ ๋ฐ›๋Š”๋‹ค๋Š” ์˜๋ฏธ!!! ์šฐ๋ฆฌ๊ฐ€ ํ‰์†Œ์— ์•ฑ์‚ฌ์šฉํ•  ๋•Œ ํ„ฐ์น˜ํ•˜๊ณ  ์†์•ˆ๋–ผ๋„ ๊ทธ๊ฑฐ ํ„ฐ์น˜๋˜์ž–์•„!! ์œ„ ๊ทธ๋ฆผ์€ ์ด ํ–‰์œ„๋ฅผ ๋ฌ˜์‚ฌํ•œ ๋‹ค์ด์–ด๊ทธ๋žจ์ด๋‹ค)

๐Ÿ”ต ์ด๋ฒคํŠธ์— ๋ฐ˜์‘ํ•  UIView ๊ฐ์ฒด๋Š” ์–ด๋–ป๊ฒŒ ์ฐพ์„๊นŒ

hit-testing์€ view๋ฅผ ํƒ์ƒ‰ํ•  ๋•Œ, reverse pre-order DFS ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•œ๋‹ค. (๊ฐ€์žฅ ๊นŠ์€ ๋ทฐ๋ฅผ ์ฐพ๊ณ , ํƒ์ƒ‰์„ ์ค‘๋‹จํ•ด์•ผํ•˜๊ธฐ๋•Œ๋ฌธ์— ๊นŠ์ด์šฐ์„ ํƒ์ƒ‰์„ ์ง„ํ–‰ํ•œ๋‹ค!) → โœ… ๊ฐœ๋…๋ณด๋Ÿฌ๊ฐ€๊ธฐ
๊ฐ€์žฅ ๋จผ์ € root node๋ฅผ ๋ฐฉ๋ฌธํ•œ ๋‹ค์Œ, ํ•˜์œ„ ์ธ๋ฑ์Šค๋กœ ํ•˜์œ„ ํŠธ๋ฆฌ๋ฅผ ์ˆœํšŒํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค. ์ด๋Ÿฌํ•œ ์ข…๋ฅ˜์˜ ์ˆœํšŒ๋ฅผ ํ†ตํ•ด์„œ ์ˆœํšŒ ๋ฐ˜๋ณตํšŸ์ˆ˜๋ฅผ ์ค„์ด๊ณ , ํ„ฐ์น˜ ํฌ์ธํŠธ๋ฅผ ํฌํ•จํ•˜๋Š” ์ฒซ๋ฒˆ์งธ ๊ฐ€์žฅ ๊นŠ์€ ํ•˜์œ„ view๊ฐ€ ๋ฐœ๊ฒฌ๋˜๋ฉด ํƒ์ƒ‰์„ ์ค‘์ง€ํ•˜๋Š” ๋ฐฉ์‹์„ ์ฑ„ํƒํ•œ๋‹ค.
์ด๊ฒƒ์€ usbView๊ฐ€ ํ•ญ์ƒ superView์•ž์— ๋ Œ๋”๋ง๋˜๊ณ , sibling view(ํ˜•์ œ๋ทฐ)๊ฐ€ ํ•ญ์ƒ ํ•˜์œ„ ์ธ๋ฑ์Šค๊ฐ€ ์žˆ๋Š” ํ˜•์ œ๋ทฐ ์•ž subView ๋ฐฐ์—ด์— ๋ Œ๋”๋ง๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋”ฐ๋ผ์„œ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๊ฒน์น˜๋Š” view์— ํŠน์ • ํฌ์ธํŠธ๊ฐ€ ํฌํ•จ๋œ ๊ฒฝ์šฐ, ๋งจ ์˜ค๋ฅธ์ชฝ ํ•˜์œ„ ํŠธ๋ฆฌ์—์„œ ๊ฐ€์žฅ ๊นŠ์€ view๊ฐ€ frontmost view(hit test๊ฐ€ ์ฐพ๋Š” ๊ฐ€์žฅ ๋งจ์•ž ๋ทฐ)๊ฐ€ ๋œ๋‹ค.

๐Ÿ”ต hit-test์˜ process


์™ผ์ชฝ ๊ทธ๋ฆผ์„ ๋ณด๋ฉด, mainView ๊ฐ€์žฅ ๋’ค์— ํ•˜์–€์ƒ‰ View๊ฐ€ ์žˆ๊ณ , ๊ทธ ์œ„์— 3๊ฐœ์˜ View๊ฐ€ ์กด์žฌํ•œ๋‹ค. MainView์ž…์žฅ์—์„œ๋Š” ์„ธ๊ฐœ์˜ view ๋ชจ๋‘ ์ž์‹๋ทฐ์ด๋‹ค.
์œ„ 3๊ฐœ์˜ View์—์„œ ๋นจ๊ฐ„์ƒ‰ ViewA์™€ ์ดˆ๋ก์ƒ‰ ViewB๊ฐ€ ๊ฒน์ณ์ ธ ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ์ •ํ™•ํžˆ ๋งํ•˜๋ฉด, A์™€ B๊ฐ๊ฐ์˜ SubView์ธ ViewA.2์™€ ViewB.1์ด ๊ฒน์ณ์žˆ๋‹ค. ๊ทธ๋ฆผ์—์„œ ๋ณผ์ˆ˜ ์žˆ๋“ฏ, ViewB.1์ด ์ธ๋ฑ์Šค๊ฐ€ ๋” ๋†’๊ธฐ ๋•Œ๋ฌธ์— ViewA.2์œ„์— ์œ„์น˜ํ•˜๊ฒŒ ๋œ๋‹ค. ๊ทธ๋Ÿผ ์ € ๊ฒน์น˜๋Š” ๋ถ€๋ถ„์„ ํ„ฐ์น˜ํ•˜๊ฒŒ ๋˜๋ฉด ๊ฐ€์žฅ ์ „๋ฉด์— ์žˆ๋Š” frontmost view์ธ ViewB.1์ด ๋ฐ˜ํ™˜๋˜๊ฒ ์ง€?

ํ„ฐ์น˜ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด, ์˜ค๋ฅธ์ชฝ ํ™”์‚ดํ‘œ ๋ฐฉํ–ฅ๋Œ€๋กœ ์ด๋ฒคํŠธ๊ฐ€ ์ „๋‹ฌ๋œ๋‹ค. UIWindow๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์ •๋ง๋กœ ๊ฐ€์žฅ ๋จผ ์ž์†, ํ•˜์ง€๋งŒ ์œ ์ €์ž…์žฅ์—์„œ๋Š” frontmostํ•œ ViewB.1์ด hit-test View๊ฐ€ ๋˜์—ˆ๋‹ค.
๊ทธ๋Ÿผ ViewC๋Š” ์™œ๋“ค๋ ธ๋‹ค๊ฐ€ ์˜จ๊ฑธ๊นŒ?
์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๊ฐ€์žฅ ๋’ค์—์žˆ๋Š” ์ธ๋ฑ์Šค๋ถ€ํ„ฐ ๋“ค๋ฆด ๊ฒƒ์ธ๋ฐ ๊ทธ๋ž˜์„œ ViewC๋„ ๋“ค๋ฆฐ๊ฒƒ์ด๊ณ , ViewC๋Š” ํ•ด๋‹น point๋ฅผ bounds(์ขŒํ‘œ๊ณ„)์— ๊ฐ–๊ณ ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— point(inside: with: )์ด false๋ฅผ ๋ฆฌํ„ดํ–ˆ์„๊ฑฐ๊ณ , ๊ทธ๋ž˜์„œ ๋ฐ”๋กœ ViewB๋กœ ๋„˜์–ด๊ฐ“์„ ๊ฒƒ์ด๋‹ค.

๐Ÿ”น hit-test์˜ ๊ตฌํ˜„๋ถ€ ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด์ž.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{    
	if (self.hidden || !self.userInteractionEnabled || self.alpha < 0.01 || ![self pointInside:point withEvent:event] || ![self _isAnimatedUserInteractionEnabled]) {
    	return nil;    
    } else {
    	for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
        	UIView *hitView = [subview hitTest:[subview convertPoint:point fromView:self] withEvent:event];
            if (hitView) {
            return hitView;            
            }
        }
    	return self;
    }
}

์œ„ ๊ณต์‹๋ฌธ์„œ discussion์—์„œ ๋ดค๋“ฏ์ด, hidden์ด๊ฑฐ๋‚˜, interact๊ฐ€ false์ด๊ฑฐ๋‚˜, alpha๊ฐ€ 0.01 ๋ฏธ๋งŒ์ธ ๊ฒƒ์€ nil์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

์ด ๊ทธ๋ฆผ์„ ๋‹ค์‹œ ๋ณด๋ฉด, ์œ„์—์„œ ๋งํ–ˆ๋“ฏ์ด ์ € ๋ถ€๋ถ„์„ ํ„ฐ์น˜ํ•˜๋ฉด ViewB.1์ด hit-test view ๊ฐ€ ๋œ๋‹ค. ViewA.2๊ฐ€ ์ € touch area๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์–ด๋„ ๋ง์ด๋‹ค.
๊ทธ๋Ÿผ ํ˜น์‹œ ๋Œ€๋ถ€๋ถ„ ๊ทธ๋Ÿด์ผ์€ ์—†๊ฒ ์ง€๋งŒ, ์ €๋ถ€๋ถ„์„ ํ„ฐ์น˜ํ–ˆ์„๋•Œ ViewA.2๊ฐ€ ํ„ฐ์น˜๋œ๊ฑธ๋กœ ํ•˜๊ณ ์‹ถ๋‹ค๋ฉด? (์ฆ‰, ViewA.2๊ฐ€ hit-Test view๊ฐ€ ๋˜๊ฒŒ๋” ํ•˜๋ ค๋ฉด?)
์ด๋Ÿด๋•Œ hitTest๋ฅผ override ํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

class ViewA: UIView {
	override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {        
		let hitView: UIView? = super.hitTest(point, with: event)        
		if (self == hitView) { return nil }        
		return hitView    
	}
}

hitTest๋ฐ›๋Š”์• ๊ฐ€ ViewB.2์˜€๋Š”๋ฐ, ํ•ด๋‹น ๋ทฐ์— ์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ๋ฉด, nil์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๊ทธ ํ•˜์œ„ view๋กœ ํ„ฐ์น˜์ด๋ฒคํŠธ๊ฐ€ ๋„˜์–ด๊ฐ„๋‹ค.
์ฆ‰, ์ด ์ฝ”๋“œ๋ฅผ ViewB.2์˜ ํด๋ž˜์Šค๋ฅผ ์œ„์—์„œ ์ž‘์„ฑํ•œ ์†Œ์Šค์ฝ”๋“œ ์ปค์Šคํ…€ UIView๋กœ ์ง€์ •ํ•ด์ฃผ๊ณ  ์‹คํ–‰ํ•˜๋ฉด, ํ„ฐ์น˜ ์ด๋ฒคํŠธ๊ฐ€ ViewA๋กœ ๋„˜์–ด๊ฐ€์„œ VIewA.2๋กœ ํ„ฐ์น˜์ด๋ฒคํŠธ๊ฐ€ ์ „๋‹ฌ๋œ๋‹ค.
ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•œ ๋” ์ž์„ธํ•œ ์˜ˆ์‹œ, ์ง๊ด€์ ์ธ ์˜ˆ์‹œ๋ฅผ ๋ณด๊ณ ์‹ถ๋‹ค๋ฉด ํ•ด๋‹น ๋ธ”๋กœ๊ทธ๋ฅผ ๋ฐฉ๋ฌธํ•ด๋ณด์ž. → โœ… reference ๋ฐ”๋กœ๊ฐ€๊ธฐ

์ด๋ ‡๊ฒŒ hittest์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ดค๋‹ค. ์ด์ œ ํ„ฐ์น˜์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋Š” ๋ทฐ๊ฐ€ ์–ด๋–ป๊ฒŒ ์ „๋‹ฌ๋˜๋Š”์ง€ ์•Œ์•˜๊ณ , first responder๊ฐ€ ์–ด๋–ป๊ฒŒ ์„ ์ •๋˜๊ฒŸ๊ตฌ๋‚˜! ๋Œ€๋žต์ ์œผ๋กœ ๊ฐ์ด ์˜ค๋Š”๊ฒƒ ๊ฐ™๋‹ค.

๐Ÿ“– references

https://zeddios.tistory.com/536

 

iOS ) hitTest

์•ˆ๋…•ํ•˜์„ธ์š” :) Zedd์ž…๋‹ˆ๋‹ค. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ์†Œ์Šค ๋ณด๋ฉด ๊ฐ€์•„๋” hitTest๊ฐ€ ์žˆ์—ˆ๋Š”๋ฐ, ๋ญ์ง€?ํ•˜๊ณ  ๊ทธ๋ƒฅ ์ง€๋‚˜์ณค๋˜ ๊ธฐ์–ต์ด... ์˜ค๋Š˜ ์ œ๋Œ€๋กœ ๊ณต๋ถ€ํ•ด๋ณผ๋ ค๊ณ  ํ•ด์šฉ ์ด๋ฅผ ์œ„ํ•ด์„œ..UIResponder๋ฅผ ์ผ์—ˆ์ฃ .. hi

zeddios.tistory.com

https://lena-chamna.netlify.app/post/hit_testing_in_ios/

 

View ๊ณ„์ธต ํƒ์ƒ‰ ์•Œ๊ณ ๋ฆฌ์ฆ˜๊ณผ Hit-Testing in iOSโ€จ

explain how to find UIView object to handle events with reverse pre-order depth-first traverse algorithm and Hit-testing

lena-chamna.netlify.app

https://smnh.me/hit-testing-in-ios

 

Hit-Testing in iOS

How hit-testing works in iOS

smnh.me

 

๋ฐ˜์‘ํ˜•