2020. 8. 7. 13:46ㆍiOS/Swift
오늘은 KVO에 대해서 알아보겠습니다.
KVO는 Key-Value Observing의 약자로 특정한 변수가 변경될 때마다 코드가 실행되도록 하는 기능입니다.
일반적으로 프로퍼티 감시자인 willSet이나 didSet과 비슷하지만, 모델과 뷰 등 논리적으로 분리된 앱 부분간에 변경사항을 전달하는데 유용하다고 합니다. 이 기능은 Objective-C 런타임에 의존하기 때문에 순수한 Swift 코드에서는 그리 좋지 않다고 합니다..!
따라서 NSObject를 상속받는 클래스로 정의해 주어야 하며 각각의 프로퍼티에는 @objc dynamic 이라는 표시를 해야합니다. dynamic 이 자세히 어떤건지는 추후 다루어 보겠습니다!
KVO를 적용시켜 볼 프로젝트는 다음과 같습니다.
상단의 탭바가 존재하고 화면 안에서도 스크롤통해 탭 이동이 가능해집니다. 이에따라 상단 탭의 하이라이팅과 현재 탭을 표시하는 인디케이터 가 존재합니다.
화면의 구성을 간단하게 말씀드리면 상단 탭은 컬렉션 뷰를 통해 구현하였고, 아래에 보여지는 화면은 컨테이너뷰와 PageViewController를 통해 페이징을 하였습니다.
자세한 구현 내용은
lidium.tistory.com/14?category=873871
이곳을 참고하였습니다.
페이징을 담당하는 PageViewController와 부모 ViewController가 달라도 PageViewController 내부에서 현재 페이지 인덱스를 저장하는 KVO Object를 생성하여 이 값이 변경될 때마다 상단 탭의 인디케이터를 바꾸어주는 동작을 수행하였습니다.
가장 먼저 해야할 것은 관찰할 프로퍼티를 다음과 같이 만드는 것 입니다.
class KVOObject : NSObject {
@objc dynamic var curPresentViewIndex: Int = 0
}
앞서 설명드렸다 시피 값을 관찰하기 위해서는 NSObject를 상속받는 클래스 내부의 프로퍼티로 @objc dynamic 이라는 표시를 해 주어야 합니다. 따라서 현재 PageViewController에서 보여지고 있는 화면의 인덱스 값을 저장시켜줄 프로퍼티를 생성하였습니다.
그리고 페이징을 담당하는 페이지 뷰컨트롤러의 프로퍼티로 만들어 두었던 클래스의 인스턴스를 생성해 둡니다.
class PageVC: UIPageViewController {
...
var keyValue = KVOObject()
override func viewDidLoad() {
super.viewDidLoad()
...
}
...
}
이렇게 생성을 해둔 다음페이지 뷰컨트롤러 내부에서 페이징이 됨에 따라 위의 프로퍼티 내부에 존재하는 'curPresentViewIndex' 변경해 주었습니다.
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed {
print(previousViewControllers[0])
if previousViewControllers[0] is CustomTopTab.ThemaVC {
self.keyValue.curPresentViewIndex = 1
}
else {
self.keyValue.curPresentViewIndex = 0
}
}
}
테스트로 탭이 두개인 상황만 가정했기 때문에 간단하게 다음과 같이 변경해 주었습니다.
메인으로 돌아와서 해당 값이 바뀔 때 마다 인디케이터 바를 변경시켜주는 동작을 수행시켜 주면 됩니다.
컨테이너뷰의 segue를 통해 페이지뷰컨의 인스턴스를 생성하여 observe 를 등록해주면 됩니다.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "pageSegue" {
pageInstance = segue.destination as? PageVC
let ob = pageInstance?
.keyValue
.observe(\.curPresentViewIndex,
options: [.new, .old]) {
[weak self] (changeObject, value) in
self?.tabBarCollectionView.selectItem(at: IndexPath(item: value.newValue!,
section: 0),
animated: false,
scrollPosition: .bottom)
if (value.newValue == 0){
UIView.animate(withDuration: 0.1){
self!.underBarView.transform = CGAffineTransform(translationX:0 ,y: 0)
}
}
else {
UIView.animate(withDuration: 0.1){
self!.underBarView.transform = CGAffineTransform(translationX:200 ,y: 0)
}
}
}
observingList.append(ob!)
pageInstance?.pageControlDelegate = self
}
}
}
pageInstance는 메인 뷰컨트롤러에서 PageVC의 옵셔널 타입으로 선언한 프로퍼티 변수 입니다. prepare를 통해 해당 프로퍼티를 초기화 시킨 다음 pageInstance에 존재하는 keyValue에 observe 함수를 호출해 주면 됩니다.
observe 함수는
observe(keyPath: KeyPath<KVOObject, Value>, options: NSKeyValueObservingOptions, changeHandler: (KVOObject, NSKeyValueObservedChange<Value>) -> Void) 다음과 같은 형식으로 호출할 수가 있습니다.
keyPath는 관찰할 변수를 설정해 줍니다 '.\변수명' 과 같이 넣어주면 되고, option에는 NSKeyValueObservingOptions 라는 타입을 넣어주어야 하는데 .new, .old, .initial, prior 의 옵션을 줄 수가 있습니다.
.new는 적용 가능한 경우 change dirctionaly가 새 속성 값을 제공해야 함을 나타낸다고 합니다.
.old는 new 와 비슷하지만 이전 속성 값이 포함되어야 함을 뜻한다고 합니다.
.initial은 지정한 경우 옵저버 registration method가 반환되지 전에 알림을 옵저버에게 즉시 보내야한다는 옵션이고
.prior는 변경 후 단일 알람 대신에 각 변경 전 후에 별도의 통지를 관찰자에게 보내야 하는지 여부를 결정해 주는 옵션입니다.
이 옵션 값에 따라 뒤에 정의해줄 handler 클로져로 들어오는 파라미터 중 NSKeyValueObservedChange<Value> 의 값이 조금 씩 변경 됩니다.
이 값을 print 할 경우
NSKeyValueObservedChange<Int>(kind: __C.NSKeyValueChange, newValue: nil, oldValue: Optional(0), indexes: nil, isPrior: false)
다음과 같이 나옵니다. old와 new옵션에 따라 newValue 또는 oldValue 에 nil 값이 들어오기도 하며 isPrior와 같은 속성이 변경되기도 합니다. 따라서 이 값에서 필요한 정보를 가져와 동작을 수행시켜 주면 됩니다.
저는 이 값에 따라 상단 탭을 구성하는 컬렉션뷰의 selectItem을 호출해 주고, 그 밑에 있는 인디케이터 바를 애니메이션함수를 통해 이동시켜 주었습니다.
KVO를 써보다 보니 이전에 공부했던 RxSwift와 유사한 구조로 동작한다는 것을 알 수 있었습니다.
아쉬운 점은 동작의 반응속도가 어느정도 delay가 존재하는 점 이였습니다. 구현에는 아쉬운 점이 있었지만 KVO 방식을 이해해 보는 좋은 시간이였습니다.
'iOS > Swift' 카테고리의 다른 글
UIBezierPath 사용해보기 (0) | 2020.10.16 |
---|---|
Swift Combine은 또 뭐야 ..? (1) | 2020.09.04 |
Swift Lint 사용해보기 !! (2) | 2020.04.10 |