HitTest

이전 포스팅 Responder에 대해서 공부할 때, 잠깐 나왔던 개념이다.

 

정의는

Returns the farthest descendant of the receiver in the view hierarchy (including itself) that contains a specified point
지정된 점(point)을 포함하는 View 계층에서 리시버의 가장 먼 자손을 리턴한다.

라고 한다.

 

메소드이다.

 

point = 리시버의 bounds로 지점된 점

event = 메소드에 대한 호출을 보증하는 이벤트이다. 이벤트 처리코드 외부에서 이 메소드를 호출하는 경우에는 nil을 지정할 수 있다.

 

리턴 타입이 UIView?로 옵셔널이다.. nil이 반환될 수도 있다는 건데 -> 잠시 후에 알아보자

 

Discussion

이 메소드는 SubView의 point(inside:with:)를 호출하여 View 계층을 탐색하고, 어떤 하위 VIew가 터치 이벤트를 받아야 하는지 결정한다.

point 메소드의 파라미터는 hitTest와 같고, 반환타입만 다르다.

  • hitTest = 해당 point를 통해 리시버로부터 가장 먼 SubView를 반환
  • point = 해당 point가 리시버의 Bounds 내에 존재하는가?

point가 true를 반환하면, 하위 View 계층구조는 지정된 point를 포함하는 가장 앞에 있는 View를 찾을 때까지 재귀적으로 찾는다.

point가 false를 반환하면, View 계층 구조의 해당 분기가 무시된다.

(분기? -> hitTest는 Reverse DFS 방식으로 찾는다.)

ex) 

해당 View 계층일 때,

MainView -> View C -> View C.2 -> View C.1 -> View B ... 

순서로 찾게 된다.

 

왜? DFS면 DFS지 Reverse가 붙어?

-> iOS View 체계에서는 SubView가 SuperView보다 위(사용자에게 가까이)에 놓여진다.

-> 같은 SuperView를 가진 형제 View들의 경우에는 index가 늦은 View가 위에 놓여진다.

따라서, Reverse로 탐색할 때, hitTest가 성공한 최하단 SubView = 가장 위에 놓여진 View 가 된다.

 

 

hitTest 메소드의 동작 조건

  • hidden = false
  • alpha > 0.01
  • userInterationEnabled = true

리시버 Bounds 외부에 있는 point는 실제로 리시버 하위 View중 하나에 속하더라도, hit이 아니다.

-> Responder Chain에서의 GreenView와 같은 현상

 

hitTest 구현부

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if (self.hidden || !self.userInteractionEnabled || self.alpha < 0.01 || ![self pointInside:point withEvent:event] || ![self _isAnimatedUserInteractionEnabled]) {
        return nil;
    } else {
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            UIView *hitView = [subview hitTest:[subview convertPoint:point fromView:self] withEvent:event];
            if (hitView) {
                return hitView;
            }
        }
        return self;
    }
}

조건문에 있듯이 hitTest의 동작 조건에 반하는 조건들이 if문에 걸려있다.

 

Why?

왜 hitTest를 알아야 하나?

Responder Chain 포스팅에서도 적었 듯

 

HitTest 테스트 포스팅

http://smnh.me/hit-testing-in-ios/

 

Hit-Testing in iOS

Hit-testing is the process of determining whether a point, such as touch-point, intersects a given graphical object, such as UIView, drawn on the screen. iOS uses hit-testing to determine which UIView is the frontmost view under the user’s finger that sh

smnh.me

 

 

 

 

 

'iOS' 카테고리의 다른 글

[iOS] View LifeCycle  (0) 2021.05.24
[iOS] UIWindowScene / UIWindow / UIView  (0) 2021.05.23
[iOS] Responder / Responder Chain  (0) 2021.05.21
[iOS] State Preserving/Restoring ViewControlle  (0) 2021.05.20
[iOS] App Life Cycle  (0) 2021.05.18

UIResponder

Responder = 응답자

공식 문서에 따르면 '이벤트에 응답하고 처리하기 위한 추상 인터페이스'라고 설명되어 있다.

 

class UIResponder: NSObject

UIResponder의 인스턴스는 UIKit 앱 이벤트 처리 백본을 구성한다.

많은 UIWindow, UIApplication, UIViewController, 모든 UIView 객체들은 리스폰더이다.

 

