Share
Sign In
[Swift - UIKit] iOS에서의 기본적인 Notification System
한결
알림센터나 사이드 푸쉬 등으로 특정 블록 형태의 UI가 뜨는 것을 Notification 이라고 한다. 카카오톡에서 친구가 채팅을 보냈을 때, 쿠팡에서 특가 상품이 떴을 때, 주변에 토스를 사용하는 사용자가 발견되었을 때와 같은 상황에서 주로 알림이 온다. 이런 기본적인 '알림'이라고 하는 것을 다룰 수 있는게 iOS Notification System이다.
내장된 User Notification Framework를 이용하여 쉽게 알림을 구현할 수 있다.
리모트 방식의 알림은 서버를 통해 인증된 유저에게만 보여줄 수 있기 때문에 APNs 라고하는 서비스를 거쳐야 한다.
Notification System의 두 가지 방식
Notification 방식은 Local, Remote 형태가 있다. 이름에서 두 방식의 차이를 대강 알 수 있다.
Local
알림에 전달할 내용이 전부 앱에 들어있다.
루틴한 내용이나 비슷한 컨텐츠를 전달해주는 용도로 사용된다. (디데이, 루틴, 투두 어플 등)
Remote
서버단에서 보내는 알림을 컨트롤하는 형식. (푸시 알림이라고도 함)
(서버에서 데이터가 오기 때문에) 유저가 알 수 없는 시간에 서로 다른 컨텐츠를 전달해주는 용도로 사용된다.
광고, 개인화된 추천, 채팅 알림(ex. 카카오톡 채팅별 알림) 등의 알림이 리모트 형식이다.
Notification System에서 유의해야 할 점
Notification이 동작하기 위해서는 반드시 유저에게 권한 동의를 받아야 한다. 아이폰의 여러 앱을 써보면 알겠지만, 앱을 처음 실행할 때나 앱을 실행시키고 있는 도중에도 계속 '알림'에 대한 권한을 획득하려고 하는 뷰가 나타난다. 어떤 앱에서는 '권한이 어떤 시점에서 부여되지 않았다'는 사실을 알려주는 alert을 보여줄 때도 있다.
애플에서는 알림에 대한 유저 동의 여부를 강하게 체크한다.
무분별하게 알림을 내뱉는 앱은 앱의 사용 경험을 넘어서 아이폰의 사용 경험 자체를 헤칠 수 있기 때문에 애플에서 권한 획득을 중요하게 생각하는 것 같다.
커머스나 앱 자체로 비즈니스 가치가 발생하는 서비스들은 사용자에게 알림 권한을 획득하기 위해서 노력한다.
앱이 종료된 시점에도 우리 서비스가 있고, 우리 서비스를 통해 새로운 이점을 얻을 수 있다는 사실을 알림을 통해 간편하게 전달할 수 있기 때문이다.
더불어, 애플은 Notification에 대해서 아래와 같은 정책을 운영하고 있다.
Notification은 고유한 identifier를 가진다. 하나의 앱에서 최대 64개의 identifier를 운영할 수 있다.
64개의 idendifier를 넘기면 앱이 종료될 수 있다. (무분별한 알림을 방지하는 장치인 것 같다.)
64개를 넘기기 전에 알림을 리프레시 할 수 있는 앱 내부 정책, 코드 관리가 필요해 보인다.
TimeInterval로 반복할 수 있는 최소 시간은 60초다.
60초 보다 적은 시간 단위로 동일한 알림을 반복할 수는 없다.
(역시 무분별한 반복적인 알림을 방직하는 장치로 보인다.)
Notification은 기본적으로 앱의 background에서 동작한다.
background는 앱이 켜져있지 '않은' 상태를 의미한다.
반대는 foreground인데, delegation을 통해서 앱이 실행중인 상태에서도 Notification이 오도록 설정할 수는 있다.
Notification Center나 배너 상태 등에서 유저가 Notification 자체를 인지했는지는 알 수 없다.
유저가 해당 알림을 터치했을 경우에만 확인할 수 있다.
UNUserNotificationCenter를 이용해서 간단하게 Notification을 구현해보자
AppDelegate 단에서 앱이 런칭되는 시점에 Notification에 대한 권한을 획득하는 코드를 추가하자.
UNAuthorizationOptions를 이용해서 획득하고자 하는 알림 형식을 정해줄 수 있다.
UNMutableNotificationContent, UNTimeIntervalNotificationTrigger, UNNotificationRequest 클래스로 Notification을 위한 인스턴스를 생성하여 알림을 제어하는 코드를 작성할 수 있다.
Ha
/hankyeol
Subscribe
[Swift - UIKit] BaseView, APIService 싱글턴 패턴, DispatchGroup 등을 활용한 간단한 영화 평점 매기기 서비스를 구현해보았다.
영화 앱에서는 TMDB API를 활용하여 다음과 같은 페이지를 구현했습니다. 자세한 코드는 아래 깃헙에서 확인 가능합니다. 메인페이지 - 현재 상영작 / 요즘 주목받는 작품 / 역대 최고의 평가를 받은 작품 리스트를 테이블뷰 + 가로 컬랙션뷰 형태로 노출 영화 상세 페이지 - 컬랙션 아이템 터치시 해당 영화 id값을 기반으로 내가 매긴 별점, 줄거리, 출연진, 유사한 영화 리스트가 한 페이지에서 노출 영화 검색 페이지 - 텍스트 필드에서 키워드로 영화를 검색한 결과를 테이블뷰로 노출 (검색 결과가 없을 경우 핸들링) BaseView를 만들어 뷰의 상속 관계 이용하기 UIView를 상속받는 기본적인 BaseView를 만들어서 해당 뷰를 상속받는 뷰 객체들이 미리 정의된 메서드를 오버라이딩하여 UI와 기능을 보다 빠르고 편하게 구축할 수 있게 코드를 작성했다. 프로젝트 코드에서 UI의 레이아웃을 잡거나 서브뷰를 구성한다던지, UI와 관련된 코드를 작성하는 경우에서 반복이 많다. 위의 코드처럼 베이스가 되는 뷰 클래스를 하나 만들어두면, 어떤 메서드에서 레이아웃을 잡고, 어떤 메서드에서 UI 코드를 잡을지 따로 신경쓰지 않아도 된다. (제약을 미리 하나 만들어두는 느낌이라 Protocol을 활용하는 것과 비슷하다.) 자주 사용되는 UI 객체 역시 BaseView를 만들어서 활용했다. 특히, 라벨이라던지 박스 형태의 컴포넌트는 프로젝트 페이지별로 많이 활용되기 때문에(=인스턴스를 생성할 때가 많음) 베이스가 되는 클래스를 만들어 두는 것이 정말 유용했다. 인스턴스로 생성된 뷰 객체들은 외부에서 되도록이면 속성을 직접 건드리지 않고, 내부에 구현된 메서드를 이용할 수 있도록 제어자를 만드는 것에 집중했다. BaseLabel.swift BaseBoxItem.swift ViewController에서 컨트롤할 뷰 객체 전체를 감싸는 mainView를 분리하였다. VC에서는 해당 전체 뷰 객체의 인스턴스를 만들어서 loadView 메서드 내부에서 기존의 root view를 mainView로 대치하여 사용했다. 즉, 커스텀한 뷰로 nil 상태의 root view를 대체해주는 것이 loadView의 역할인 것이다. loadView 메서드를 오버라이드 할 때 주의해야 할 점은 (어떻게 보면 당연한 이야기 일 수 있는데) super의 loadView 구현부를 상속받지 않는 것이다. ViewController에서 View의 레이아웃이나 UI 코드에 대한 관심사를 분리시킬 수 있어 아주 유용해 보인다. 다만, 뷰 컴포넌트들의 구조가 복잡해지거나, 한 페이지에 정말 다양한 뷰 요소들이 들어가는 앱이라면 클래스의 뎁스를 많이 두는 것이 코드를 유지보수 하는 관점에서 좋을지는 계속 고민해볼 필요가 있을 것 같다.
한결
[Swift - UIKit] do - try - catch 구문을 이용해서 UITextField의 입력값 조건에 맞게 검증하기
회원가입을 한다던지, 맞춤형 서비스에서 네이밍을 하는 형태에서는 유저가 입력하는 값을 특정한 조건에 맞게 제한할 수 있다. 서비스 코드에서 '조건에 따른 제한'을 가한다는 것은 데이터에 대한 '조건별 제어'가 가능해야 한다는 것이고, 조건을 충족하지 못 한 경우에는 에러가 발생할 수 있다는 의미이기도 하다. do~try - catch 구문을 활용하면 특정 코드의 액션의 결과를 try로 기다린다. 성공할 경우 do 스코프 이내에서 try 이후의 구문이 실행된다. 실패할 경우 catch 스코프에서 실패에 따른 로직이 실행된다. 에러 케이스별로 실패 구문을 관리할 수 있다는 점이 좋다. 유저의 닉네임을 아래 조건에 따라 검증한다고 할 때, do~try - catch 구문으로 어떻게 에러에 따른 처리를 할 수 있을지 한 번 살펴보자. ValidationService 클래스를 구성해서 검증하는 로직과 에러 여부에 따라 문구를 보여주는 영역을 구분해보았다. 닉네임은 빈 문자열로 저장될 수 없다. 닉네임은 공백 포함 2자 이상 10자 이하로 구성되어야 한다. 닉네임에는 숫자가 들어갈 수 없다. 닉네임에는 @, % 라는 특수문자가 들어갈 수 없다. 1. ValidationService 클래스 생성하기 클래스 내부에 에러 조건에 대한 케이스 관리를 할 Errors 열거형을 만들었다. validateNickname 이라는 타입 메서드에서 각각의 조건을 검증하는 private 함수들을 실행시키다. 각각의 검증 함수에서 true 값을 확인 할 경우, 해당하는 에러 케이스로 쉽게 throw 해줄 수 있다. 외부에서는 이 메서드만 호출해서 입력 받은 값을 검증하고, 열거형 케이스에 따라 분기처리 할 수 있다. 2. UITextFieldDelegate 내부에서 do~try - catch 구문으로 에러 핸들링하기 TextField가 있는 VC에 UITextFieldDelegate를 위임해주면 textFieldDidChangeSelection 이라는 메서드 내부 로직을 구성해줄 수 있다. (꼭 이 메서드가 아니어도 괜찮다.) 해당 메서드 내부에서 do 구문으로 ValidateService 클래스의 validateNickname 타입 메서드를 호출한다. (textfield에 입력되는 글을 인자로 넣어준다.) 만약 조건에 충족되는 텍스트가 입력될 경우 성공 문구 보여주고, 확인 버튼이 터치할 수 있는 상태가 된다. 조건을 충족하지 않는 텍스트가 입력될 경우, 각 에러 케이스에 맞는 catch 구문으로 빠진다. 각각의 에러 케이스를 굳이 만들지 않고 catch 스코프 하나로 모든 에러 케이스를 핸들링 할 수도 있다.
한결
[Swift - UIKit, CoreLocation] iOS에서 사용자 정보 권한과 위치 권한 획득
iOS, iPadOS와 같이 애플 기기에서 돌아가는 운영체제는 사용자의 개인정보 보호에 아주 민감하다는 것을 많은 기사와 사례를 통해서 알 수 있다. 애플은, 어떤 기기에서 돌아가는 앱이든, 사용자의 앱 사용 경험을 헤치지 않으면서 유저가 권한 부여에 동의한 정보에 대해서만 앱이 활용할 수 있는 강력한 통제 시스템을 구축하고 있다. 사용자 > 기술 이라는 기본적인 모토에서 앱이 사용자의 정보 통제권을 쉽게 가질 수 없도록 만든다. iOS의 메이저 버전이 올라가면서 사용자가 앱에 줄 수 있는 개인 정보 통제권 유형을 *더 세분화 하고 있다. * 위치 정보를 허용한다고 했을 때, 어느 순간부터 아래와 같은 선택지를 사용자에게 제공해주었다. (iOS13+) - 허용하지 않음 - 한 번만 허용 - 앱을 사용할 때만 허용 - 항상 허용 정리하면, 앱을 만들 때 사용자가 부여한 권한의 정도가 어떻게 되는지를 계속 신경써야 한다는 것이고, 사용자가 갑자기 해당 정보에 대한 권한을 차단하지는 않았는지를 계속 인앱 상황에서 모니터링 하고 있어야 한다는 점이다. 위치 정보에 대한 권한을 획득하고 획득한 위치 기반으로 현재 날씨를 보여주는 간단한 앱을 만들어 보면서 위치 서비스에 대한 권한 획득을 경험해봤다. 순서대로 기록해본다. 간단히 위치 정보 활용 동의받고 이용하기 info.plist에서 Privacy 항목 활성화하고 사용자 위치 정보를 수집하는 이유가 담긴 문구 수정하기 해당 항목에 작성하는 문구가 추후에 권한을 획득하는 Alert의 메시지에 반영된다. 위치 정보를 왜 수집하고, 어떻게 사용되는지 등에 대해 최대한 자세히 작성해야 앱 출시에 제한이 없다. 사용자가 애플 기기 시스템에서 위치 서비스에 대한 권한을 켜뒀는지 확인하기 CoreLocation 패키지를 가져오고 CLLocationManager 클래스의 인스턴스를 생성하여 CLLocationManagerDelegate 로 위치 권한을 획득하는 역할을 VC에 위임해준다. .locationServicesEnabled 라는 타입 메서드로 유저가 시스템 상에서 위치 서비스를 활성화했는지 확인한다. else 구문으로 빠지면, 보통 alert 등을 통해서 시스템 설정에서 위치 서비스를 켜달라는 식으로 사용자의 선제 액션을 유도할 수 있다. (혹은 이동시킬 수도 있겠다.)
한결