본문 바로가기

Application Programming Interface/Cocoa

키보드 보이기/숨김에 따른 UITextView 및 UITextField의 가려짐 현상 해결법

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

키보드 보이기/숨김에 따른 UITextView 및 UITextField의 가려짐 현상 해결법

- by Tapito


 iOS 어플 제작 시 화면에 나타나는 소프트 키보드에 의해 TextView 또는 TextField의 가려짐 현상에 대한 해결방법입니다.

 아래와 같은 화면이 있습니다. 가운데 표시한 부분이 UITextView입니다. 좀 더 과장된 현상을 위해 Lanscape 모드로 보겠습니다. Interface Builder에 View들만 배치하였고 ViewController에는 아직 아무 코드도 추가한 것이 없습니다.

 이 화면에서 표시한 UITextView를 클릭합니다. 키보드가 나타나서 원래 편집하고자 했던 UITextView의 텍스트를 가려버립니다.

 또한 코드에 아무 작업도 적지 않았으므로 일단 Focus가 주어진 UITextView는 이 상태에서 계속 자신의 포커스를 유지하려 하기 때문에 UITextView 외 다른 여백이나 Label을 클릭하여도 키보드가 사라지지 않는다는 문제 또한 있습니다. 아래와 같은 단계를 따라하시면 이 문제를 해결할 수 있습니다.

1. UITextView 바깥을 터치할 경우 키보드가 사라지도록 코드 작성하기

 TextView가 가려지는 현상은 일단 두고 여백을 터치 시 키보드가 사라졌다가 TextView를 터치할 때만 키보드가 나타나도록 해보겠습니다. 사용자의 터치 동작과 관련된 메서드는 UIViewController의 touchesBegin:withEvent 메서드입니다. 키보드는 UITextView가 포커스를 잃으면 자동으로 사라지기 때문에 이 곳에서 사용자가 어디를 터치했는지를 검사하여 포커스를 원래 갖고 있던 UITextView가 아니면 강제로 잃게 하는 방식으로 구현하겠습니다.

 ViewController.h의 내용

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

// 화면 중간에 있던 UITextView에 대응될 아웃렛입니다.
@property (strong, nonatomic) IBOutlet UITextView * textView;

@end

 ViewController.m의 내용

// ... 중략 ...
// TextView 외에 다른 View를 터치했을 경우 키보드를 숨기고 View를 원래대로 되돌리는 역할을 수행합니다.
// 이를 위해 touchesBegan:withEvent: 메서드는 이 View 내에서 터치 동작을 감지하여 어느 View가 터치되었는지 그 결과를 전달하는 역할을 수행합니다.
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
	// 터치 동작들을 가져옵니다.
	UITouch * touch = [[event allTouches] anyObject];
    
	// textView가 현재 포커스되어 키보드가 띄워져 있는 경우...
	if ([[self textView] isFirstResponder])
	{
		// [touch view]로 어느 View가 터치되었는지를 가져옵니다.
		// 터치된 View와 이 View의 textView가 다르다면, 즉 다른 View를 터치했다면...
		if ([touch view] != [self textView])
		{
			// 지금 포커스된 textView의 포커스를 박탈합니다.
			// 포커스를 잃은 textView에 의해 UIKeyboardWillHideNotification 이벤트가 발생하면서
			// keyboardWillHide 메서드가 실행됩니다.
			[[self textView] resignFirstResponder];
		}
	}
    
	// 다른 작업들은 부모 클래스의 메서드로 넘깁니다.
	[super touchesBegan:touches withEvent:event];
}
// ... 후략 ...

 

이 정도만 해도 키보드를 숨겼다가 감추는 정도는 할 수 있습니다. 덩달아 TextView가 일단 포커스를 가지면 다른 여백을 터치해도 포커스를 내놓지 않는 문제도 해결되었습니다. 그러나 여전히 UITextView는 포커스를 가질 때 소프트 키보드에 가려집니다. 키보드가 아래에서 떠오를 때는 전체적으로 View의 위치를 키보드 높이만큼 위로 들여올려주고, 키보드가 사라질 때는 들여올렸던 View의 위치를 다시 원래대로 돌려놓는 방식을 사용하겠습니다. 이를 위해 iOS에서 제공하는 이벤트는 UIKeyboardWillShowNotification과 UIKeyboardWillHideNotification입니다.