터치, 모션, 원격제어, 프레스 이벤트 등이 있고, 특정 유형의 이벤트를 처리하려면 응답자가 해당 메서드를 재정의해야 한다.

  • touchesBegan(_:with:)
  • touchesMoved(_:with:)
  • touchesEnded(_:with:)
  • touchesCanceled(_:with:)

의 메소드들을 재정의하여 특정 이벤트를 핸들링할 수 있다.

 

Responder들은 UIEvent 객체를 처리할 수도 있고, input view를 통해 Custom Input을 받아들일 수도 있다.

시스템 키보드가 이 input view의 예시이다.

사용자가 UITextField/UITextView를 탭하면, 해당 View는 first Responder가 되고, input view(키보드)를 띄운다.

비슷하게 Custom input view를 띄울수도 있다.

 

방법 : custom input view를 responder의 inputView 프로퍼티에 할당

 

 

UIResponder Chain

이벤트 핸들링 말고도, UIKit Responder들은 처리되지 않은 이벤트를 상위 Responder에게 전달할 수 있다.

특정 정해진 Responder가 이벤트를 핸들링하지 않는 경우, 해당 Responder는 그 이벤트를 Responder Chain의 다음 객체에게 전달한다.

이벤트(메세지)는 처리될 때까지 계속 Chain을 따라 상위 Responder 객체들로 이동한다.

마지막까지 처리되지 않는 경우, 이벤트를 버린다.

Responder Chain 예시

  • 만약, TextField가 event를 핸들링 하지 않으면, UIKit은 TextField의 부모인 UIView에게 event를 전달하고, 이어서 Window의 RootView에게 보내진다.
  • RootView에서, Responder Chain은 event를 Window로 보내기 전에 방향을 바꿔 RootView를 소유하고 있는 ViewController로 보낸다.
  • 만약, Window가 event를 처리하지 못하면, UIKit은 event를 UIApplication 객체로 전달한다. 그리고 AppDelegate가 UIResponder의 인스턴스이고 Responder Chain의 일부가 아니라면 event를 AppDelegate에게 전달한다.

Event의 First Responder 결정

많은 종류의 이벤트를 처음으로 받는 Responder 객체를 First Responder라고 한다.

First Responder는 대체로 앱이 이벤트를 핸들링하기 가장 적합하다고 간주하는 Responder이다.

앱이 이벤트를 받으면, UIKit이 해당 이벤트를 가장 적합한 Responder 객체인 First Responder에게 전달한다.

 

이벤트를 받기 위해서는, Responder 자신이 First Responder가 될 수 있음을 나타내야 한다.

UIResponder의 Subclass에서 canBecomeFirstResponder 프로퍼티를 오버라이드하여 true를 리턴해야 한다.

 

UIKit은 event 유형에 따라 첫 응답자로 개체를 지정한다.

컨트롤은 연관된 타겟 객체와 직접 액션 메세지를 이용해 소통한다.

액션 메세지는 이벤트는 아니지만 Responder Chain을 이용한다.

컨트롤의 타겟 객체가 nil일 경우, UIKit은 First Responder에서 시작하여 적절한 액션 메세지를 구현한 객체를 만날 때까지 Chain을 따라 전달한다.

 

View에 있는 GestureRecognizer의 경우 또한 터치 등을 인식하지 못하면 UIKit은 View로 터치를 보내고, View도 터치를 처리하지 않는다면, Chain을 따라 전달한다.

 

그렇다면 이벤트가 어디서 발생하였고, 어떻게 Responder를 결정할까?

Determining Which Responder Contained a Touch Event

  • UIKit은 hit-test를 사용하여 터치 이벤트가 발생하는 위치를 결정한다.
  • UIKit은 터치 위치를 View 계층에 있는 View 객체들의 Bounds와 비교한다.
  • UIView의 hitTest(_:with:) 메소드는 특정 터치를 포함하는 가장 깊은 SubView를 찾기 위해 View 계층을 따라 이동하고, 이 가장 깊은 SubView가 터치 이벤트의 FirstResponder가 된다.
터치 위치가 뷰의 경계 밖이라면, hitTest(_:with:) 메서드는 해당 뷰와 그 뷰의 모든 서브뷰들을 무시합니다.
결과적으로, 뷰의 clipToBounds 프로퍼티가 false 라면, 그 뷰의 밖에 있는 서브뷰들은 터치를 포함하더라도 반환되지 않습니다.

터치 이벤트가 발생하면, UIKit은 UITouch 객체를 만들고 View와 연결한다. 

