GameplayKit으로 상태표시 UI 쉽게 만들기

Soojin Ro
GameplayKit 으로
상태표시 UI 쉽게 만들기
노수진 · 네이버웹툰
contact@soojin.ro
샘플코드 : https://github.com/nsoojin/VoiceControlSample-iOS
샘플코드 : https://github.com/nsoojin/VoiceControlSample-iOS
상태 UI
attending
detecting listening processing
reporting
3가지 정의 요소
- 상태 (a list of states)
- 변화의 조건 (transition)
- 초기 상태 (initial state)
유한한 개수의 상태 중에서 한 번에 오로지 하나의 상태만을 가질 수 있고,
어떠한 사건에 의해 한 상태에서 다른 상태로 변화할 수 있다.
Finite-State Machines 유한 상태 기계
GameplayKit
Randomization
Entities and Components
State Machines
The Minmax Strategist
Pathfinding
Agents, Goals, Behaviors
Rule Systems
GameplayKit
State Machines
Use this architecture to untangle complex procedural code in your gameplay designs.
State MachinesGameplayKit
GKState
- 상속 받아서, 오버라이드를 통해 상태별 동작과 전환(transition)의 조건을 정의
- State에 (1) 진입할 때, (2) 탈출할 때, 혹은 (3) State가 지속되는 동안 주기적으로 업데이트
GKStateMachine
- [GKState] 를 파라미터로 넘겨서 생성
- 생성 후 초기 상태 지정
UI 요구사항 분석
Core Animation Lottie
animationView(Lottie)
dotsContainerView(CoreAnimation)
Core Animation
internal final class DetectingState: VoiceState {
private func bounce(_ dot: UIView) {
let delayTime = 0.2 * Double(dot.tag - 1)
let bounce = CAKeyframeAnimation(keyPath: “position.y”)
bounce.beginTime = CACurrentMediaTime() + delayTime
bounce.values = [0, -6, 0, 6, 0]
bounce.keyTimes = [0, 0.25, 0.5, 0.75, 1.0]
bounce.timingFunctions = [CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut),
CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn),
CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut),
CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)]
bounce.duration = 1.0
bounce.isAdditive = true
bounce.repeatCount = .infinity
dot.layer.add(bounce, forKey: DetectingState.animationKey)
}
}
Core Animation
internal final class ListeningState: VoiceState {
private func amplify(_ dot: UIView) {
let noise = random(min: 0, max: 0.3)
let animation = CASpringAnimation(keyPath: "bounds.size.height")
animation.beginTime = CACurrentMediaTime()
animation.toValue = dot.bounds.size.height * (regularized(magnitude) + noise)
animation.initialVelocity = -15
animation.damping = 7
animation.stiffness = 750
animation.autoreverses = true
animation.delegate = self
animation.isRemovedOnCompletion = true
dot.layer.add(animation, forKey: ListeningState.animationKey)
}
}
Lottie
internal final class ProcessingState: VoiceState {
private func animateProcessing() {
stateView.animationView.currentProgress = 0.16
stateView.animationView.loopMode = .loop
stateView.animationView.play(fromProgress: 0.16, toProgress: 0.32)
}
}
internal final class ReportingState: VoiceState {
private func animateReporting() {
stateView.animationView.currentProgress = 0.32
stateView.animationView.loopMode = .loop
stateView.animationView.play(fromProgress: 0.32, toProgress: 0.52)
}
}
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 조건
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로
공유 자원 및 반복 로직을 관리
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
진입할 수 있는 상태
초기 상태 지정
세팅 완료!
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)
... 생략 ...
}
}
}
전부 합치기
class VoiceStateView: UIView
Jarvis(가칭)
class VoiceControl: UIControl
👋
GameplayKit으로 상태표시 UI 쉽게 만들기
희망 사항
- 상태뷰는 항상 view hierarchy 최상단에 위치
- 도중에도 쉽게 dismiss / cancel
- 어느 화면에나 쉽게 컨트롤 추가
GameplayKit으로 상태표시 UI 쉽게 만들기
UIResponder
var inputView: UIView?
var inputAccessoryView: UIView?
UIResponder
var inputView: UIView?
var inputAccessoryView: UIView?
var inputAccessoryView: UIView? { get }
inputView에 악세서리 뷰를 덧붙이고 싶을 때 사용한다.
인스턴스가 first responder가 되면 시스템이 이 뷰를 input view에 붙인 후 화면에 표시.
inputView가 nil이어도 악세서리 뷰는 표시됨 ✨
장점
- 애니메이션 : 네이티브 키보드처럼 show & hide
- 레이아웃 : Safe Area 대응 용이
- 뷰 계층 : os가 별도 UIWindow로 관리
- Responder chain : becomeFirstResponder(), resignFirstResponder()
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
References
https://github.com/nsoojin/VoiceControlSample-iOS
https://developer.apple.com/documentation/gameplaykit/gkstatemachine
https://developer.apple.com/documentation/uikit/uiresponder
https://developer.apple.com/library/archive/documentation/General/Conceptual/GameplayKit_Guide/StateMachine.html#//apple_ref/doc/uid/TP40015172-CH7
https://en.wikipedia.org/wiki/Finite-state_machine
고맙습니다.
1 of 24