2. UITextView/UITextField의 가려짐 문제 해결하기

2-1. 키보드가 숨겨질 때 또는 떠오를 때 View를 옮기는 메서드 작성

 우선 View가 전체적으로 위로 들여올려졌다가 복귀하는 메서드를 작성해보겠습니다. 각각의 이름은 iOS 키보드 이벤트에서 따온 keyboardWillShow:와 keyboardWillHide:로 정하겠습니다. 매개변수인 (NSNotification *)notification은 메서드 호출 시 iOS로부터 넘어오는 각종 정보입니다. 윈도우 API의 wParam, lParam, 닷넷에서 말하는 EventArgs에 해당합니다.

 어느 정도만큼 View를 올렸다 내렸다 할 것인지를 결정해야 하는데, 여기에서는 소프트 키보드의 높이만큼 들었다 놨다 하겠습니다. 소프트 키보드의 높이는 NSNotification에 실려 옵니다. [notification valueForKey:UIKeyboardFrameEndUserInfoKey]를 통해 얻을 수 있습니다.

 부수적으로, UIKeyboardFrameBeginUserInfoKey가 있는데 둘의 차이는 이벤트 처리 전후입니다. 아직 키보드가 떠오르지 않은 상태라면 UITextView의 클릭을 통해 넘어오는 UIKeyboardFrameBeginUserInfoKey의 값은 현재 숨겨져 있는 키보드의 사각 영역이고, UIKeyboardFrameEndUserInfoKey는 이벤트처리가 끝난 후 화면에 보여지게 될 키보드의 사각 영역입니다.

 ViewController.m의 내용