터치 위치나 다른 파라미터들이 변경되면, UIKit은 같은 UITouch 객체를 업데이트한다.

변경되지 않는 유일한 프로퍼티는 view이다.

심지어 터치 위치가 원래 View의 바깥으로 이동했더라도, 터치의 view 프로퍼티는 변하지 않는다.

터치가 끝나면, UIKit은 UITouch 객체를 메모리에서 해제한다.

 

Altering the Responder Chain

Responder 객체의 next 프로퍼티를 오버라이드하여 Responder Chain을 변경할 수 있다.

(next를 그대로 사용하면, hitTest했을 때의 상위 View에게 전달되나?)

이 작업을 할 때, next Responder는 오버라이드한 프로퍼티에서 반환하는 객체입니다.

 

많은 UIKit 클래스들은 이미 이 프로퍼티를 오버라이드하여 특정 객체들을 반환하고 있다.

 

UIView

  • 만약 해당 뷰가 ViewController의 RootView라면, next Responder는 UIViewController 객체이다. 
  • 아니라면, next Responder는 해당 뷰의 Super View이다.

 

UIViewController

  • 만약 ViewController의 뷰가 Window의 RootView라면, next Responder는 Window 객체이다.
  • 만약 ViewController가 다른 ViewController에 의해 present된 경우(모달로 띄워진 경우), next Responder는 presenting VC이다.

 

UIWindow

  • 윈도우의 next Responder는 UIApplication 객체이다

 

UIApplication

  • UIApplication 객체의 next Responder는 AppDelegate이다.
  • 하지만 AppDelegate가 UIResponder의 인스턴스이면서 View, ViewController, 또는 앱 객체 자신이 아닐 때만 해당된다.

 

직접 해보면서 이해해보자

 

ViewController에는 View들과 Label, Button, TextField를 넣었다.

 

 

또한 RedView, BlueView, GreenView, YellowView 는 Custom class를 생성하여 똑같은 메소드를 오버라이드하였다.

 

GreenView Touch

 

아니 왜 2개나 올려? 첫번째 사진은 ViewController 만 있는 흰색 부분 클릭한거 아니야? -> 라고 생각할 수 있다..

실제로 둘다 GreenView Bounds 내에 부분을 터치한 것이다.

그렇다면 무엇이 다르길래 Log 또한 다른 것인가?

 

위에서 해당 문구를 다시 보자

터치 위치가 뷰의 경계 밖이라면, hitTest(_:with:) 메서드는 해당 뷰와 그 뷰의 모든 서브뷰들을 무시합니다.
결과적으로, 뷰의 clipToBounds 프로퍼티가 false 라면, 그 뷰의 밖에 있는 서브뷰들은 터치를 포함하더라도 반환되지 않습니다.

터치 위치가 뷰의 경계 밖이라면 해당 뷰와 그 뷰의 모든 서브뷰를 무시한다고 나와있다.

터치 위치가

 

흰색 점일 때, 이는 GreenView의 Bounds 내에는 존재하지만,

BlueView의 Bounds 내에는 존재하지 않는다.

따라서 BlueView와 BlueView의 모든 서브뷰(GreenView)를 무시한다.

 

그렇다면 2번째는 어딜 터치했을까? 당연히 GreenView의 Bounds 내이기도 하면서 BlueView BOunds의 내이다.

 

마찬가지로 Yellow도 같은 원리로 동작한다.

 

RedView Touch

아니 또 장난치네, Red 뒤에 Green이랑 Yellow View가 있는데 왜 쟤네는 안불려? 라고 할 수도 있지만

뷰 계층을 다시 보자

RedView와 BlueView는 ViewController의 RootView에 붙어있다.

이로써 터치했을 때, 위에서 말했던 것처럼 UI로 보이는 것이 아닌 뷰 계층을 따라서 전달된다는 것을 알 수 있다.

 

Label Touch

Label은 기본적으로 Interaction이 불가능하기 때문에 User Interaction을 true로 설정해줘야 한다.

 

Button Touch

버튼은 super.touchesBegan 메소드를 사용 시, Chaining이 되지 않는다.

next?.touchesBegan 메소드를 사용시 Chaining이 정삭적으로 동작한다.

 

 


정리

기본적으로 touches~~ 메소드들은 내부적으로 super.touches~~ 메소드가 작성되어 있다.