Recommended

Favorite moment in my life by
Favorite moment in my lifeFavorite moment in my life
Favorite moment in my lifejairobaiano
1.5K views12 slides
Psalm 70 commentary by
Psalm 70 commentaryPsalm 70 commentary
Psalm 70 commentaryGLENN PEASE
536 views32 slides
PRESENT PERFECT. USE OF ENGLISH F.C.E. by
PRESENT PERFECT. USE OF ENGLISH F.C.E.PRESENT PERFECT. USE OF ENGLISH F.C.E.
PRESENT PERFECT. USE OF ENGLISH F.C.E.Javier Martos
5K views11 slides
SAP Tables and entries.pdf by
SAP Tables and entries.pdfSAP Tables and entries.pdf
SAP Tables and entries.pdfabilash86
150 views35 slides
Wiosenne prace plastyczne- propozycje by
Wiosenne prace plastyczne- propozycjeWiosenne prace plastyczne- propozycje
Wiosenne prace plastyczne- propozycjeLauraRozlach
455 views12 slides
Spanish gustar by
Spanish gustar Spanish gustar
Spanish gustar Stanley Garland
2.3K views15 slides

More Related Content

What's hot

Parts of the house by
Parts of the houseParts of the house
Parts of the houseAlexander Arcila
8.5K views7 slides
Expressing regrets by
Expressing regretsExpressing regrets
Expressing regretsAna Mena
7.8K views10 slides
My Typical Day by
My Typical DayMy Typical Day
My Typical Dayguest1a61a2
3.2K views21 slides
Pet question 1 2 by
Pet question 1 2Pet question 1 2
Pet question 1 2Jess Erol
311 views2 slides
Harjoittele suomen puhekieltä by
Harjoittele suomen puhekieltäHarjoittele suomen puhekieltä
Harjoittele suomen puhekieltäRitva Tammi
3K views21 slides

Similar to GameplayKit으로 상태표시 UI 쉽게 만들기

Unity3D Scripting: State Machine by
Unity3D Scripting: State MachineUnity3D Scripting: State Machine
Unity3D Scripting: State MachineSperasoft
6.5K views24 slides
Introduction to State Machines by
Introduction to State MachinesIntroduction to State Machines
Introduction to State Machinescodeofficer
3.7K views12 slides
Flutter by
FlutterFlutter
FlutterDave Chao
368 views51 slides
An Introduction To CQRS by
An Introduction To CQRSAn Introduction To CQRS
An Introduction To CQRSNeil Robbins
4.4K views68 slides
Nikolay Tsyb (HYS Enterprise) "Immortal system as a piece of cake." by
Nikolay Tsyb (HYS Enterprise) "Immortal system as a piece of cake."Nikolay Tsyb (HYS Enterprise) "Immortal system as a piece of cake."
Nikolay Tsyb (HYS Enterprise) "Immortal system as a piece of cake."HYS Enterprise
69 views100 slides
Behavioral Design Patterns by
Behavioral Design PatternsBehavioral Design Patterns
Behavioral Design PatternsLidan Hifi
2.7K views70 slides

