Share
Sign In
Swift | 학습 내용 정리
[Swift - UIKit] UIViewController 위에 여러 Protocol을 적용하여 동작하는 다양한 뷰 만들기
한결
👍
💡
코드 베이스로 뷰를 그리는 연습을 하기 전, SB + Outlet 코드로 뷰를 그리는 연습을 하면서 최종적으로 만들어 본 TravelProject에 대한 정리 글입니다. 전체 코드는 해당 링크에서 볼 수 있습니다.
이런 느낌의 간단한 앱을 만들어 보면서 Storyboard + Code 형태로 간단하게 앱을 빌드하는 과정에 익숙해짐
Swift를 학습하면서 정말 다양한 뷰를 직접 만들어보고 있다. 24년 5월 27일부터 30일까지 스토리보드 기반으로 UIViewController 위에서 코드로 여러 화면을 그리는 연습을 했다. 그 과정에서 나 스스로 '이건 꼭 기록으로 남겨봐야겠다.' 하는 점들이 있어서 남겨본다.
1. UIViewController 위에 커스텀한 TableView 올려보기 (with. XIB)
UIViewController에 UITable을 핸들링하기 위한 Delegation, Datasource 프로토콜을 적용해준다.
*Protocol을 통해 delegation을 위임하는 것에 대한 내용은 요 링크로 짧게 작성해봤다.
UITable에 들어갈 커스텀한 TableCell을 작업하기 위해 Swift 파일과 XIB 파일을 함께 생성해준다.
XIB에서 작업한 커스텀 셀을 재사용이 가능한 셀로 만들기 위해 정확한 Identifier를 지정해줘야 한다.
이 부분 역시 UITableViewCell 클래스를 상속받는 클래스들이 identifier라는 멤버 변수를 가질 수 있도록 protocol + extension 조합으로 코드를 미리 작성해두면 편하다.
UIViewController 위에 올려진 UITableView의 인스턴스에 커스텀한 Cell을 연결하고, 어떤 행에 어떤 셀을 어떻게 보여줄 것인지를 위임받은 코드에 설정해준다.
코드로 한 번 보자.
// UIViewController class SomeUIViewController: UIViewController { @IBOutlet var someTable: UITableView! ... func configureTable() { // 각각의 프로토콜에 테이블 핸들링을 위임 받는다. someTable.delegate = self someTable.dataSource = self someTable.register( UINib(nibName: CustomTableCell.identifier, bundle: nil), forCellReuseIdentifier: CustomTableCell.identifier ) } } extension SomeUIViewController: UITableViewDelegate, UITableViewDataSource { // SomeUIViewController의 역할을 테이블 관련 프로토콜로 확장시킨다. func tableView( _ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // 하나의 테이블 섹션에서 몇 개의 행을 생성할 지 결정해주어야 한다. return datas.count } func tableView( _ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // xib로 생성한 커스텀 Cell 인스턴스를 반환해줘야 한다. let cell = someTable.dequeueReusableCell( withIdentifier: CustomTableCell.identifier, for: indexPath ) as! CustomTableCell return cell } func tableView( _ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // 특정 Cell이 선택(select)되었을 때, 어떤 동작을 처리할 지에 대한 로직이 온다. // 해당 메서드에 대한 구현은 필수가 아니지만, 보통 구현하는 것 같다. } }
위 코드 형태가 Swift에서 테이블 뷰를 그리는 가장 기본적인 방법이다.
cellForRowAt 에서 커스텀하게 생성한 TableViewCell 클래스의 인스턴스를 as 키워드로 정확하게 캐스팅 해줘야 해당 클래스 내부에 구현한 멤버 요소들을 불러와 사용할 수 있다.
2. 연결된 VC에 데이터 넘겨주기
테이블 뷰에서 특정 셀을 터치하여 선택할 경우, 연결된 페이지 뷰(UIViewController)로 데이터를 전달해야하는 경우가 있다. 여러 방법이 있겠지만, 가장 쉽다고 생각하는 방법은 해당 VC의 멤버 변수(Outlet 변수 포함)에 값을 할당하는 것이다.
// SomeViewController.swift class SomeViewController: UIViewController { var datas: SomeDataSet? override func viewDidLoad() { super.viewDidLoad() .. } func configureViewWithData(dataSet: SomeDataSet) { // 멤버 변수로 선언한 datas에 값을 할당해준다. datas = dataSet // Optional 멤버 변수로 선언했기 때문에 unwrapping은 필수다. guard let datas else { return } someLabel.text = datas.somekey.text .. } } // UITableView가 컨트롤 되는 VC에서 작성 func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let vc = storyboard?.instantiateViewController( withIdentifier: SomeViewController.id ) as! SomeViewController // pushViewController 메서드를 호출하기 전에 데이터를 VC 멤버들에 할당해준다. vc.configureViewWithData(dataSet: someDatas[indexPath.row]) navigationController?.pushViewController(vc, animated: true) someTable.reloadRows(at: [indexPath], with: .none) } func tableView(_ tableView: UITableView, didSelect)
테이블의 특정 셀이 선택되어 연결된 VC 인스턴스가 Present되기 전에 반드시 데이터 자체를 VC의 내부 멤버들에 할당하는 코드가 반영되어야 한다.
VC가 Present되면 ViewDidLoad 메서드가 호출되고, 해당 뷰에 있는 각각의 뷰 객체들의 속성 값이 반영되어 있지 않으면 알 수 없는 nil 값을 만나서 앱이 종료된다.
다른 SB에 있는 VC를 찾아 navigating하는 방법에 대해서는 요 링크에 작성해뒀다.
코드로 다른 VC를 연결해주는 방법은 오히려 더 간단하다. 그냥 VC 인스턴스를 생성해주면 된다.
let vc = SomeViewController() present(vc, animated: true)
유저의 액션에 의해서 VC간 데이터가 잘 전송되면 다양한 형태의 UI로 앱을 구성하기 좋다.
3. UISearchBar + UISegmentedControl + UITableView 조합으로 필터링된 데이터 보여주기
유저가 원하는 조건으로 데이터를 필터링해서 보여줘야 할 경우가 많다. 검색을 할 수도 있고, 메뉴를 여러개 생성할 수도 있고, 조건 자체를 미리 여러 개 생성해 둘 수도 있다. 이번 프로젝트를 하면서 iOS에 내장된 SegmentedControl, SearchBar를 통해 데이터를 필터링 해볼 수 있었다. 물론 미리 정의된 Protocol을 불러와 역할을 ViewController에 위임하는 형식으로 코드를 작성했다.
1.
필터링 할 데이터셋 확인하기
데이터를 필터링하려면 데이터가 어떤 형식으로 생겼는지 알아야 한다.
데이터는 기본적으로 (관계성이 있는 정형화되었다면) 동일한 키를 서로다른 값으로 채우는 형식이다.
이런 데이터를 특정 기준으로 필터링 하는데 가장 좋은 기준은 Bool 타입을 찾는 것이다.
// City Model struct City { let city_name: String let city_english_name: String let city_explain: String let city_image: String let domestic_travel: Bool var formattedCityName: String { return "\(city_name) | \(city_english_name)" } } struct Cities { static let cities: [City] = [ ... ] var isDomesticCities: [City] { Cities.cities.filter { return $0.domestic_travel } } var isNotDomesticCities: [City] { Cities.cities.filter { return !$0.domestic_travel } } }
City라는 구조체는 domestic_travel (국내 여행 여부) 라는 키(사실 멤버 변수명이지만)에 Bool 타입의 값이 들어와야 한다. 필터링하기 딱 좋은 기준이다.
해당 키를 기반으로 .filter 메서드를 활용해 국내 여행 / 비국내 여행을 구조체 내에서 계산형 변수로 반환해줄 수 있다. View 단에서 로직을 세울 필요가 없다.
2.
세그먼트의 선택에 따라서 필터링된 데이터 할당하기
데이터 처리가 준비되었다면, 이제 세그먼트 요소별 선택 값만 잘 지정해주면 된다.
// View Controller class SomeViewController { .. var data: [City]? @IBOutlet var segmentControl: UISegmentedControl! func configSegment() { ["전체", "국내", "해외"].enumerated().forEach { (idx, v) in // 이렇게 배열로 하지 않고 다른 방법을 사용해도 물론 괜찮다. segmentControl.setTitle(v, forSegmentAt: idx) } // 버튼에 addTarget하는 것과는 다르게, // 세그먼트의 value가 변경되면 액션이 발생되도록 설정해줬다. segmentControl.addTarget( self, action: #selector(selectSegment), for: .valueChanged ) } @objc func selectSegment(_ sender: UISegmentedControl) { // sender 인스턴스 자체에 selectedSegmentIndex 값이 있다. switch sender.selectedSegmentIndex { case 1: data = Cities.isDomesticCities case 2: data = Cities.isNotDomesticCities default: data = Cities.cities } // 물론 여기쯤에서 테이블 데이터를 reload하는 함수 호출이 필요할 수 있다. } }
UISegmentedControl 객체에 내장된 setTitle 메서드로 세그먼트 선택지 이름을 부여할 수 있다.
UIButton에 액션 타겟 함수를 지정해주는 것과 동일하게, 세그먼트 인스턴스에도 값이 변경될 때마다 불러낼 액션 타겟 함수를 지정할 수 있다.
.selectedSegmentIndex 라는 접근자로 필터링 해두었던 데이터를 맵핑만 잘 해주면 된다.
필터링 된 데이터를 테이블에 보여주려면, 테이블 reload 함수를 호출하면 된다.
기준이 명확하다면, 열거형으로 기준을 명확하게 정의해주는 것도 좋을 것 같다.
3.
검색어에 따라서 필터링된 데이터 보여주기
UISearchBar 역시 VC에 그 역할을 위임할 수 있다. (UISearchBarDelegate)
extension 키워드로 VC에 서치바에 대한 역할 코드를 구분하여 작성하면 코드를 관리하기 편하다.
// View Controller extension SomeViewController: UISearchBarDelegate { }
서치바에서 입력이 어떻게 이루어졌을 때, 검색어를 어떻게 처리해줄 지를 결정하는 것이 로직의 핵심이다. 아래 목록 중에 밑줄을 그어둔 부분이 서치바에서 내가 고려한 유저 이벤트다.
유저가 보고 싶은 데이터를 키워드로 검색했을 때 (검색 완료) → 해당 키워드를 포함하는 or 키워드와 일치하는 데이터를 필터링해서 보여준다.
유저가 서치바에 입력할 때마다 데이터를 검증하면서 해당하는 내용이 있으면 필터링해서 보여준다.
유저가 서치바에 입력한 데이터를 다 지우면, 처음 데이터를 다시 보여준다.
extension SomeViewController: UISearchBarDelegate { var data: [City]? @IBOutlet var searchBar: UISearchBar! // 1. 검색어를 입력하고, '검색' 버튼 or '입력키' 를 입력했을 때 func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { view.isEndEditing(true) // 키보드를 내려주고 // 서치바에 입력한 값이 nil일 경우를 배제해주고 guard let text = searchBar.text?.lowercase() else { return } if text.isEmpty { // 서치바를 비어둔 상태에서 입력 -> 기존 데이터 유지 data = 기존 데이터 } else { data = 검색어로 필터링한 데이터 } // 검색이 끝나면, 업데이트 된 데이터를 기반으로 테이블 리로드 table.reloadSections(IndexSet(integer: 0), with: .none) } // 2. 검색어를 타이필 할 때마다 호출되는 메서드 (1번과 내부는 거의 동일함) func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { guard let text = searchBar.text?.lowercased() else { return } if text.isEmpty { searchedData = 기존 데이터 } else { searchedData = 검색어로 필터링한 데이터 } table.reloadSections(IndexSet(integer: 0), with: .none) } // 검색어에 따라 기존 데이터를 필터링하는 helper function func _filteringData(_ dataSet: [City], keyword: String) -> [City] { return dataSet.filter { $0.city_name.contains(keyword) || $0.city_en_name.contains(keyword) || $0.city_explain.contains(keyword) } } }
검색어를 어떻게 필터링 할 것인지는 개발자의 뜻에 달려있는 것 같다.
양이 정해진 데이터에서 검색을 할 때는 매번 필터링을 거쳐도 괜찮겠지만, 검색에 따라 API를 호출하여 외부 서버와 통신을 해야한다면 필터 함수에 적절한 지연이 필요해보인다.
1~3을 통해 서치바 + 세그먼트 컨트롤로 데이터를 필터링하여 뷰를 다르게 보여준 예시는 아래 영상과 같다.
4. MapKit을 이용해서 지도상에 주소의 위치를 표시하고, UITextField에 PickerView 삽입하여 위치 데이터 선택하기
iOS에서는 자체 내장 패키지로 MapKit을 지원한다. Apple Map이 있기 때문에, 해당 지도에 구현되어 있는 내부 API를 활용하여 앱 위에서 지도를 그려갈 수 있다. VC에서는 위에서 계속 해왔던 대로 Delegation을 잘 해주면 된다.
MapKit을 활용하기 위해서는, 다른 어떤 웹 지도와 동일하게 latitude, langitude 값이 필요하다. 위치에 대한 이름도 같이 기본적으로 표시하면 참 좋겠다.
import MapKit class SomeViewController: UIViewController, MKMapViewDelegate { var address: (String, Double, Double) = ("서울역", 37.554921, 126.970345) @IBOutlet var mapview: MKMapView! @IBOutlet var mapPickerTextField: UITextField! let picker = UIPickerView() func configureMapView() { mapview.delegate = self setAddressOnMap(address, map: mapview) } func setAddressOnMap(_ addressData: (String, Double, Double), map: MKMapView) { let annotation = MKPointAnnotation() map.setRegion( MKCoordinateRegion( center: CLLocationCoordinate2D(latitude: addressData.1, longitude: addressData.2), latitudinalMeters: 100, longitudinalMeters: 100) , animated: true) annotation.coordinate = CLLocationCoordinate2D(latitude: addressData.1, longitude: addressData.2) annotation.title = addressData.0 map.addAnnotation(annotation) } }
MKMapViewDelgete 프로토콜에 내장된 setRegion API를 이용해서 위도, 경도, 주소의 이름 값으로 지도에 위치를 찾고, 반경 얼마만큼으로 보여줄 지를 결정할 수 있다.
이 API를 이용해서 주소값을 알고 있으면 동일 지도의 위치로 이동시켜주는 간단한 메서드를 작성해서 이용했다. setAddressOnMap
위치 이름, 위도, 경도 순서대로 데이터를 관리하면 좋을 것 같아서 Tuple 타입을 이용했다. 사실 데이터의 명시성을 더 높이기 위해서는 구조체의 인스턴스를 이용하는게 더 좋았지 싶다.
MKPointAnnotation 인스턴스를 이용해서 해당하는 주소 포인트에 Pin (Apple이 만들어주는)을 꼽을수도 있다.
setAddressOnMap 메서드를 PickerView 선택 액션에서 호출될 수 있도록 코드를 작성하면 딱 깔끔할 것 같다.
class SomeViewController { var mapDatas: [(String, Double, Double)] = [..] @IBOutlet var mapPickerTextField: UITextField! let picker = UIPickerView() func viewDidLoad() { .. // 관련 권한을 위임해준다. picker.delegate = self mapPickerTextField.inputView = picker } } // TextField 위에 UIPicker를 올려서 필드가 터치되면 피커가 bottom-up 되게 하려고 한다. extension SomeViewController: UIPickerViewDelegate, UIPickerViewDataSource { // PickerView에 몇 개의 휠 컴포넌트가 있을지 결정한다. func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } // 휠 컴포넌트마다 몇 개의 데이터를 보여줄 지 결정한다. func pickerView( _ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return mapDatas.count } // 컴포넌트에서 어떤 데이터를 보여줄 지 결정한다. func pickerView( _ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return mapDatas[row].0 } func pickerView( _ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { // 마치 텍스트 필드에 선택 값이 반영된 것처럼 보여주고 mapPickerTextField.text = mapDatas[row].0 // 데이터에 해당하는 값을 맵핑하고 present = (mapDatas[row].0, mapDatas[row].1, mapDatas[row].2) // 지도에서 보여주고 setAddressOnMap(present) // 피커를 내려준다. view.endEditing(true) } }
UITextField에 UIPickerView를 InputView로 넣어준다. 그리고, 텍스트 필드의 UI를 마치 버튼인 것처럼 꾸며주면 그럴싸한 UI가 된다.
코드로 살펴보면 정말 별거 아닌 기능인데, 생각보다 UX적으로 신경써야 할 것이 많았던 것 같다.
💡
iOS 학습을 시작한지 2~3주차 시점에 스토리보드와 여러 View 클래스들을 이용해서 어느정도 동작하는 다양한 뷰를 반복적으로 만들어 보면서 확실히 개발 매커니즘에 조금은 익숙해질 수 있었다.
현재는 코드베이스로 UIKit 기반의 앱을 만드는 연습에 매진하고 있다. SB에서 코드베이스로 넘어온다고 코드의 대격변이 일어나지는 않기 때문에, 이번에 기록한 것들이 좋은 자양분이 될 것 같다.
Ha
Subscribe to 'hankyeol'
Welcome to 'hankyeol'!
By subscribing to my site, you'll be the first to receive notifications and emails about the latest updates, including new posts.
Join SlashPage and subscribe to 'hankyeol'!
Subscribe
👍
한결
[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 등을 통해서 시스템 설정에서 위치 서비스를 켜달라는 식으로 사용자의 선제 액션을 유도할 수 있다. (혹은 이동시킬 수도 있겠다.)
한결
[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을 보여줄 때도 있다. 애플에서는 알림에 대한 유저 동의 여부를 강하게 체크한다. 무분별하게 알림을 내뱉는 앱은 앱의 사용 경험을 넘어서 아이폰의 사용 경험 자체를 헤칠 수 있기 때문에 애플에서 권한 획득을 중요하게 생각하는 것 같다. 커머스나 앱 자체로 비즈니스 가치가 발생하는 서비스들은 사용자에게 알림 권한을 획득하기 위해서 노력한다. 앱이 종료된 시점에도 우리 서비스가 있고, 우리 서비스를 통해 새로운 이점을 얻을 수 있다는 사실을 알림을 통해 간편하게 전달할 수 있기 때문이다.