따라서, 내가 RedView에서 Touch를 했을 때, ViewController에서 어떠한 동작을 하고 싶다!

하면 touchesBegan 메소드를 오버라이드해서 원하는 조건이 맞으면 원하는 동작을 수행하면 된다.

동작을 구현할 위치(Responder)가 ViewController가 아닌 Window나 AppDelegate여도 된다.

'iOS' 카테고리의 다른 글

[iOS] UIWindowScene / UIWindow / UIView  (0) 2021.05.23
[iOS] HitTest  (0) 2021.05.21
[iOS] State Preserving/Restoring ViewControlle  (0) 2021.05.20
[iOS] App Life Cycle  (0) 2021.05.18
[iOS] TabBar Slide Down/Up Animation  (0) 2021.03.25

ViewController는 State의 복원과 보존에서 중요합니다.

 

상태 보존

앱이 suspend되기 전에 정보를 저장한 후에, 이후에 실행시 앱을 이전 상태로 되돌린다.

 

방법 2가지

  1. iOS 13 이상에서는 NSUserActivity를 사용해서 각 Scene의 상태를 저장
  2. iOS 12 이하에서는 ViewController의 Configuration을 저장

예제) https://developer.apple.com/documentation/uikit/uiviewcontroller/restoring_your_app_s_state

 

Apple Developer Documentation

 

developer.apple.com

 

  • 복원하길 원하는 ViewController에게 restore identifier를 할당
  • iOS에게 앱이 시작되었을 때, 새로운 ViewController 객체의 위치를 어떻게 지정할지 알려준다.
  • VC가 원래 스토리보드 파일에서 로드된 경우, UIKit은 저장된 스토리 보드 정보를 사용해 찾아 만든다.

~ iOS 12

Tagging ViewControllers for Preservation

UIKit은 보존하기 위해 지정한 VC만 보존한다. 각 VC는 restorationIdentifier 속성을 갖고 있고 기본적으로 값은 nil이다.

이 속성을 문자열로 설정하면 UIKit에서 VC와 해당 View를 preserve한다.

복원 식별자는 programitically / storyboard 모두 할당 가능하다.

 

복원 식별자를 할당할 때, VC 계층에 있는 부모 VC 또한 복원 식별자를 갖고 있어야 한다.

복원 처리하는 동안 UIKit은 window의 root VC에서 시작하여 VC 계층을 탐색한다.

계층의 VC가 복원 식별자를 갖지 않는다면, VC와 해당 VC의 자식 VC는 무시된다.

-> A 라는 VC를 복원하고 싶다면, 해당 VC의 계층적으로 연결된 부모 VC들 또한 복원식별자가 필요하다?

 

Choosing Effective Restoration Identifiers

UIKit은 복원 식별자 문자열을 사용하여 나중에 VC를 다시 작성하므로 코드에서 쉽게 식별할 수 있는 문자열을 선택하세요.

UIKit에서 VC 중 하나를 자동으로 만들 수 없는 경우, VC 및 모든 상위 VC의 복원 식별자를 제공하여 VC를 만들것을 요청합니다.

이 식별자 체인은 각 VC를 위한 복원 경로를 대표하고, 요청된 VC가 어떻게 결정되어지는 지에 대한 방법입니다.

복원 경로는 root VC에서 시작하여 요청된 모든 VC를 포함합니다.

 

복원 식별자는 종종 VC의 클래스 이름으로 사용한다 -> (나 역시도 이게 편하다...)

 

모든 VC의 복원 경로는 고유해야 한다. Container VC에 두개의 자식이 있는 경우 Container는 각 자식에게 고유한 복원 식별자를 할당해야 한다.

UIKit의 일부 컨테이너는 자동으로 그들의 자식 VC들의 명확한 차이를 보여주고, 각 자식에 대해 동일한 복원 식별자를 사용할 수 있도록 한다.

ex) UINavigationController 는 탐색 스택의 위치를 기반으로 각 자식에 정보를 추가합니다.

 

Excluding Groups of View Controllers

복원 처리에서 VC의 전체 그룹을 제외하려면 상위 VC의 복원 식별자를 nil로 설정하세요.

 

*이 달린 VC는 복원 식별자를 갖고 있다.

빨간색의 VC는 preserve가 되지 않는다.

좌측의 UINavigationController가 복원 식별자를 갖고 있지 않기 때문에, 아래의 VC들이 복원 식별자를 갖고 있어도 preserve되지 않음

 

 

Preserving a View Controller's Views

뷰들은 뷰와 관계된 추가적인 상태 정보를 가집니다.

하지만 부모 VC는 추가 상태 정보가 없습니다.

ex) ScrollView는 보존하길 원하는 Scroll Offset을 가집니다. VC가 ScrollView의 컨텐츠를 제공하는 역할을 하는 동안 내부 View를 보존해야 한다.

 

View의 상태를 저장하기 위해 다음의 것들을 해야 합니다.

  • View의 유효한 문자열을 View의 restorationIdentifier 속성에 할당해야 합니다.
  • VC에서 사용하는 View 또한 유효한 복원 식별자를 가집니다.
  • TableView와 CollectionView는 UIDataSourceModelAssociation Protocol을 채택하는 DataSource를 할당합니다.

복원 식별자를 View에 할당하여 UIKit에게 보존 아카이브로 View의 상태를 기록해야 한다.

VC가 이후에 복원될 때, UIKit 또한 복원 식별자를 가진 View를 복원한다.

 

 

Restoring View Controllers at Launch Time

앱 실행시, UIKit은 앱을 이전 상태로 복원을 시도합니다.

이때, UIKit은 앱에서 유저 인터페이스를 보존한 VC 객체를 생성하도록 앱에게 요청합니다.

UIKit은 VC를 찾을 때, 다음 순서로 검색합니다.

  • VC가 복원 class를 가졌다면, UIKit은 VC를 제공하기 위해 class에게 요청합니다.
  • VC가 복원 class를 가지지 않았다면, UIKit AppDelegate에게 VC를 제공하라고 요청합니다.
  • VC가 올바른 복원 경로가 이미 존재한다면, UIKit은 이 객체를 사용합니다.
    • 앱을 실행할 때, VC를 생성(code/storyboard)하고 이 VC들은 복원 식별자를 갖고 있고 UIKit은 이 복원 경로를 기반으로 이들을 찾습니다.
  • VC가 storyboard에서 로드되었다면, UIKit은 이 VC를 생성하고 위치를 찾기 위해 저장된 storyboard 정보를 사용한다.
    • UIKit은 복원 아카이브 내부에 VC의 storyboard에 대한 정보를 저장합니다.
    • 복원시 UIKit은 같은 storyboard 파일을 찾기 위해 이 정보를 사용하고 이것에 일치하는 VC를 인스턴스화 합니다.

복원 class를 VC에 할당하면 UIKit에서 해당 VC를 암시적으로 검색할 수 없게 합니다.

복원 class를 사용하면 실제로 VC를 만들지 여부를 제어할 수 있습니다.

ex) viewControllerWithResotrationIdentifierPath:coder:) 메소드는 VC를 다시 만들지 않아야 한다고 결정하면 nil을 반환 가능

-> 상황에 따라 복원 class가 있더라도, 복원하지 않고 싶다면 nil을 return

 

복원 class를 사용할 때, viewControllerWithResotrationIdentifierPath:coder:) 메소드는 class의 새로운 인스턴스를 생성해야 하고 최소한의 초기화를 수행하고 결과 객체를 반환해야 합니다.

아래의 코드는 storyboard에서 VC를 로드하는 방법이다.

static func viewController(withRestorationIdentifierPath identifierComponents: [String], coder: NSCoder) -> UIViewController? {
    var vc: MyViewController?
    let sb = coder.decodeObject(forKey: UIApplication.stateRestorationViewControllerStoryboardKey) as? UIStoryboard
    if sb != nil {
        vc = sb?.instantiateViewController(withIdentifier: "MyViewController") as? PushViewController
        vc?.restorationIdentifier = identifierComponents.last
        vc?.restorationClass = MyViewController.self
    }
    return vc
}

복원 식별자와 복원 class를 재할당하는 것은 VC가 수동으로 재 생성되었을 때 채택하지 위한 좋은 습관이다.

복원 식별자를 복원하는 간단한 방법은 identifierComponents 배열의 마지막 아이템을 가져오는 것이다.

 

앱을 실행할 때, 앱의 Main Storyboard에서 생성했던 객체의 경우, 각 객체의 새로운 인스턴스를 생성하지 않습니다.

UIKit은 이 객체를 암묵적으로 찾거나,  AppDelegate의 application:viewcontrollerWithRestorationIdentifierPath:coder: 를 통해 찾는다.

 

 

