[swift] 키보드 감지 및 감추기

개요

iOS에서 화면에 올라와있는 키보드를 감지하여 제거할 방법을 찾고 있었습니다. 검색해보면 수 많은 결과는 노티피케이션으로부터 통지를 받을 수 있다는 것입니다.
하지만 두 가지 문제가 있는데 반드시 노티피케이션을 등록해줘야한다는 점과 등록한 후에는 필요가 없는 시점에도 계속 통지가 온다는 것입니다.
그보다는 현재 시점에 키보드가 있는지 확인하고 그 키보드를 제거하는 방향으로 함수를 짜기로 맘먹었습니다.

키보드가 올라온 상태인지 감지

한 app의 windows는 여러 개의 레이어로 구성될 수 있습니다. 일반적으로 개발자가 다루는 영역은 windows[0] 레이어입니다. 다이얼로그나 키보드를 불러오게 되면 새로운 window 레이어를 생성하여 활용하게 되는데 그중에서도 키보드는 매우 이상하게 작동합니다.
Alert등의 다이얼로그 계열은 window를 하나 만들어낸 후 계속 재활용하는 식으로 작동하는데, 키보드는 신기하게도 열릴 때마다 window를 만들어냅니다.
따라서 현재 키보드가 올라왔는지를 감지하려면 0번 window를 제외한 모든 window를 검사하여 그 안에 활성화된 키보드가 있는지 알아내야 합니다.
해당 window에 키보드용 UIView를 감지하기 위해서는 다시 첫 번째 subview의 subviews를 탐색하면서 UIKBCompatInputView의 인스턴스를 찾는 구조로 진행됩니다.

static func isKeyboard()->Bool{
	for i in 1..<UIApplication.sharedApplication().windows.count{
		for v in UIApplication.sharedApplication().windows[i].subviews{
			for v2 in v.subviews[0].subviews{
				//if v2.description.hasPrefix("<_UIKBCompatInputView"){
				if NSStringFromClass(v2.dynamicType) == "_UIKBCompatInputView"{
					return true //키보드 살아있음
				}
			}
		}
	}
	return false;
}

비활성화 시키기 위한 UIView탐색

키보드를 비활성화시키는 방법은 키보드를 활성화시킨 UIView객체를 찾아서 resignFirstResponder() 를 호출하는 것입니다. 하지만 이 문제는 어떤 녀석이 활성화시킨지 모른다는 점입니다.
따라서 모든 UITextInputTraits를 찾아 resignFirstResponder() 를 호출해봐야합니다. 위에서 이미 키보드가 살아있는지 아닌지를 검출하게 되었으므로 스택을 통해 windows[0]의 모든 자식 뷰를 탐색해가면서 확인해보다 사라지면 정지시키면 됩니다.

static func hideKeyboard(){
	if isKeyboard() {
		var stack = [UIApplication.sharedApplication().windows[0].subviews]
		while !stack.isEmpty {
			for v in stack.popLast()!{
				if v is UITextInputTraits{
					v.resignFirstResponder();
					if !isKeyboard() {
						//키보드를 숨기는데 성공!
						return 
					}
				}
				if v.subviews.count > 0{
					stack.append(v.subviews)
				}
			}
		}
	}
}

결론

키보드 활성상태를 감지할 수 있는 더 좋은 방법이 있다면 좋겠습니다. 하지만 노티피케이션으로는 등록절차와 해지절차가 필요하기 때문에 유틸리티형식으로 쓰기엔 적당하지 않았습니다.

%d 블로거가 이것을 좋아합니다: