Skip to content

Commit

Permalink
Merge pull request #9 from trafi/feature/tabbar
Browse files Browse the repository at this point in the history
TabBar support
  • Loading branch information
Domas Nutautas authored May 15, 2020
2 parents 134cd60 + 0cfd08b commit 17c23bd
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 19 deletions.
29 changes: 25 additions & 4 deletions Sources/StoryFlow/Flow/Helpers/Transition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,33 @@ extension Transition where To == UIViewController {

public static func unwind(animated: Bool = true) -> Transition {
return Transition {
if $1.presentedViewController != nil {
$1.navigationController?.popToViewController($1, animated: false)

let parentInTab = $1.parentInTabBarController
let isWrongTab = parentInTab != $1.tabBarController?.selectedViewController
let isPresenting = $1.presentedViewController != nil

// 1. Navigation pop
if let nav = $1.navigationController {
let animatedPop = animated && !isPresenting && !isWrongTab
nav.popToViewController($1, animated: animatedPop)
}

// 2. Tab change
if isWrongTab {
$1.tabBarController?.selectedViewController = parentInTab
}

// 3. Presented dismiss
if isPresenting {
$1.dismiss(animated: animated, completion: nil)
} else {
$1.navigationController?.popToViewController($1, animated: animated)
}
}
}
}

private extension UIViewController {

var parentInTabBarController: UIViewController? {
self.parent == self.tabBarController ? self : self.parent?.parentInTabBarController
}
}
35 changes: 25 additions & 10 deletions Sources/StoryFlow/Flow/Helpers/Unwinding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@ import UIKit

extension UIViewController {

func unwindVc(for updateType: Any.Type, filterOutSelf: Bool = true) -> UIViewController? {
func unwindVc(for updateType: Any.Type) -> UIViewController? {
unwindVc(for: updateType, from: self)
}

private func unwindVc(for updateType: Any.Type, from source: UIViewController) -> UIViewController? {

Thread.onMain {
if canHandle(updateType, filterOutSelf: filterOutSelf) {
if canHandle(updateType, from: source) {
return self
} else if let vc = navBackStack?.first(where: { $0.canHandle(updateType) }) {
} else if let vc = navStack?.first(where: { $0.canHandle(updateType, from: source) }) {
return vc
} else if let vc = tabs?.first(where: { $0.canHandle(updateType, from: source) }) {
return vc
} else {
return presentingViewController?.unwindVc(for: updateType, filterOutSelf: false)
?? parent?.unwindVc(for: updateType, filterOutSelf: false)
return (parent ?? presentingViewController)?.unwindVc(for: updateType, from: source)
}
}
}
Expand All @@ -26,22 +31,32 @@ extension UIViewController {

private extension UIViewController {

func canHandle(_ updateType: Any.Type, filterOutSelf: Bool = false) -> Bool {
return visibleVcs.contains { $0.isVc(for: updateType) && (filterOutSelf == false || $0 !== self) }
func canHandle(_ updateType: Any.Type, from source: UIViewController) -> Bool {
return visibleVcs.contains { $0.isVc(for: updateType) && $0 !== source }
}

var visibleVcs: [UIViewController] {
if let nav = self as? UINavigationController {
guard let top = nav.topViewController else { return [self] }
return [self] + top.visibleVcs
} else if let tab = self as? UITabBarController {
guard let selected = tab.selectedViewController else { return [self] }
return [self] + selected.visibleVcs
} else {
return [self] + children.flatMap { $0.visibleVcs }
}
}

var navBackStack: ArraySlice<UIViewController>? {
guard let nav = navigationController ?? self as? UINavigationController else { return nil }
return nav.viewControllers.reversed().dropFirst()
func firstChild<T>(_ t: T.Type) -> T? {
self as? T ?? children.first { $0.firstChild(t) != nil }?.firstChild(t)
}

var navStack: [UIViewController]? {
firstChild(UINavigationController.self)?.viewControllers.reversed()
}

var tabs: [UIViewController]? {
firstChild(UITabBarController.self)?.viewControllers
}

func isVc(for updateType: Any.Type) -> Bool {
Expand Down
1 change: 1 addition & 0 deletions StoryFlowTests/Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var currentVc: UIViewController {

func currentVc(from vc: UIViewController) -> UIViewController {
return (vc as? UINavigationController)?.topViewController.flatMap(currentVc)
?? (vc as? UITabBarController)?.selectedViewController.flatMap(currentVc)
?? vc.presentedViewController.flatMap(currentVc)
?? vc
}
Expand Down
124 changes: 119 additions & 5 deletions StoryFlowTests/ImplicitFlowTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,50 @@ class ImplicitFlowTests: XCTestCase {
XCTAssert(currentVc is To)
}

func testProduce_whenInNav_itShowsNextVc() {

// Arrange
class T {}

class From: UIViewController, OutputProducing { typealias OutputType = T }
class To: UIViewController, InputRequiring { typealias InputType = T }

let from = From()

let nav = UINavigationController().visible()
nav.setViewControllers([UIViewController(), UIViewController(), from], animated: false)

let output = T()

// Act
from.produce(output)

// Assert
XCTAssert(currentVc is To)
}

func testProduce_whenInTab_itShowsNextVc() {

// Arrange
class T {}

class From: UIViewController, OutputProducing { typealias OutputType = T }
class To: UIViewController, InputRequiring { typealias InputType = T }

let from = From()

let tab = UITabBarController().visible()
tab.setViewControllers([from, UIViewController(), UIViewController()], animated: false)

let output = T()

// Act
from.produce(output)

// Assert
XCTAssert(currentVc is To)
}

// MARK: Unwind

func testProduce_itUnwindsToUpdateHandlingVcAndPassesOutput() {
Expand Down Expand Up @@ -308,7 +352,6 @@ class ImplicitFlowTests: XCTestCase {

// Act
from.produce(output)
XCTAssert(currentVc.didDismiss())

// Assert
XCTAssert(currentVc == to)
Expand All @@ -317,6 +360,65 @@ class ImplicitFlowTests: XCTestCase {

}

func testProduce_itUnwindsWithTabChange() {

// Arrange
class T {}

class From: UIViewController, OutputProducing { typealias OutputType = T }
class To: UIViewController, UpdateHandling {
func handle(update: T) { self.update = update }
var update: T!
}

let to = To()
let from = From()

let tab = UITabBarController().visible()
tab.setViewControllers([from, to], animated: false)

let output = T()

// Act
from.produce(output)

// Assert
XCTAssert(tab.selectedIndex == 1)
XCTAssert(currentVc == to)
XCTAssert(to.update === output)
}

func testProduce_itUnwindsWithTabChangeInNavigationStack() {

// Arrange
class T {}

class From: UIViewController, OutputProducing { typealias OutputType = T }
class To: UIViewController, UpdateHandling {
func handle(update: T) { self.update = update }
var update: T!
}

let to = To()
let from = From()

let nav = UINavigationController()
nav.setViewControllers([to, UIViewController()], animated: false)

let tab = UITabBarController().visible()
tab.setViewControllers([from, nav], animated: false)

let output = T()

// Act
from.produce(output)

// Assert
XCTAssert(tab.selectedIndex == 1)
XCTAssert(currentVc == to)
XCTAssert(to.update === output)
}

func testProduce_itUnwindsInComplexFlow() {

// Arrange
Expand All @@ -326,26 +428,38 @@ class ImplicitFlowTests: XCTestCase {
class To: UIViewController, UpdateHandling { func handle(update: T) { } }

let to = To()
UINavigationController(rootViewController: to).visible()
let tab = UITabBarController().visible()
tab.setViewControllers([UINavigationController(rootViewController: to), UIViewController()],
animated: false)

to.show(UIViewController(), sender: nil)
XCTAssert(currentVc.didAppear())

tab.selectedIndex = 1

currentVc.show(UINavigationController(rootViewController: UIViewController()), sender: nil)
XCTAssert(currentVc.didAppear())

currentVc.show(UIViewController(), sender: nil)
XCTAssert(currentVc.didAppear())

let from = From()
currentVc.show(from, sender: nil)
XCTAssert(currentVc.didAppear())

// TabBar
// - Navigation
// - >> TO <<
// - dummy
// - dummy
// - Navigation
// - dummy
// - dummy
// - << From >>

// Act
from.produce(T())
XCTAssert(currentVc.didDismiss())

// Assert
XCTAssert(tab.selectedIndex == 0)
XCTAssert(currentVc == to)
}

Expand Down

0 comments on commit 17c23bd

Please sign in to comment.