Encoding and Decoding View Controller's State

보존이 예정된 각 객체의 경우 UIKit은 상태 저장할 기회를 제공하기 위해

encodeRestorableStateWithcoder: 를 호출한다.

보존 처리 동안 UIKit은 decodeRestorableStateWithCoder: 메소드를 호출하여 해당 상태를 해독하고 객체에 적용한다.

메소드들의 구현은 선택적이다.

저장되는 정보들은 다음의 타입을 따라야 한다.

  • 화면에 표시되어지는 데이터 참조(데이터 그 자체가 아니다?)
  • Container VC의 경우 자식 VC를 참조한다.
  • 현재 선택에 대한 정보
  • 유저가 구성할 수 있는 VC의 경우 그 View의 현재 구성에 대한 정보

인코딩과 디코딩 메소드에서 객체와 coder가 지원하는 데이터 타입을 인코딩할 수 있다.

View와 VC들을 제외한 모든 객체의 경우 해당 객체는 반드시 NSCoding Protocol를 채택해야 하고, 상태 기록을 위해 protocol의 메소드 사용

View와 VC의 경우 상태를 저장하기 위해 NSCoding Protocol 사용 X

 

 


iOS 13 ~

SceneDelegate를 사용할 것이다.

 

우선은 Info.plist에 속성을 추가해준다.

 

Enable State Preservation and Restoration for Your App

해당 Application의 Scene에서 NSUserActivity를 사용하여 State를 복구할거야! 라고 명시하는 메소드입니다.

 

Preserve and Restore the App State with an Activity Object

NSUserActivity객체는 현재 시점의 앱 상태를 캡처한다고 한다.

ex) 앱을 사용하다가 다른 앱으로의 전환 및 홈화면으로의 이동 등의 상황에서 앱이 Active상태를 나갈 때 상태를 저장하면 된다.

 

- detailUserActivity는 property로 직접 생성해야 한다.

 

우선, 메소드를 작성하게 되면, Scene이 ResignActive 될 때, 코드에 작성한 ViewController의 detailUserActivity라는 NSUserActivity라는 객체가 scene의 userActivity로 저장된다.

 

MainViewController에 detailUserActivity 만들기

하단에 있는 메소드인 addUserInfoEntries에서 딕셔너리를 저장하고 있다.

해당 딕셔너리에 Restore하고 싶은 값들은 Key와 함께 저장하면 된다.

 

Scene Configure

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions){
	if let userActivity = connectionOptions.userAcitvities.first ?? session.stateRestorationActivity{
    	if !self.configure(window: self.window, with userActivity){
        	print("Failed to restore MainViewController")
        }
    }
}

func configure(window: UIWindow?, with activity: NSUserActivity) -> Bool{
	let mainVC = MainViewController()
    if let navigation = window?.rootViewController as? UINavigationController{
    	navigation.pushViewController(mainVC, animated: false)
        mainVC.restoreUserActivityState()
        mainVC.label = activity.userInfo?["key"] as? String
        ......
    }
}

항상 원하지 않는 다면

configure 메소드를 호출하기 전에 조건을 걸거나 하면 원하는 상황일 때만, Restoration이 가능하다

 

 

근데... 정확하게 어떤식으로 복구되는 지를 모르겠다...

  1. App이 Active 상태를 잃어버릴 때, detailUserActivity가 scene의 activity가 된다.
  2. App을 재 실행하게 되면 scene(_:willConnectTo: ...)가 호출되면서 scene에 activity가 존재하면 configure()메소드를 호출한다.
  3. configure() 메소드에서 해당 VC를 찾고 매개변수로 넘어온 activity를 사용해서 UI를 복구한다.

이 순서로 동작하는 것 같은데... 그렇다면 LocalDB나 UserDefaults를 사용하는 것이랑 다른 것이 뭐지...?

좀 더 자세히 알아봐야겠다...

'iOS' 카테고리의 다른 글

[iOS] HitTest  (0) 2021.05.21
[iOS] Responder / Responder Chain  (0) 2021.05.21
[iOS] App Life Cycle  (0) 2021.05.18
[iOS] TabBar Slide Down/Up Animation  (0) 2021.03.25
[iOS] Animation - 2 (feat. Frame-Based Layout vs AutoLayout)  (0) 2021.03.25

"App Life Cycle"

앱의 실행부터 종료까지의 주기이며,