Similar to GameplayKit으로 상태표시 UI 쉽게 만들기(20)

Unity3D Scripting: State Machine by Sperasoft
Unity3D Scripting: State MachineUnity3D Scripting: State Machine
Unity3D Scripting: State Machine
Sperasoft6.5K views
Introduction to State Machines by codeofficer
Introduction to State MachinesIntroduction to State Machines
Introduction to State Machines
codeofficer3.7K views
An Introduction To CQRS by Neil Robbins
An Introduction To CQRSAn Introduction To CQRS
An Introduction To CQRS
Neil Robbins4.4K views
Nikolay Tsyb (HYS Enterprise) "Immortal system as a piece of cake." by HYS Enterprise
Nikolay Tsyb (HYS Enterprise) "Immortal system as a piece of cake."Nikolay Tsyb (HYS Enterprise) "Immortal system as a piece of cake."
Nikolay Tsyb (HYS Enterprise) "Immortal system as a piece of cake."
HYS Enterprise69 views
Behavioral Design Patterns by Lidan Hifi
Behavioral Design PatternsBehavioral Design Patterns
Behavioral Design Patterns
Lidan Hifi2.7K views
Petri Niemi Qt Advanced Part 2 by NokiaAppForum
Petri Niemi Qt Advanced Part 2Petri Niemi Qt Advanced Part 2
Petri Niemi Qt Advanced Part 2
NokiaAppForum2.4K views
Building Testable Reactive Apps with MVI by James Shvarts
Building Testable Reactive Apps with MVIBuilding Testable Reactive Apps with MVI
Building Testable Reactive Apps with MVI
James Shvarts340 views
Jetpack Compose untuk UI Masa Depan Bagian 2 - Sidiq Permana by DicodingEvent
Jetpack Compose untuk UI Masa Depan Bagian 2 - Sidiq PermanaJetpack Compose untuk UI Masa Depan Bagian 2 - Sidiq Permana
Jetpack Compose untuk UI Masa Depan Bagian 2 - Sidiq Permana
DicodingEvent165 views
Larena3 0架构与关键技术 by hik_lhz
Larena3 0架构与关键技术Larena3 0架构与关键技术
Larena3 0架构与关键技术
hik_lhz383 views
5. state diagrams by APU
5. state diagrams5. state diagrams
5. state diagrams
APU2.8K views
5.state diagrams by APU
5.state diagrams5.state diagrams
5.state diagrams
APU44K views
GDSC NCU Flutter by NCUDSC
GDSC NCU FlutterGDSC NCU Flutter
GDSC NCU Flutter
NCUDSC114 views
Java Design Patterns: The State Pattern by Antony Quinn
Java Design Patterns: The State PatternJava Design Patterns: The State Pattern
Java Design Patterns: The State Pattern
Antony Quinn2.4K views
Architecting Single Activity Applications (With or Without Fragments) by Gabor Varadi
Architecting Single Activity Applications (With or Without Fragments)Architecting Single Activity Applications (With or Without Fragments)
Architecting Single Activity Applications (With or Without Fragments)
Gabor Varadi1.8K views

Recently uploaded

