4. 3가지 정의 요소
- 상태 (a list of states)
- 변화의 조건 (transition)
- 초기 상태 (initial state)
유한한 개수의 상태 중에서 한 번에 오로지 하나의 상태만을 가질 수 있고,
어떠한 사건에 의해 한 상태에서 다른 상태로 변화할 수 있다.
Finite-State Machines 유한 상태 기계
7. State MachinesGameplayKit
GKState
- 상속 받아서, 오버라이드를 통해 상태별 동작과 전환(transition)의 조건을 정의
- State에 (1) 진입할 때, (2) 탈출할 때, 혹은 (3) State가 지속되는 동안 주기적으로 업데이트
GKStateMachine
- [GKState] 를 파라미터로 넘겨서 생성
- 생성 후 초기 상태 지정
8. UI 요구사항 분석
Core Animation Lottie
animationView(Lottie)
dotsContainerView(CoreAnimation)
12. GKState
internal final class DetectingState: VoiceState {
override func didEnter(from previousState: GKState?) {
super.didEnter(from: previousState)
animateBounce()
}
override func willExit(to nextState: GKState) {
super.willExit(to: nextState)
removeAnimation()
}
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
switch stateClass {
case is AttendingState.Type, is ListeningState.Type:
return true
default:
return false
}
}
}
(1) State 진입 시
(2) State 탈출 시
(3) Transition 조건
13. internal class VoiceState: GKState {
unowned let stateView: VoiceStateView
init(statusView: VoiceStateView) {
self.stateView = statusView
}
override func didEnter(from previousState: GKState?) {
switch self {
case is AttendingState, is ListeningState, is DetectingState:
stateView.animationView.isHidden = true
stateView.dotsContainerView.isHidden = false
case is ProcessingState, is ReportingState:
stateView.animationView.isHidden = false
stateView.dotsContainerView.isHidden = true
default:
break
}
}
}
GKState Best Practice
⚠ 바로 GKState을 상속받지 말고
공통 Superclass State로
공유 자원 및 반복 로직을 관리
14. internal class VoiceStateView: UIView {
private lazy var stateMachine: GKStateMachine = {
return GKStateMachine(states: [
AttendingState(statusView: self),
DetectingState(statusView: self),
ListeningState(statusView: self),
ProcessingState(statusView: self),
ReportingState(statusView: self)
])
}()
override func didMoveToWindow() {
state = .attending
stateMachine.enter(AttendingState.self)
}
}
GKStateMachine
진입할 수 있는 상태
초기 상태 지정
세팅 완료!
15. internal final class VoiceStateView: UIView {
@discardableResult
func setState(_ state: State) -> Bool {
let isNextStateValid = stateMachine.canEnterState(state.classType())
if isNextStateValid {
self.state = state
stateMachine.enter(state.classType())
}
return isNextStateValid
}
}
상태 변경
extension VoiceControl: JarvisDelegate {
func jarvis(_ jarvis: Jarvis, didChangeState state: JarvisState) {
switch state {
case .detecting:
voiceAgentView?.transcription = listeningStateDescription
voiceAgentView?.stateView?.setState(.detecting)
... 생략 ...
}
}
}
22. var inputAccessoryView: UIView? { get }
inputView에 악세서리 뷰를 덧붙이고 싶을 때 사용한다.
인스턴스가 first responder가 되면 시스템이 이 뷰를 input view에 붙인 후 화면에 표시.
inputView가 nil이어도 악세서리 뷰는 표시됨 ✨
장점
- 애니메이션 : 네이티브 키보드처럼 show & hide
- 레이아웃 : Safe Area 대응 용이
- 뷰 계층 : os가 별도 UIWindow로 관리
- Responder chain : becomeFirstResponder(), resignFirstResponder()
23. resignFirstResponder()becomeFirstResponder()
마지막 총 정리
class VoiceControl: UIControl
override var inputAccessoryView: UIView? {
return voiceStateView
}
private var jarvis: Jarvis
private var voiceStateView: VoiceStateView
class VoiceStateView: UIView
var stateMachine: GKStateMachine