Skip to content

Commit

Permalink
Merge pull request #1 from formbound/release-1.1.0
Browse files Browse the repository at this point in the history
Release 1.1.0
  • Loading branch information
davidask authored Aug 19, 2019
2 parents 8bc6adc + bfc4c43 commit 631d353
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 5 deletions.
58 changes: 53 additions & 5 deletions Sources/StateViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ open class StateViewController<State: Equatable>: UIViewController {
return false // We completely manage forwarding of appearance methods ourselves.
}

fileprivate var observers: [StateViewControllerObserver<State>] = []

// MARK: - View lifecycle

/// :nodoc:
Expand Down Expand Up @@ -504,6 +506,12 @@ fileprivate extension StateViewController {
return false
}

// We may not have made any changes to content view controllers, even though we have changed the state.
// Therefore, we must be prepare to end the state transition immediately.
defer {
endStateTransitionIfNeeded(animated: animated)
}

// If we're transitioning between states, we need to abort and wait for the current state
// transition to finish.
guard isTransitioningBetweenStates == false else {
Expand All @@ -513,12 +521,8 @@ fileprivate extension StateViewController {

// Invoke callback method, indicating that we will change state
willTransition(to: state, animated: animated)
dispatchStateEvent(.willTransitionTo(nextState: state, animated: animated))

// We may not have made any changes to content view controllers, even though we have changed the state.
// Therefore, we must be prepare to end the state transition immediately.
defer {
endStateTransitionIfNeeded(animated: animated)
}

// Note that we're transitioning from a state
transitioningFromState = state
Expand Down Expand Up @@ -608,6 +612,7 @@ fileprivate extension StateViewController {
transitioningFromState = nil

// Notify that we're finished transitioning
dispatchStateEvent(.didTransitionFrom(previousState: fromState, animated: animated))
didTransition(from: fromState, animated: animated)

// If we still need another state, let's transition to it immediately.
Expand Down Expand Up @@ -731,6 +736,8 @@ fileprivate extension StateViewController {

func updateHierarchy(of viewControllers: [UIViewController]) {

let previousSubviews = contentViewControllerContainerView.subviews

for (index, viewController) in viewControllers.enumerated() {
viewController.view.translatesAutoresizingMaskIntoConstraints = true
viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
Expand All @@ -740,6 +747,14 @@ fileprivate extension StateViewController {
contentViewControllerContainerView.insertSubview(viewController.view, at: index)
}

// Only proceed if the previous subviews of the content view controller container view
// differ from the new subviews
guard previousSubviews.elementsEqual(contentViewControllerContainerView.subviews) == false else {
return
}

dispatchStateEvent(.didChangeHierarhcy)

// Tell everyone we're updating the view hierarchy
NotificationCenter.default.post(name: .stateViewControllerDidChangeViewHierarchy, object: self)

Expand Down Expand Up @@ -787,6 +802,7 @@ fileprivate extension StateViewController {
// If we are, appearance methods will be forwarded at a later time
if isInAppearanceTransition == false {
contentViewControllerWillAppear(viewController, animated: animated)
dispatchStateEvent(.contentWillAppear(viewController))
viewController.beginAppearanceTransition(true, animated: animated)
}

Expand All @@ -802,6 +818,7 @@ fileprivate extension StateViewController {
// If we're not in an appearance transition, forward appearance methods.
// If we are, appearance methods will be forwarded at a later time
if isInAppearanceTransition == false {
dispatchStateEvent(.contentDidAppear(viewController))
viewController.endAppearanceTransition()
contentViewControllerDidAppear(viewController, animated: animated)
}
Expand All @@ -822,6 +839,7 @@ fileprivate extension StateViewController {
// If we are, appearance methods will be forwarded at a later time
if isInAppearanceTransition == false {
contentViewControllerWillDisappear(viewController, animated: animated)
dispatchStateEvent(.contentWillDisappear(viewController))
viewController.beginAppearanceTransition(false, animated: animated)
}

Expand All @@ -840,6 +858,7 @@ fileprivate extension StateViewController {
// If we're not in an appearance transition, forward appearance methods.
// If we are, appearance methods will be forwarded at a later time
if isInAppearanceTransition == false {
dispatchStateEvent(.contentDidDisappear(viewController))
viewController.endAppearanceTransition()
contentViewControllerDidDisappear(viewController, animated: animated)
}
Expand All @@ -848,3 +867,32 @@ fileprivate extension StateViewController {
viewControllersBeingRemoved.remove(viewController)
}
}


public extension StateViewController {


func addStateObserver(
_ eventHandler: @escaping StateViewControllerObserver<State>.EventHandler
) -> StateViewControllerObserver<State> {

let observer = StateViewControllerObserver(stateViewController: self, eventHandler: eventHandler)

observers.append(observer)

return observer
}

func removeStateObserver(_ observerToRemove: StateViewControllerObserver<State>) {
observers = observers.filter { observer in
observer != observerToRemove
}
}

fileprivate func dispatchStateEvent(_ event: StateViewControllerObserver<State>.Event) {
for observer in observers {
observer.invoke(with: event)
}
}

}
49 changes: 49 additions & 0 deletions Sources/StateViewControllerObserver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

public protocol AnyStateViewControllerObserver : AnyObject {
func remove()
}

public class StateViewControllerObserver<T: Equatable>: AnyStateViewControllerObserver {

public typealias EventHandler = (StateViewControllerObserver<T>.Event) -> Void

public enum Event {
case willTransitionTo(nextState: T, animated: Bool)
case didTransitionFrom(previousState: T?, animated: Bool)

case didChangeHierarhcy

case contentWillAppear(UIViewController)
case contentDidAppear(UIViewController)

case contentWillDisappear(UIViewController)
case contentDidDisappear(UIViewController)
}

private weak var stateViewController: StateViewController<T>?

private let eventHandler: EventHandler

internal init(stateViewController: StateViewController<T>, eventHandler: @escaping EventHandler) {
self.eventHandler = eventHandler
self.stateViewController = stateViewController
}

public func remove() {
stateViewController?.removeStateObserver(self)
}

internal func invoke(with event: Event) {
eventHandler(event)
}
}

extension StateViewControllerObserver: Equatable {

public static func == (lhs: StateViewControllerObserver, rhs: StateViewControllerObserver) -> Bool {
return lhs === rhs
}
}



4 changes: 4 additions & 0 deletions StateViewController.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
objects = {

/* Begin PBXBuildFile section */
6325167D224E221A00235B46 /* StateViewControllerObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6325167C224E221A00235B46 /* StateViewControllerObserver.swift */; };
B76AA3C6212052FA00020277 /* StateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B76AA3C4212052FA00020277 /* StateViewController.swift */; };
B76AA3C7212052FA00020277 /* StateViewControllerTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B76AA3C5212052FA00020277 /* StateViewControllerTransitionCoordinator.swift */; };
B76AA3CB2120531600020277 /* StateViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = B76AA3C92120531600020277 /* StateViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
B76AA3DA212053D800020277 /* StateViewControllerTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = B76AA3D9212053D800020277 /* StateViewControllerTransitioning.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
6325167C224E221A00235B46 /* StateViewControllerObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateViewControllerObserver.swift; sourceTree = "<group>"; };
B76AA3B7212052B500020277 /* StateViewController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StateViewController.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B76AA3C4212052FA00020277 /* StateViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateViewController.swift; sourceTree = "<group>"; };
B76AA3C5212052FA00020277 /* StateViewControllerTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateViewControllerTransitionCoordinator.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -63,6 +65,7 @@
isa = PBXGroup;
children = (
B76AA3C4212052FA00020277 /* StateViewController.swift */,
6325167C224E221A00235B46 /* StateViewControllerObserver.swift */,
B76AA3C5212052FA00020277 /* StateViewControllerTransitionCoordinator.swift */,
B76AA3D9212053D800020277 /* StateViewControllerTransitioning.swift */,
);
Expand Down Expand Up @@ -165,6 +168,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6325167D224E221A00235B46 /* StateViewControllerObserver.swift in Sources */,
B76AA3C6212052FA00020277 /* StateViewController.swift in Sources */,
B76AA3C7212052FA00020277 /* StateViewControllerTransitionCoordinator.swift in Sources */,
B76AA3DA212053D800020277 /* StateViewControllerTransitioning.swift in Sources */,
Expand Down

0 comments on commit 631d353

Please sign in to comment.