Upskilling the Evolving Workforce with Digital Fluency for Tomorrow's Challen... by
Upskilling the Evolving Workforce with Digital Fluency for Tomorrow's Challen...Upskilling the Evolving Workforce with Digital Fluency for Tomorrow's Challen...
Upskilling the Evolving Workforce with Digital Fluency for Tomorrow's Challen...NUS-ISS
28 views70 slides
Top 10 Strategic Technologies in 2024: AI and Automation by
Top 10 Strategic Technologies in 2024: AI and AutomationTop 10 Strategic Technologies in 2024: AI and Automation
Top 10 Strategic Technologies in 2024: AI and AutomationAutomationEdge Technologies
14 views14 slides
[2023] Putting the R! in R&D.pdf by
[2023] Putting the R! in R&D.pdf[2023] Putting the R! in R&D.pdf
[2023] Putting the R! in R&D.pdfEleanor McHugh
38 views127 slides
Melek BEN MAHMOUD.pdf by
Melek BEN MAHMOUD.pdfMelek BEN MAHMOUD.pdf
Melek BEN MAHMOUD.pdfMelekBenMahmoud
14 views1 slide
Voice Logger - Telephony Integration Solution at Aegis by
Voice Logger - Telephony Integration Solution at AegisVoice Logger - Telephony Integration Solution at Aegis
Voice Logger - Telephony Integration Solution at AegisNirmal Sharma
17 views1 slide
Perth MeetUp November 2023 by
Perth MeetUp November 2023 Perth MeetUp November 2023
Perth MeetUp November 2023 Michael Price
15 views44 slides

Recently uploaded(20)

Upskilling the Evolving Workforce with Digital Fluency for Tomorrow's Challen... by NUS-ISS
Upskilling the Evolving Workforce with Digital Fluency for Tomorrow's Challen...Upskilling the Evolving Workforce with Digital Fluency for Tomorrow's Challen...
Upskilling the Evolving Workforce with Digital Fluency for Tomorrow's Challen...
NUS-ISS28 views
[2023] Putting the R! in R&D.pdf by Eleanor McHugh
[2023] Putting the R! in R&D.pdf[2023] Putting the R! in R&D.pdf
[2023] Putting the R! in R&D.pdf
Eleanor McHugh38 views
Voice Logger - Telephony Integration Solution at Aegis by Nirmal Sharma
Voice Logger - Telephony Integration Solution at AegisVoice Logger - Telephony Integration Solution at Aegis
Voice Logger - Telephony Integration Solution at Aegis
Nirmal Sharma17 views
Perth MeetUp November 2023 by Michael Price
Perth MeetUp November 2023 Perth MeetUp November 2023
Perth MeetUp November 2023
Michael Price15 views
SAP Automation Using Bar Code and FIORI.pdf by Virendra Rai, PMP
SAP Automation Using Bar Code and FIORI.pdfSAP Automation Using Bar Code and FIORI.pdf
SAP Automation Using Bar Code and FIORI.pdf
RADIUS-Omnichannel Interaction System by RADIUS
RADIUS-Omnichannel Interaction SystemRADIUS-Omnichannel Interaction System
RADIUS-Omnichannel Interaction System
RADIUS15 views
The Importance of Cybersecurity for Digital Transformation by NUS-ISS
The Importance of Cybersecurity for Digital TransformationThe Importance of Cybersecurity for Digital Transformation
The Importance of Cybersecurity for Digital Transformation
NUS-ISS27 views
Web Dev - 1 PPT.pdf by gdsczhcet
Web Dev - 1 PPT.pdfWeb Dev - 1 PPT.pdf
Web Dev - 1 PPT.pdf
gdsczhcet55 views
Combining Orchestration and Choreography for a Clean Architecture by ThomasHeinrichs1
Combining Orchestration and Choreography for a Clean ArchitectureCombining Orchestration and Choreography for a Clean Architecture
Combining Orchestration and Choreography for a Clean Architecture
ThomasHeinrichs169 views
Attacking IoT Devices from a Web Perspective - Linux Day by Simone Onofri
Attacking IoT Devices from a Web Perspective - Linux Day Attacking IoT Devices from a Web Perspective - Linux Day
Attacking IoT Devices from a Web Perspective - Linux Day
Simone Onofri15 views
Transcript: The Details of Description Techniques tips and tangents on altern... by BookNet Canada
Transcript: The Details of Description Techniques tips and tangents on altern...Transcript: The Details of Description Techniques tips and tangents on altern...
Transcript: The Details of Description Techniques tips and tangents on altern...
BookNet Canada130 views
Special_edition_innovator_2023.pdf by WillDavies22
Special_edition_innovator_2023.pdfSpecial_edition_innovator_2023.pdf
Special_edition_innovator_2023.pdf
WillDavies2216 views
Future of Learning - Khoong Chan Meng by NUS-ISS
Future of Learning - Khoong Chan MengFuture of Learning - Khoong Chan Meng
Future of Learning - Khoong Chan Meng
NUS-ISS33 views