// 키보드가 아래에서 떠오르는 이벤트 발생 시 실행할 이벤트 처리 메서드입니다.
- (void) keyboardWillShow:(NSNotification *)notification
{
	CGRect rectView; // 이 View에 대한 위치와 크기의 사각 영역을 나타낼 CGRect 구조체입니다.
	CGRect rectKeyboard; // 키보드에 대한 위치와 크기의 사각 영역을 나타낼 CGRect 구조체입니다.
    
	// 애니메이션 효과를 지정합니다. 이와 관련한 함수들은 다음에 따로 정리합니다.
	[UIView beginAnimations:nil context:NULL];
	[UIView setAnimationDuration:0.3];
    
	// 이 View 자체에 대한 사각 영역을 가져옵니다.
	rectView = [self.view frame];
	// 하단에서 떠오른 키보드의 사각 영역을 가져옵니다.
    
	[[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue:&rectKeyboard];
    
	// View를 전체적으로 키보드 높이만큼 위로 올립니다.
	rectView.origin.y -= rectKeyboard.size.height;
	// View의 변경된 사각 영역을 자기 자신에게 적용합니다.
	[self.view setFrame:rectView];
    
	// 변경된 사각 영역을 애니메이션을 적용하면서 보여줍니다.
	[UIView commitAnimations];
}

// 키보드가 아래로 잠기는 이벤트 발생 시 실행할 이벤트 처리 메서드입니다.
- (void) keyboardWillHide:(NSNotification *)notification
{
	CGRect rectView; // 이 View에 대한 위치와 크기의 사각 영역을 나타낼 CGRect 구조체입니다.
	CGRect rectKeyboard; // 키보드에 대한 위치와 크기의 사각 영역을 나타낼 CGRect 구조체입니다.
    
	// 애니메이션 효과를 지정합니다. 이와 관련한 함수들은 다음에 따로 정리합니다.
	[UIView beginAnimations:nil context:NULL];
	[UIView setAnimationDuration:0.3];
    
	// 이 View 자체에 대한 사각 영역을 가져옵니다.
	rectView = [self.view frame];
	// 하단으로 잠길 키보드의 사각 영역을 가져옵니다.
	[[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue:&rectKeyboard];
    
	// View를 전체적으로 키보드 높이만큼 아래로 내립니다.
	rectView.origin.y += rectKeyboard.size.height;
	// View의 변경된 사각 영역을 자기 자신에게 적용합니다.
	[self.view setFrame:rectView];
    
	// 변경된 사각 영역을 애니메이션을 적용하면서 보여줍니다.
	[UIView commitAnimations];
}

2-2. 키보드 이벤트 메서드의 등록/해제

 위에서 작성한 keyboardWillShow:와 keyboardWillHide:를 iOS에 등록하기 위한 메서드를 다시 작성합니다. 각각의 함수를 registerKeyboardEvent와 unregisterKeyboardEvent로 이름 붙이겠습니다.

// iOS가 키보드를 보여주려고 할 때 keyboardWillShow: 메서드를,
// iOS가 키보드를 숨기려고 할 때 keyboardWillHide: 메서드도 함께 실행할 것을 지정하는 부분입니다.
- (void) registerKeyboardEvent
{
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}

// iOS가 키보드를 보이거나 숨길 때 더 이상 keyboardWillShow:, keyboardWillHide: 메서드를
// 실행하지 말 것을 지정하는 부분입니다.
- (void) unregisterKeyboardEvent
{
	[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
	[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}


 

2-3. View의 로드/언로드시 registerKeyboardEvent와 unregisterKeyboardEvent 메서드가 실행되도록 하기

 View가 로드되고 나서 호출되는 메서드는 viewDidLoad:이고 언로드 되고나서 호출되는 메서드는 viewDidUnload:입니다. 이를 이용해 View가 로드되면 키보드 이벤트 처리기를 iOS에 등록하고, View가 언로드 되면 키보드 이벤트 처리기를 iOS에서 제거하는 코드를 작성합니다.

// View의 메모리 적재가 완료되었을 경우
- (void) viewDidLoad
{
	[super viewDidLoad];

	// Do any additional setup after loading the view, typically from a nib.
    
	// registerKeyboardEvent를 실행하여 iOS에 키보드 관련 이벤트 처리기 등록
	[self performSelector:@selector(registerKeyboardEvent)];
}

// View가 언로드 되었을 경우
- (void) viewDidUnload
{
	[super viewDidUnload];
    
	// Do any additional setup after loading the view, typically from a nib.
    
	// unregisterKeyboardEvent를 실행하여 iOS에 키보드 관련 이벤트 처리기 등록을 해제
	[self performSelector:@selector(unregisterKeyboardEvent)];
}

 performSelector는 특별히 실행시간이 오래 걸리는 메서드를 별도의 Thread에서 실행하도록 지정하는 메시지입니다. NSThread 객체를 직접 선언하여 UI 관련 메서드를 실행할 경우 MainThread가 아니라는 내용의 예외가 throw되니 UI 관련 해 스레드를 따로 만들어서 작동되게 하고 싶다면 performSelector를 이용하기 바랍니다. 여기에서는 이벤트 등록/해제하는 과정이 극적으로 느려지는 과정은 아니기 때문에 아래와 같이 2-2와 2-3을 하나로 합쳐도 무방합니다.

- (void) viewDidLoad
{
	[super viewDidLoad];

	// Do any additional setup after loading the view, typically from a nib.
    
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}

// View가 언로드 되었을 경우
- (void) viewDidUnload
{
	[super viewDidUnload];
    
	// Do any additional setup after loading the view, typically from a nib.
    
	[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
	[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}

 이제 앱을 실행해보겠습니다. 겉으로 보여지는 모양은 일단 동일합니다.

 UITextView를 클릭해 봅니다. UITextView로 포커스가 옮겨지면서 소프트 키보드의 등장과 함께 View도 전체적으로 위로 뜹니다. 즉, 전과 같은 UITextView의 가려짐 현상이 해결되었습니다.

 UITextView 이외의 영역을 클릭해 봅니다. UITextView가 포커스를 잃습니다. UITextView가 포커스를 잃었기 때문에 소프트 키보드로 아래로 잠겨 사라집니다. 소프트 키보드가 잠기는만큼 View도 전체적으로 아래로 내려와 원위치됩니다.