- 앱이 화면에 보이고 있는 foreground (Active)

- 앱이 화면에 보이지 않고 실행되고 있는 background

- 앱이 실행 중이지만, 아무런 이벤트가 없는 InActive (ex. 전화)

- 앱이 background에 있지만, 실행되는 코드가 없는 상태인 Suspend

    - 메모리가 부족한 상황이 오면 이 상태에 있는 앱들을 특별한 알림 없이 정리한다.

- 실행되지 않은 상태인 Not Running 

이 있다.

-> Active와 InActive를 합쳐서 Foreground 상태라고 한다.

 

상태 변화

iOS는 시스템에서 발생하는 특정 상황에 맞게 앱의 상태를 변화시키고 제어한다.

ex) 우리가 카카오톡을 사용하다가, 친구에게 전화가 오면 카톡이 꺼지고 '전화 앱'이 켜지는 상황을 보셨을 것이다.

이는 iOS가 '전화'라는 상황에 맞게 앱의 상태를 변경한 것이다.

 

이처럼 iOS는 각각의 앱마다 상태 변화를 제어하여 어떤 앱은 실행시키고, 어떤 앱은 홈 화면으로, 종료를 하는 등 다양한 처리를 한다.

 

앱 상태에 따른 메소드 ( ~ iOS 12 )

앱의 상태가 변화할 때마다 앱 객체는 AppDelegate에 정의된 특정 메소드를 호출한다.

AppDelegate는 앱의 모든 Window를 관리한다.

 

  • application(_:willFinishLaunchingWithOptions:)
    • 앱이 구동되어 필요한 초기 실행 과정이 완료되기 직전에 호출되는 메소드
  • application(_:didFinishLaunchingWithOptions:)
    • 앱이 사용자에게 화면으로 표시되기 직전에 호출되는 메소드
    • 앱이 실행된 후에 진행할 초기화 작업들을 주로 수행
  • applicationDidBecomeActive(_:)
    • 실행된 앱이 화면 전면에 표시될 때 호출되는 메소드
    • 앱이 InActive 상태에 들어가면서 일시 중지된 작업이 있다면, 재시작 코드를 여기서 작성해야 한다.
    • ex) 타이머 앱의 경우, InActive 상태로 들어가면 더이상 화면 갱신이 이뤄지지 않기 때문에, 이 메소드에서 갱신해야 함
  • applicationDidEnterBackground(_:)
    • 앱이 Background 상태에 진입했을 때 호출되는 메소드
    • 이 메소드가 호출되면 미래에 앱이 종료될 가능성이 있다는 뜻이므로, 사용자의 데이터를 미리 저장하거나, 공유 자원을 해제해야 함
  • applicationWillTerminate(_:)
    • 앱이 종료되기 직전에 호출되는 메소드

 

앱 상태에 따른 메소드 ( iOS 13 ~ )

Scene을 iOS 13부터 지원하게 되면서 iOS 12까지의 AppDelegate와는 다르다.

같은 App을 동시에 여러개 켰다..!

각각의 App의 화면을 Scene이라고 일컫는다.

따라서 iOS 13부터는 Scene을 지원하게 되면서, 하나의 App을 여러개(다수의 인스턴스 / Scene)를 사용할 수 있는 것이다.

 

이전까지의 AppDelegate에서는 하나의 Scene만을 관리하게 설계되어 있었다.

하지만 위와 같은 iPad의 경우엔, 상황에 따라 여러 Scene을 허용해야 하는 경우가 있을 것이다. 그렇다면 여러 Scene을 AppDelegate에서 동시에 관리할 수 없지 않은가?

따라서 iOS 13부터는 SceneDelegate를 통해 Scene을 관리할 수 있다.

 

 

AppDelegate(~ iOS 12) vs SceneDelegate(iOS 13 ~)

 

12까지의 AppDelegate의 역할은 크게 2가지 였습니다.

Process LifeCycle, UI LifeCycle

 

 

13부터는 UI LifeCycle은 SceneDelegate가 처리합니다.

각각의 Scene마다 현재 가장 상단에 있는 VC, 내부에 UI Components가 각각의 상황에 따라 다를 것이다.

-> 그렇지 않으면 Scene을 사용할 의미가 없다고 생각함...

 

(이후에SceneDelegate에 대해서 좀 더 알아보도록 하겠다.)

 

 

ViewController의 수명주기 보러 가기

+ Recent posts