GameplayKit으로 상태표시 UI 쉽게 만들기

  • 1. GameplayKit 으로 상태표시 UI 쉽게 만들기 노수진 · 네이버웹툰 contact@soojin.ro 샘플코드 : https://github.com/nsoojin/VoiceControlSample-iOS
  • 4. 3가지 정의 요소 - 상태 (a list of states) - 변화의 조건 (transition) - 초기 상태 (initial state) 유한한 개수의 상태 중에서 한 번에 오로지 하나의 상태만을 가질 수 있고, 어떠한 사건에 의해 한 상태에서 다른 상태로 변화할 수 있다. Finite-State Machines 유한 상태 기계
  • 5. GameplayKit Randomization Entities and Components State Machines The Minmax Strategist Pathfinding Agents, Goals, Behaviors Rule Systems
  • 6. GameplayKit State Machines Use this architecture to untangle complex procedural code in your gameplay designs.
  • 7. State MachinesGameplayKit GKState - 상속 받아서, 오버라이드를 통해 상태별 동작과 전환(transition)의 조건을 정의 - State에 (1) 진입할 때, (2) 탈출할 때, 혹은 (3) State가 지속되는 동안 주기적으로 업데이트 GKStateMachine - [GKState] 를 파라미터로 넘겨서 생성 - 생성 후 초기 상태 지정
  • 8. UI 요구사항 분석 Core Animation Lottie animationView(Lottie) dotsContainerView(CoreAnimation)
  • 9. Core Animation internal final class DetectingState: VoiceState { private func bounce(_ dot: UIView) { let delayTime = 0.2 * Double(dot.tag - 1) let bounce = CAKeyframeAnimation(keyPath: “position.y”) bounce.beginTime = CACurrentMediaTime() + delayTime bounce.values = [0, -6, 0, 6, 0] bounce.keyTimes = [0, 0.25, 0.5, 0.75, 1.0] bounce.timingFunctions = [CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut), CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn), CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut), CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)] bounce.duration = 1.0 bounce.isAdditive = true bounce.repeatCount = .infinity dot.layer.add(bounce, forKey: DetectingState.animationKey) } }
  • 10. Core Animation internal final class ListeningState: VoiceState { private func amplify(_ dot: UIView) { let noise = random(min: 0, max: 0.3) let animation = CASpringAnimation(keyPath: "bounds.size.height") animation.beginTime = CACurrentMediaTime() animation.toValue = dot.bounds.size.height * (regularized(magnitude) + noise) animation.initialVelocity = -15 animation.damping = 7 animation.stiffness = 750 animation.autoreverses = true animation.delegate = self animation.isRemovedOnCompletion = true dot.layer.add(animation, forKey: ListeningState.animationKey) } }
  • 11. Lottie internal final class ProcessingState: VoiceState { private func animateProcessing() { stateView.animationView.currentProgress = 0.16 stateView.animationView.loopMode = .loop stateView.animationView.play(fromProgress: 0.16, toProgress: 0.32) } } internal final class ReportingState: VoiceState { private func animateReporting() { stateView.animationView.currentProgress = 0.32 stateView.animationView.loopMode = .loop stateView.animationView.play(fromProgress: 0.32, toProgress: 0.52) } }
  • 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) ... 생략 ... } } }
  • 16. 전부 합치기 class VoiceStateView: UIView Jarvis(가칭) class VoiceControl: UIControl 👋
  • 18. 희망 사항 - 상태뷰는 항상 view hierarchy 최상단에 위치 - 도중에도 쉽게 dismiss / cancel - 어느 화면에나 쉽게 컨트롤 추가
  • 20. UIResponder var inputView: UIView? var inputAccessoryView: UIView?
  • 21. UIResponder var inputView: UIView? var inputAccessoryView: UIView?
  • 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