Skip to content

Commit

Permalink
Merge branch 'master' into fix/flash-news
Browse files Browse the repository at this point in the history
  • Loading branch information
LukasHromadnik authored Dec 7, 2020
2 parents b3ccbba + 856eb48 commit a7902ec
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 36 deletions.
81 changes: 51 additions & 30 deletions ACKategories-iOS/Base/FlowCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,6 @@ extension Base {

/// Clean up. Must be called when FC finished the flow to avoid memory leaks and unexpected behavior.
open func stop(animated: Bool = false, completion: (() -> Void)? = nil) {
/// Determines whether dismiss should be called on `presentingViewController` of root,
/// based on whether there are remaining VCs in the navigation stack.
var shouldCallDismissOnPresentingVC = true

let animationGroup = DispatchGroup()

// stop all children
Expand All @@ -81,22 +77,63 @@ extension Base {
$0.stop(animated: animated, completion: animationGroup.leave)
}

// dismiss all VCs presented from root or nav
if rootViewController.presentedViewController != nil {
if rootViewController == nil {
ErrorHandlers.rootViewControllerDeallocatedBeforeStop?()
}

dismissPresentedViewControllerIfPossible(animated: animated, group: animationGroup)

/// Determines whether dismiss should be called on `presentingViewController` of root,
/// based on whether there are remaining VCs in the navigation stack.
let shouldCallDismissOnPresentingVC = popAllViewControllersIfPossible(animated: animated, group: animationGroup)

// ensure that dismiss will be called on presentingVC of root only when appropriate,
// as presentingVC of root when modally presenting can be UITabBarController,
// but the whole navigation shouldn't be dismissed, as there are still VCs
// remaining in the navigation stack
if shouldCallDismissOnPresentingVC, let presentingViewController = rootViewController?.presentingViewController {
// dismiss when root was presented
animationGroup.enter()
rootViewController.dismiss(animated: animated, completion: animationGroup.leave)
presentingViewController.dismiss(animated: animated, completion: animationGroup.leave)
}

// stopping FC doesn't need to be nav delegate anymore -> pass it to parent
navigationController?.delegate = parentCoordinator

parentCoordinator?.removeChild(self)

animationGroup.notify(queue: DispatchQueue(label: "animationGroup")) {
completion?()
}
}

// MARK: - Stop helpers

/// Dismiss all VCs presented from root or nav if possible
private func dismissPresentedViewControllerIfPossible(animated: Bool, group: DispatchGroup) {
if let rootViewController = rootViewController, rootViewController.presentedViewController != nil {
group.enter()
rootViewController.dismiss(animated: animated, completion: group.leave)
}
}

// pop all view controllers when started within navigation controller
if let navigationController = navigationController, let index = navigationController.viewControllers.firstIndex(of: rootViewController) {
/// Pop all view controllers when started within navigation controller
/// - Returns: Flag whether dismiss should be called on `presentingViewController` of root,
/// based on whether there are remaining VCs in the navigation stack.
private func popAllViewControllersIfPossible(animated: Bool, group: DispatchGroup) -> Bool {
if
let navigationController = navigationController,
let rootViewController = rootViewController,
let index = navigationController.viewControllers.firstIndex(of: rootViewController)
{
// VCs to be removed from navigation stack
let toRemoveViewControllers = navigationController.viewControllers[index..<navigationController.viewControllers.count]

// dismiss all presented VCs on VCs to be removed
toRemoveViewControllers.forEach { vc in
if vc.presentedViewController != nil {
animationGroup.enter()
vc.dismiss(animated: animated, completion: animationGroup.leave)
group.enter()
vc.dismiss(animated: animated, completion: group.leave)
}
}

Expand All @@ -108,27 +145,11 @@ extension Base {
}

// set the appropriate value based on whether there are VCs remaining in the navigation stack
shouldCallDismissOnPresentingVC = remainingViewControllers.isEmpty
return remainingViewControllers.isEmpty
}

// ensure that dismiss will be called on presentingVC of root only when appropriate,
// as presentingVC of root when modally presenting can be UITabBarController,
// but the whole navigation shouldn't be dismissed, as there are still VCs
// remaining in the navigation stack
if shouldCallDismissOnPresentingVC, let presentingViewController = rootViewController.presentingViewController {
// dismiss when root was presented
animationGroup.enter()
presentingViewController.dismiss(animated: animated, completion: animationGroup.leave)
}

// stopping FC doesn't need to be nav delegate anymore -> pass it to parent
navigationController?.delegate = parentCoordinator

parentCoordinator?.removeChild(self)

animationGroup.notify(queue: DispatchQueue(label: "animationGroup")) {
completion?()
}
// Return the default value for the flag
return true
}

// MARK: - Child coordinators
Expand Down
16 changes: 16 additions & 0 deletions ACKategories-iOS/ErrorHandlers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// ErrorHandlers.swift
// ACKategories-iOS
//
// Created by Lukáš Hromadník on 07.12.2020.
//

import Foundation

/// Set of handlers for some undefined behaviors in the framework
public enum ErrorHandlers {

/// Called when `rootViewController` of the respective flow coordinator
/// is deallocated before the actual stop method logic is called
public static var rootViewControllerDeallocatedBeforeStop: (() -> Void)?
}
12 changes: 6 additions & 6 deletions ACKategories-iOS/GradientView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,7 @@ import UIKit
`[UIColor.white, UIColor.white.withAlphaComponent(0)]`
*/
open class GradientView: UIView {

override open class var layerClass: Swift.AnyClass {
get {
return CAGradientLayer.self
}
}
override open class var layerClass: Swift.AnyClass { CAGradientLayer.self }

/**
Creates a gradient view with colors and axis
Expand All @@ -30,9 +25,14 @@ open class GradientView: UIView {
*/
public init(colors: [UIColor], axis: NSLayoutConstraint.Axis) {
super.init(frame: CGRect(x: 0, y: 0, width: 1, height: 1))

guard let gradientLayer = layer as? CAGradientLayer else { return }

isUserInteractionEnabled = false

gradientLayer.frame = bounds
gradientLayer.colors = colors.map { $0.cgColor }

if axis == .vertical {
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1)
Expand Down
27 changes: 27 additions & 0 deletions ACKategories-iOSTests/FlowCoordinator/FlowCoordinatorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ final class FlowCoordinatorTests: XCTestCase {
override func setUp() {
super.setUp()

ErrorHandlers.rootViewControllerDeallocatedBeforeStop = nil

window = .dummy
}

Expand Down Expand Up @@ -188,4 +190,29 @@ final class FlowCoordinatorTests: XCTestCase {
XCTAssertEqual(navigationController.viewControllers.count, 1)
XCTAssertEqual(fc.childCoordinators.count, 0)
}

func testRootViewControllerIsNil() {
let fc = NavigationFC()
fc.start(in: window)
_ = fc.rootViewController.view
fc.rootViewController = nil

let exp = expectation(description: "Flow did finish")
fc.stop(animated: false) { exp.fulfill() }
wait(for: [exp], timeout: 0.3)
}

func testErrorCallbackIsCalled() {
let rootExp = expectation(description: "Root is deallocated")
ErrorHandlers.rootViewControllerDeallocatedBeforeStop = { rootExp.fulfill() }

let fc = NavigationFC()
fc.start(in: window)
_ = fc.rootViewController.view
fc.rootViewController = nil

let exp = expectation(description: "Flow did finish")
fc.stop(animated: false) { exp.fulfill() }
wait(for: [exp, rootExp], timeout: 0.3)
}
}
4 changes: 4 additions & 0 deletions ACKategories.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
69FA5FC223C8690A00B44BCD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 69FA5FC123C8690A00B44BCD /* LaunchScreen.storyboard */; };
6A31C9F3250572FE0047A983 /* SelfSizingTableHeaderFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A31C9F2250572FE0047A983 /* SelfSizingTableHeaderFooterView.swift */; };
A33559012555270F009B9D89 /* FlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33559002555270F009B9D89 /* FlowCoordinatorTests.swift */; };
A38883E3257E2D2D00B958DD /* ErrorHandlers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A38883E2257E2D2D00B958DD /* ErrorHandlers.swift */; };
A3BA685B256BEC7B006DB42F /* UIWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BA685A256BEC7B006DB42F /* UIWindow.swift */; };
A3BA6867256BECC6006DB42F /* UINavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BA6866256BECC6006DB42F /* UINavigationController.swift */; };
A3BA686E256BED96006DB42F /* Dummies.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BA686D256BED96006DB42F /* Dummies.swift */; };
Expand Down Expand Up @@ -266,6 +267,7 @@
69FA5FE423C8712D00B44BCD /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
6A31C9F2250572FE0047A983 /* SelfSizingTableHeaderFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfSizingTableHeaderFooterView.swift; sourceTree = "<group>"; };
A33559002555270F009B9D89 /* FlowCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowCoordinatorTests.swift; sourceTree = "<group>"; };
A38883E2257E2D2D00B958DD /* ErrorHandlers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandlers.swift; sourceTree = "<group>"; };
A3BA685A256BEC7B006DB42F /* UIWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIWindow.swift; sourceTree = "<group>"; };
A3BA6866256BECC6006DB42F /* UINavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UINavigationController.swift; sourceTree = "<group>"; };
A3BA686D256BED96006DB42F /* Dummies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dummies.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -444,6 +446,7 @@
children = (
6950963023C7747C00E8F457 /* Base */,
697CECF023C877B20019FE61 /* Aliases.swift */,
A38883E2257E2D2D00B958DD /* ErrorHandlers.swift */,
6950964823C7751600E8F457 /* GradientView.swift */,
6950964D23C7752B00E8F457 /* NSMutableParagraphStyleExtensions.swift */,
6950965223C7753D00E8F457 /* ReusableView.swift */,
Expand Down Expand Up @@ -995,6 +998,7 @@
6950965123C7753500E8F457 /* NumberFormatterExtensions.swift in Sources */,
6950964523C774DB00E8F457 /* ConditionalAssignment.swift in Sources */,
6950962823C7740B00E8F457 /* Base.swift in Sources */,
A38883E3257E2D2D00B958DD /* ErrorHandlers.swift in Sources */,
6950964C23C7751E00E8F457 /* NSAttributedStringExtensions.swift in Sources */,
6950967123C78AC900E8F457 /* UILabelExtensions.swift in Sources */,
F8B81694246C59F7005D1D74 /* Date+Random.swift in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
### Fixed

- Check if `navigationController != rootViewController` before running navigation delegate method ([#100](https://github.com/AckeeCZ/ACKategories/pull/100), kudos to @lukashromadnik)
- `GradientView` has `isUserInteractionEnabled = false` as it is not supposed to be interactive by design ([#99](https://github.com/AckeeCZ/ACKategories/pull/99), kudos to @olejnjak)
- Check the value of `rootViewController` before stopping the flow ([#98](https://github.com/AckeeCZ/ACKategories/pull/98), kudos to @lukashromadnik)

## 6.7.2

Expand Down

0 comments on commit a7902ec

Please sign in to comment.