Skip to content

Commit

Permalink
Merge pull request #677 from ibrahimkteish/view-controller-life-cycle
Browse files Browse the repository at this point in the history
Add the ability to observe  UIViewController life cycle using signals
  • Loading branch information
srdanrasic authored Apr 20, 2020
2 parents 0feb570 + 798d92b commit f002f3d
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Bond.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
/* Begin PBXBuildFile section */
070FE2741F0138180031B7BD /* NSLayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90C04D2A1E8F0B1D000077C8 /* NSLayoutConstraint.swift */; };
070FE2781F0138190031B7BD /* NSLayoutConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90C04D2A1E8F0B1D000077C8 /* NSLayoutConstraint.swift */; };
0856892E2426BC1D002ACEE7 /* ViewControllerLifeCycle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0856892D2426BC1D002ACEE7 /* ViewControllerLifeCycle.swift */; };
16210A461D3EC474004AEDF3 /* Bond.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 16210A3C1D3EC474004AEDF3 /* Bond.framework */; };
16887E3A1D744ABB00EDA099 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16887E391D744ABB00EDA099 /* AppDelegate.swift */; };
16887E441D744ABB00EDA099 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 16887E421D744ABB00EDA099 /* LaunchScreen.storyboard */; };
Expand Down Expand Up @@ -382,6 +383,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
0856892D2426BC1D002ACEE7 /* ViewControllerLifeCycle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerLifeCycle.swift; sourceTree = "<group>"; };
161D6D2D1D6F0D92004CA17D /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
16210A3C1D3EC474004AEDF3 /* Bond.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Bond.framework; sourceTree = BUILT_PRODUCTS_DIR; };
16210A451D3EC474004AEDF3 /* BondTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BondTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -784,6 +786,7 @@
90C04D2A1E8F0B1D000077C8 /* NSLayoutConstraint.swift */,
90C04D2D1E8F0B1D000077C8 /* NSObject.swift */,
90C04D2C1E8F0B1D000077C8 /* NSObject+KVO.swift */,
0856892D2426BC1D002ACEE7 /* ViewControllerLifeCycle.swift */,
);
path = Shared;
sourceTree = "<group>";
Expand Down Expand Up @@ -1146,6 +1149,7 @@
E58FBB8D22187FD700339211 /* UIPickerView+DataSource.swift in Sources */,
90C04DBC1E8F0B97000077C8 /* UIRefreshControl.swift in Sources */,
ECBC51FA216163BA00BE80EC /* TreeNode.swift in Sources */,
0856892E2426BC1D002ACEE7 /* ViewControllerLifeCycle.swift in Sources */,
90B5F63B2269D6DD001917B4 /* NSCollectionView+DataSource.swift in Sources */,
90C04DBD1E8F0B97000077C8 /* UISegmentedControl.swift in Sources */,
ECFF44B12168C21F00B5EDB0 /* Array2D.swift in Sources */,
Expand Down
154 changes: 154 additions & 0 deletions Sources/Bond/Shared/ViewControllerLifecycle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//
// ViewControllerLifeCycle.swift
// Bond-iOS
//
// Created by Ibrahim Koteish on 21/03/2020.
// Copyright © 2020 Swift Bond. All rights reserved.
//

import Foundation
import UIKit
import ReactiveKit

public enum LifecycleEvent {
case viewDidLoad
case viewWillAppear
case viewDidAppear
case viewWillDisappear
case viewDidDisappear
case viewDidLayoutSubviews
case viewWillLayoutSubviews
}

/// Observe UIViewController life cycle events
public final class ViewControllerLifecycle {

private let wrapperViewController: WrapperViewController

public var lifecycleEvents: Signal<LifecycleEvent, Never> {
self.wrapperViewController.lifecycleEvents
}

public func lifecycleEvent(_ event: LifecycleEvent) -> Signal<Void, Never> {
self.wrapperViewController.lifecycleEvent(event)
}

public init(viewController: UIViewController) {
self.wrapperViewController = WrapperViewController()
if viewController.isViewLoaded {
self.addAsChildViewController(viewController)
} else {
viewController
.reactive
.keyPath(\.view, startWithCurrentValue: false)
.prefix(maxLength: 1)
.bind(to: viewController) { (viewController, _) in
self.addAsChildViewController(viewController)
}
}
}

private func addAsChildViewController(_ viewController: UIViewController) {

viewController.addChild(self.wrapperViewController)
viewController.view.addSubview(self.wrapperViewController.view)
self.wrapperViewController.view.frame = .zero
self.wrapperViewController.view.autoresizingMask = []
self.wrapperViewController.didMove(toParent: viewController)
}
}

private extension ViewControllerLifecycle {

final class WrapperViewController: UIViewController {


deinit {
_lifecycleEvent.send(completion: .finished)
}

private let _lifecycleEvent = PassthroughSubject<LifecycleEvent, Never>()

public var lifecycleEvents: Signal<LifecycleEvent, Never> {
var signal = _lifecycleEvent.toSignal()
if isViewLoaded {
signal = signal.prepend(.viewDidLoad)
}
return signal
}

public func lifecycleEvent(_ event: LifecycleEvent) -> Signal<Void, Never> {
return lifecycleEvents.filter { $0 == event }.eraseType()
}

//MARK: - Overrides
override func viewDidLoad() {
super.viewDidLoad()
_lifecycleEvent.send(.viewDidLoad)
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
_lifecycleEvent.send(.viewWillAppear)
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
_lifecycleEvent.send(.viewDidAppear)
}

override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
_lifecycleEvent.send(.viewWillDisappear)
}

override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
_lifecycleEvent.send(.viewDidDisappear)
}

override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
_lifecycleEvent.send(.viewWillLayoutSubviews)
}

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
_lifecycleEvent.send(.viewDidLayoutSubviews)
}
}
}

public protocol ViewControllerLifecycleProvider: class {
var viewControllerLifecycle: ViewControllerLifecycle { get }
}

extension UIViewController: ViewControllerLifecycleProvider {

private struct AssociatedKeys {
static var viewControllerLifeCycleKey = "viewControllerLifeCycleKey"
}

public var viewControllerLifecycle: ViewControllerLifecycle {
if let lifeCycle = objc_getAssociatedObject(self, &AssociatedKeys.viewControllerLifeCycleKey) {
return lifeCycle as! ViewControllerLifecycle
} else {
let lifeCycle = ViewControllerLifecycle(viewController: self)
objc_setAssociatedObject(
self,
&AssociatedKeys.viewControllerLifeCycleKey,
lifeCycle, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return lifeCycle
}
}
}

extension ReactiveExtensions where Base: ViewControllerLifecycleProvider {
public var lifecycleEvents: Signal<LifecycleEvent, Never> {
self.base.viewControllerLifecycle.lifecycleEvents
}

public func lifecycleEvent(_ event: LifecycleEvent) -> Signal<Void, Never> {
self.base.viewControllerLifecycle.lifecycleEvent(event)
}
}

0 comments on commit f002f3d

Please sign in to comment.