Push Notification 사용 권한 요청 1. UNUserNotificationCenterDelegate 채택 2. Notification 권한 부여 요청 3. APNS(Apple Push Notification Service)에 등록
UNUserNotificationCenter란?
앱과 앱 익스텐션에 대한 알림 관련 작업을 관리하기 위한 센트럴 오브젝트. 공유된 UNUserNotificationCenter 객체를 사용하여 앱과 앱 확장의 모든 알림 관련 동작을 관리한다.
Push Notification 사용 권한 요청
알림에 응답하여 알럿을 표시하거나, 소리를 재생하거나, 앱 아이콘에 배지를 달 수 있는 권한을 요청하는 방법에 대해 알아보자.
1. UNUserNotificationCenterDelegate 채택
알림 관련 작업을 처리하려면 UNUserNotificationCenterDelegate 프로토콜을 채택하는 객체를 생성하여 이 객체의 델리게이트 프로퍼티에 할당한다. 이 작업은 해당 델리게이트와 상호작용을 할 수 있는 다른 작업을 수행 하기 전에 이 델리게이트 프로퍼티에 객체를 항상 할당하자.
승인을 요청하려면, UNUserNotificationCenter 인스턴스를 가져와서 requestAuthorization(options:completionHandler:) 을 호출하자. (alert, sound, badge 타입 요청 가능)
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if let error = error {
// Handle the error here.
}
// Enable or disable features based on the authorization.
}
3. APNS(Apple Push Notification Service)에 원격 알림을 수신하도록 등록
Apple Push Notification Service에 등록 프로세스를 시작하려면 아래 메소드를 호출하자. (Main Thread에서 진행해야함.)
Modifying Value Types from Within Instance Methods
기본적으로, Value 타입의 property는 인스턴스 메소드 안에서 수정될 수 없음. (여기서말하는 value 타입은 struct, enum)
mutating 키워드 : 메소드에서 property의 수정이 가능하도록 정의하기 위해 추가
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// Prints "The point is now at (3.0, 4.0)"
2. Type Method
타입 자체에서 호출되는 메소드
func 키워드 앞에 static 키워드 작성
Objective-C에서는 Class에서만 type-level method를 정의할 수 있었다. Swift에서는 모든 클래스, 구조체, 열거형에서 정의 가능
class SomeClass {
class func someTypeMethod() {
// type method implementation goes here
}
}
SomeClass.someTypeMethod()
TheStringandStaticStringtypes conform to theExpressibleByStringLiteralprotocol. You can initialize a variable or constant of either of these types using a string literal of any length.
Conforming to ExpressibleByStringLiteral
To addExpressibleByStringLiteralconformance to your custom type, implement the required initializer.
Types that conform to theCustomStringConvertibleprotocol can provide their own representation to be used when converting an instance to a string. TheString(describing:)initializer is the preferred way to convert an instance ofanytype to a string. If the passed instance conforms toCustomStringConvertible, theString(describing:)initializer and theprint(_:)function use the instance’s customdescriptionproperty.
Accessing a type’sdescriptionproperty directly or usingCustomStringConvertibleas a generic constraint is discouraged.
Conforming to the CustomStringConvertible Protocol
AddCustomStringConvertibleconformance to your custom types by defining adescriptionproperty.
For example, this customPointstruct uses the default representation supplied by the standard library:
Example
enum CompassPoint {
case north
case south
case custom(String)
}
extension CompassPoint: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
switch value {
case "north":
self = .north
case "south":
self = .south
default:
self = .custom(value)
}
}
}
extension CompassPoint: CustomStringConvertible {
public var description: String {
switch self {
case .north:
return "north"
case .south:
return "south"
case .custom(let type):
return type
}
}
}
NOTE ReatorKit에서 말하는 액션 스트림과 상태 스트림은 아래 Reactor 클래스의 구현부를 보면 알 수 있다. 뷰에 있는 UI 컴포넌트에 rx를 이용하여 해당 Observable을 reactor의 action이 구독한다. 그리고, reactor.state인 Observable을 뷰의 UI 컴포넌트가 구독한다.
이와 같이 단방향으로 액션 스트림과 상태스트림으로 Action과 State 데이터가 이동한다.
3. Reactor = ViewModel
Reactor는 뷰의 상태를 관리하는 UI 독립적인 계층
Reactor의 가장 중요한 역할은 뷰로부터 제어 흐름을 분리하는 것
모든 뷰는 해당 Reacor를 가지고 있으며, 모든 로직을 그 Reactor에 위임한다.
Reactor는 뷰의 의존성이 없기 때문에 테스트하기가 용이하다.
리엑터를 정의하기 위해서는 Reactor 프로토콜을 준수하자!
세개 타입 Action, Mutation, State. + initialState 프로퍼티 필수!
Action은 사용자의 인터렉션, State는 뷰의 상태
class ProfileViewReactor: Reactor {
// represent user actions (유저 액션을 나타냄)
enum Action {
case refreshFollowingStatus(Int)
case follow(Int)
}
// represent state changes (상태 변화를 나타냄)
enum Mutation {
case setFollowing(Bool)
}
// represents the current view state (현재 뷰 상태를 나타냄)
struct State {
var isFollowing: Bool = false
}
let initialState: State = State()
}
(중요)
리엑터는 2단계로 액션 스트림을 상태 스트림으로 변경한다:mutate()와reduce().
비동기 오퍼레이션 또는 API 호출과 같은 모든 사이드 이펙트는 이 메소드 안에서 수행된다
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case let .refreshFollowingStatus(userID): // receive an action
return UserAPI.isFollowing(userID) // create an API stream
.map { (isFollowing: Bool) -> Mutation in
return Mutation.setFollowing(isFollowing) // convert to Mutation stream
}
case let .follow(userID):
return UserAPI.follow()
.map { _ -> Mutation in
return Mutation.setFollowing(true)
}
}
}
2) reduce()
기존 State과 Mutation으로부터 새로운 State를 생성
func reduce(state: State, mutation: Mutation) -> State
이 메소드는 순수함수(pure function)이므로 동기적으로 새로운 State를 리턴해야한다. 이 함수안에서 어떤 side effect들을 수행하지 말자.
func reduce(state: State, mutation: Mutation) -> State {
var state = state // create a copy of the old state
switch mutation {
case let .setFollowing(isFollowing):
state.isFollowing = isFollowing // manipulate the state, create a new state
return state // return the new state
}
}
Reactor의 일반적 Action → Mutation → State 흐름에 global state는 없으므로, global state 변경을 위해서 transform(muation:)을 사용해야 한다.
예) 현재 인증된 유저들을 저장하는 global BehaviorSubject가 있다고 가정. 만약 currentUser가 변경되었을 때 Mutation.setUser(User?)를 방출하기를 원한다면 아래와 같이 작성. 그리고 그 mutation은 뷰가 액션을 리엑터에게 보낼 때 각 시간에 방출될 것이다.
var currentUser: BehaviorSubject<User> // global state
func transform(mutation: Observable<Mutation>) -> Observable<Mutation> {
return Observable.merge(mutation, currentUser.map(Mutation.setUser))
}
View Communication
ReactorKit은 global app state를 정의하고 있지 않으므로, global state를 관리하기 위해서 BehaviorSubject, PublishSubject, reactor를 사용할 수 있다.
여러개의 뷰들을 커뮤니케이션하기 위해서는 callback 클로저나 delegate 패턴을 사용하는 것이 일반적.
결론 iOS는 디바이스 크기 별로 할당되는 content area의 Size classes의 값을 토대로 다이나믹하게 layout 조정을 한다. - Size classes: 디바이스 별로 시스템이 두개로 정의함 (compact, regular)
Adaptivity and Layout
In iOS, interface elements and layouts can be configured to automatically change shape and size on different devices, during multitasking on iPad, in split view, when the screen is rotated, and more.
It’s essential that you design an adaptable interface that provides a great experience in any environment.
Auto Layout
Auto Layout is a development tool for constructing adaptive interfaces.
Using Auto Layout, you can define rules (known asconstraints) that govern the content in your app.
Auto Layout automatically readjusts layouts according to the specified constraints when certain environmental variations (known as traits) are detected.
Layout Guides and Safe Area
Layout guides define rectangular regions that don’t actually appear visibly onscreen, but aid with the positioning, alignment, and spacing of content. The system includes predefined layout guides that make it easy to apply standard margins around content and restrict the width of text for optimal readability. You can also define custom layout guides.
Size Classes
In iOS, Size Classes are groups of screen sizes that are applied to the width and height of the device screen.
The system defines two size classes. The two Size Classes that exist currently are Compact and Regular. - TheCompact Size Classrefers to a constrained space. It is denoted in Xcode as wC (Compact width) and hC (Compact height). - TheRegular Size Classrefers to a non-constrained space. It is denoted in Xcode as wR (Regular width) and hR (Regular height).
As with other environmental variations, iOS dynamically makes layout adjustments based on the size classes of a content area. (For example, when the vertical size class changes from compact height to regular height, perhaps because the user rotated the device from landscape to portrait orientation, tab bars may become taller.)