UIKit에서 각 View는 고유한 좌표계를 가지고 있습니다. 이를 테면, 작은 View에도 scrollView가 추가될 수 있고, scrollView는 View보다 더 큰 영역일 수 있습니다. 이러한 경우, 각 뷰의 좌표계가 서로 다르기 때문에 특정 좌표를 처리하거나 비교하려면 좌표 변환이 필요합니다.
UIView는 좌표 변환을 위해 convert 메서드를 제공합니다. convert 메서드는 크게 두가지 종류가 있습니다.
convert(_:to:)
// 서브뷰 좌표계의 포인트를 슈펴뷰의 좌표계로 변환
let subViewPoint = CGPoint(x: 10, y: 20)
let pointInSuperView = subview.convert(subViewPoint, to: superview)
convert(_:from:)
// 서브뷰 좌표계에서 받은 포인트를 superview의 좌표계로 변환. 위 예시 코드와 같은 결과임
let subViewPoint = CGPoint(x: 10, y: 20)
let pointInSuperview = superview.convert(subViewPoint, from: subview)
각 convert 메서드는 CGPoint를 받아 변환된 CGPoint를 반환하거나, CGRect를 받아 변환된 CGRect를 반환하는 메서드가 존재합니다.
에어플레인의 화이트보드 뷰 계층은 아래와 같이 이루어져 있습니다.
(ToolBar의 경우 scrollView보다 위에 존재하지만, ViewController의 view에 추가되어 있습니다!)
viewDidLoad 시점에 configureScrollView()
를 통해 scrollView에 TapGesture를 추가했습니다. target을 ViewController로 설정하여, ScrollView에서 제스쳐가 인식되었을 경우, ViewController가 handleScrollViewTapGesture
를 실행하도록 했습니다.
private func configureScrollView() {
let scrollViewTapGestureRecognizer = UITapGestureRecognizer(
target: self,
action: #selector(handleScrollViewTapGesture))
scrollViewTapGestureRecognizer.numberOfTapsRequired = 1
scrollViewTapGestureRecognizer.isEnabled = true
scrollView.addGestureRecognizer(scrollViewTapGestureRecognizer)
}
@objc private func handleScrollViewTapGesture(gesture: UITapGestureRecognizer) {
let location = gesture.location(in: canvasView)
let touchedView = canvasView.hitTest(location, with: nil)
if let touchedView = touchedView as? WhiteboardObjectView {
viewModel.action(input: .selectObject(objectID: touchedView.objectID))
} else {
viewModel.action(input: .deselectObject)
}
}
handleScrollViewTapGesture
내부에서는, hitTest를 통해 오브젝트 뷰를 반환받으면 오브젝트 뷰에 대한 선택 작업을, 반대의 경우에는 선택 해제 작업을 진행하도록 구현했습니다.
각 오브젝트 뷰는 아래와 같이 구성되어 있습니다. 오브젝트 뷰를 중심으로 오브젝트뷰의 bounds
외부에 선택한 사람을 나타내는 ProfileIconVIew
와, 오브젝트를 조작할 수 있는 ControlView
가 있습니다.
**ProfileIconView(좌상단), ControlView(우하단)**
ControlView에는 panning을 통해 오브젝트의 크기와 각도를 수정할 수 있는 panGesture가 추가되어 있습니다. 한편, ControlView의 center는 ObjectView의 (maxX, maxY) 점에 위치합니다. 따라서 ControlView의
영역 중 1/4 만 오브젝트 View의 bounds에 위치합니다.