UIKit으로 어플 개발하기 - PiggyBank 개발일지 3
개발일지 3
멋진 어플 개발을 하게 되었다. 일종의 타임캡슐 어플이다.
멋진 피드백을 기반으로 새로운 백로그를 작성했고, 뷰를 더 단순하고 직관적인, iOS 감성의 디자인으로 바꾸었다. 그에 따라 타임캡슐을 새로 추가하는 팝업을 다시 만들게 되었다.
구조
- 현재 진행중인 타임캡슐이 없을 때 홈 화면을 누르면 새로운 타임캡슐을 추가할 수 있다.
- 홈 화면을 누르면 전체화면으로 타임캡슐의 이름을 쓸 수 있는 텍스트필드가 나온다.
- 텍스트를 채운 뒤 다음으로 넘어가면 타임캡슐을 개봉할 시점을 고를 수 있는 피커가 나온다.
- 두 정보를 모두 채우고 확인 버튼을 누르면 저장된다.
1 ~ 4의 과정으로 새로운 타임캡슐을 추가할 수 있다.
1 ~ 4의 과정동안 홈 뷰 -> 텍스트필드 뷰 -> 피커 뷰 -> 홈 뷰 이렇게 뷰간 이동을 해야 하는데, 이는 segue
를 통해 구현했다.
텍스트필드와 피커를 각각 구현하는 것은 어렵지 않았다. 오토레이아웃만 잘 잡아주면 이상 없이 잘 작동됐다.
사용자의 입장에서 기능 구체화하기
기본적으로 뷰 각각이 자신의 역할(이름 입력받기, 시점 픽 하기)을 다 할 수 있게 만든 뒤, 유저의 입장에서 기능을 써 보았다.
써보면서 느낀 아쉬운 점은 다음과 같았다.
- 텍스트필드를 누르지 않아도 자동으로 키보드가 뜨면 좋겠다.
- 다음 버튼을 누르지 않아도 키보드의 return 키를 누르면 자동으로 다음 뷰로 넘어가면 좋겠다.
- 텍스트필드가 비었을 때 다음으로 넘어갈 수 없다는 점을 강렬하게 표현하면 좋겠다.
- 피커 뷰에서 값을 선택한 뒤 텍스트필드 뷰로 되돌아갔다가 다시 피커 뷰로 돌아와도 해당 값이 남아있으면 좋겠다.
자동으로 키보드 뜨게 하기
이름을 입력하는 뷰에서 할 수 있는 동작은 단 한가지, 텍스트 필드에 내용을 입력하는 것이다.
해당 뷰에 딱 들어왔을 때 텍스트필드를 누를 필요 없이 그냥 바로 키보드가 뜨고, 바로 입력만 하면 동작이 끝나는 것이 유저 입장에서 빠르고 간편할 거라고 생각했다. 또, 버튼이 아닌 텍스트필드 이외의 부분을 터치했을 때 키보드가 내려가지 않고 그 상태를 유지하는 것이 그 뷰의 동작을 직관적으로 보여 주는 거라고 생각했다.
그래서 뷰의 초기 세팅을 할 때 텍스트필드를 First Responder로 설정하도록 했다.
Responder Chain
앱은 responder 객체를 이용해 이벤트를 입력받고 핸들링한다. 이벤트 데이터를 받은 responder는 그 이벤트를 처리하던지, 아니면 가장 그 이벤트를 처리하기 적절한 responder 객체에게 넘겨준다. 이 때, 이벤트를 처리하기 가장 적절한 객체를 first responder
라 한다.
처리되지 않은 이벤트들은 이렇게 responder를 거쳐가는데, 이를 responder chain이라고 한다. responder chain은 아래 그림으로 확인할 수 있다.
becomeFirstResponder
UIKit은 이런 responder chain 안에서 이벤트를 처리할 first responder를 적절하게 찾을 수 있도록 이벤트 타입에 따라 first responder를 지정해놓았다. 문서
텍스트필드(혹은 텍스트뷰)의 경우, 해당 필드(혹은 뷰)를 탭하면 그 뷰가 first responder가 되고, 값을 입력받을 수 있는 키보드가 화면에 등장하게 된다.
하지만 프레임워크가 만들어놓은 판이 아니라 내가 직접 first responder를 설정해줄 수 있다. 텍스트필드를 탭하지 않아도 텍스트필드가 있는 뷰가 만들어질 때 그 텍스트필드를 first responder로 지정하면 어플리케이션은 값을 입력받을 수 있는 키보드를 바로 띄워준다.
func becomeFirstReponder() -> Bool
이는 UIResponder 클래스 안의 인스턴스 메서드로, first responder로 지정하고 싶은 UIResponder 객체에 적용해주면 된다.
반대로 first responder를 해제하는 메서드도 있다. resignFirstResponder()
가 그것이다. 텍스트필드 이외의 부분을 터치했을 때 키보드가 내려가도록 구현하려면 resignFirstResponder를 써 주면 된다.
구현중인 이 뷰에서는 텍스트필드에 값을 입력하는 동작밖에 하지 않고, 텍스트필드에 값을 입력하지 않으면 다음 뷰로 넘어갈 수 없게 해야 했기 때문에 굳이 키보드를 다시 내리도록 구현하지 않았다.
리턴 키 누르면 다음 뷰로 이동
그 대신, 텍스트필드가 비어있지 않을 때 키보드의 리턴 키를 누르면 다음 뷰로 넘어갈 수 있도록 구현했다.
이건 내가 하는 게 아니라 UITextFieldDelegate
가 대신 해 준다. UITextFieldDelegate는 유저가 입력을 시작했는지, 멈췄는지, 입력된 값이 유효한지 등을 확인해준다.
텍스트필드가 편집되는 일련의 과정 중, 유저가 키보드의 리턴 버튼을 눌렀을 때 함수 textFieldShouldReturn(_:)
도 이 Delegate에 정의되어 있다. 다음 뷰로 넘어가는 코드를 해당 함수 구현부에 작성해주면 된다.
유저에게 현재 상태 강조하기
유저가 타임캡슐의 이름을 지정하지 않으면 다음 뷰로 넘어가지 못하도록 만들고 싶었다.
텍스트필드가 비었으면, 다음으로 넘어가는 내비게이션 바 버튼을 비활성화하고, textFieldShouldReturn 내부의 로직을 무효화시키게 해 놓았다.
그런데 이는 유저에게 불친절하다는 생각이 들었다. 다음으로 넘어가는 오른쪽 위 버튼이 회색으로 되어있고 눌리지도 않는 상태로 멈춰 있는 격이기 때문이다.
가장 일반적인 방법으로 유저에게 alert을 주는 방식을 생각했다. 하지만 이는 내용을 읽고 확인 버튼을 눌러서 알림 창을 꺼야 하는 부가적인 동작이 필요했다. 그래서 조금 더 직관적으로 유저에게 알릴 수 있도록 소리 혹은 진동을 이용하기로 했다.
UIFeedbackGenerator
어떤 상태를 알리는 진동을 햅틱Haptic
이라 하고, 이를 관장하는 객체는 UIFeedbackGenerator
라고 한다. UIFeedbackGenerator는 세 개의 서브클래스가 있는데 문서에 따르면 이 객체 자체를 인스턴스화해 사용하지 말고, 객체의 서브클래스들을 인스턴스화해 사용하라고 되어 있다.
간혹가다 슈퍼클래스를 인스턴스화하지 말고 서브클래스를 인스턴스화해 사용하라는 것들이 있는데 왜일까? 모르겠다… 궁금하다…
아무튼, 세 개의 서브클래스는 다음과 같다
- UIImpactFeedbackGenerator
- UISelectionFeedbackGenerator
- UINotificationFeedbackGenerator
각각 UI상에서 객체들 간의 충돌이 일어났을 때(impact), 선택이 변화됐음을 알릴 때(selection), 성공, 실패, 경고를 알리기 위해(notification) 사용한다.
텍스트필드가 비었다는 것을 경고하기 위해 사용하기 때문에 UINotificationFeedbackGenerator 객체를 사용했다.
사용자가 시스템 햅틱 꺼놓으면 말짱 도루묵
구현을 해 놓고 내 기기로 실행을 해 봤는데 아무런 진동이 나지 않았다. 아이폰을 사용할 때 소리를 꺼 놓거나 진동을 꺼 놓는 등 사용자가 원하는대로 소리, 진동 등을 조작할 수 있듯 햅틱 반응도 사용자가 원치 않는다면 꺼 놓을 수 있다.
햅틱을 꺼 놓은 사용자는 텍스트필드가 비어있어도 아무런 피드백을 받지 못한 채 ‘이 앱 왜 안돼 삭제할래’ 라는 생각을 할 수 있다. 내가 그런 사용자 중 하나였던 것..!
그래서 햅틱만으로 경고를 주는 것은 위험하다는 생각이 들었다. 햅틱에 대한 HIG에서도 ‘Make haptics optional’이라 되어있다. 그래서 햅틱과 동시에, 텍스트필드가 비었음을 알리는 라벨을 띄웠다. 평소엔 안 보이다가 텍스트필드가 비어 있는 상태로 키보드 리턴 키를 누르거나 다음 버튼을 누르면 라벨을 띄우고 햅틱 반응을 실행했다.
결론
기본적으로 UI를 구성하고 구현해내는 것보다 유저의 입장에서 더 직관적이고 편리하게 느껴지는 유저 인터페이스를 구성하는 것이 중요하다는 걸 느꼈다.
이와 동시에 Apple은 정말 하나부터 열까지 애플스러운 UI/UX를 다 짜 놓았다는 생각이 들었다. 애플 기기를 사용하는 유저들은 이에 익숙할테니 Human Interface Guide에 잘 따르는 게 더 잘 팔리는, 애플스러운 앱을 만드는 지름길이라고 생각된다.
이번엔 유저의 입장에서 아쉬웠던 점, 보완해야 할 점 중 텍스트필드 입력에 관한 것들을 정리했다. 이번엔 UI/UX 부분의 보완점이었다면 다음엔 뷰 간 이동을 할 때 서로의 데이터를 넘겨주는 방식으로 Delegate를 사용하는, 객체 간의 결합도를 낮추는 방법을 정리해보려 한다.
댓글남기기