swift 轻松推出自己的`UITabBarController`替代品。这里是您需要的所有逻辑,而无需假设您的UI。

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了swift 轻松推出自己的`UITabBarController`替代品。这里是您需要的所有逻辑,而无需假设您的UI。相关的知识,希望对你有一定的参考价值。

final class TabComponentViewController: UIViewController, TabComponent {
    let tabItem: TabItem
    
    init(title: String) {
        tabItem = TabItem(title: title)
        
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

final class TabItemViewControllerRouterTests: XCTestCase {
    let tabComponent1 = TabComponentViewController(title: "1")
    
    let tabComponent2 = TabComponentViewController(title: "2")
    
    let parentViewController = UIViewController()

    func testNoViewControllerParentsByDetault() {
        let _ = TabItemViewControllerRouter(tabComponents: [tabComponent1, tabComponent2], parentViewController: parentViewController,
                                               viewHierarchyUpdater: { _ in })
        
        XCTAssertNil(tabComponent1.parentViewController)
        XCTAssertNil(tabComponent2.parentViewController)
    }
    
    func testSelectingTabBarItemAddsViewControllerToParent() {
        let router = TabItemViewControllerRouter(tabComponents: [tabComponent1, tabComponent2], parentViewController: parentViewController,
                                                    viewHierarchyUpdater: { _ in })
        router.selectTabItem(tabComponent1.tabItem)
        
        XCTAssertEqual(tabComponent1.parentViewController, parentViewController)
    }
    
    func testSelectingTwoTabBarItemAddsViewControllerToParentAndRemovesPreviousController() {
        let router = TabItemViewControllerRouter(tabComponents: [tabComponent1, tabComponent2], parentViewController: parentViewController,
                                                    viewHierarchyUpdater: { _ in })
        router.selectTabItem(tabComponent1.tabItem)
        router.selectTabItem(tabComponent2.tabItem)
        
        XCTAssertNil(tabComponent1.parentViewController)
        XCTAssertEqual(tabComponent2.parentViewController, parentViewController)
    }
    
    func testSelectionInvokesHierarchyUpdaterWithCorrectView() {
        let expectation = expectationWithDescription("View hierarchy will be updated")
        
        let router = TabItemViewControllerRouter(tabComponents: [tabComponent1, tabComponent2], parentViewController: parentViewController, viewHierarchyUpdater: { view in
            XCTAssertEqual(view, self.tabComponent1.view)
            expectation.fulfill()
        })
        router.selectTabItem(tabComponent1.tabItem)
        
        waitForExpectationsWithTimeout(2) { error in
            if let _ = error {
                XCTFail()
            }
        }
    }
}
/**
 Maintains the state needed to implement your own `UITabController` replacement, without making any assumptions about
 how your UI looks or works.
 */
public final class TabItemViewControllerRouter {
    /// The tab bar items for the view controllers. You can use these when constructing your UI’s buttons
    public let tabItems: [TabItem]
    
    // MARK: - Immutable private state
    
    /// The view controllers that can be routed between
    private let viewControllers: [UIViewController]
    
    /// Mapping of tab bar items to view controllers
    private let tabItemsToViewControllers: [TabItem: UIViewController]
    
    /// The container view controller serving as the `UITabController` replacement
    private weak var parentViewController: UIViewController?
    
    /// A function for adding a view to the container view controller’s hierarchy
    private let viewHierarchyUpdater: UIView -> Void
    
    // MARK: - Mutable private state
    
    /// The currently selected view controller
    private var selectedViewController: UIViewController?
    
    // MARK: - Initialization
    
    /**
     Create a new tab bar item view controller router.
     
     - parameter viewControllers:      The view controllers that can be routed between
     - parameter parentViewController: The container view controller serving as the `UITabController` replacement
     - parameter viewHierarchyUpdater: A function for adding a view to the container view controller’s hierarchy
     
     - returns: New instance
     */
    public init(tabComponents: [TabComponent], parentViewController: UIViewController, viewHierarchyUpdater: UIView -> Void) {
        self.viewControllers = tabComponents.map { $0.viewController }
        self.parentViewController = parentViewController
        self.viewHierarchyUpdater = viewHierarchyUpdater
        
        self.tabItems = tabComponents.map { $0.tabItem }
        self.tabItemsToViewControllers = Dictionary<TabItem, UIViewController>(zip(tabItems, viewControllers))
    }
    
    /**
     Route based on a new tab bar item having been selected.
     
     - parameter tabItem: Selected tab bar item
     */
    public func selectTabItem(tabItem: TabItem) {
        guard let viewController = tabItemsToViewControllers[tabItem] else { return }
        
        selectViewController(viewController)
    }
    
    /**
     Update which view controller is the selected one.
     
     - parameter viewController: Selected view controller
     */
    private func selectViewController(viewController: UIViewController) {
        let oldValue = selectedViewController
        
        oldValue?.willMoveToParentViewController(nil)
        parentViewController?.addChildViewController(viewController)
        
        viewHierarchyUpdater(viewController.view)
        
        oldValue?.removeFromParentViewController()
        viewController.didMoveToParentViewController(parentViewController)
        
        selectedViewController = viewController
    }
}
final class TabItemSelectionStateTests: XCTestCase {
    var tabItem1: TabItem!
    var tabItem2: TabItem!
    var button1: UIButton!
    var button2: UIButton!
    var selectionState: TabItemSelectionState!
    
    override func setUp() {
        tabItem1 = TabItem(title: "Foo")
        tabItem2 = TabItem(title: "Bar")
        button1 = UIButton()
        button2 = UIButton()
        
        selectionState = TabItemSelectionState(
            buttons: [button1, button2],
            tabItems: [tabItem1, tabItem2]
        )
    }

    func testInitialSelection() {
        XCTAssertEqual(selectionState.selectedTabItem.value, tabItem1)
    }
    
    func testSelectionUpdatesSelectedValue() {
        selectionState.selectButton(button2)
        XCTAssertEqual(selectionState.selectedTabItem.value, tabItem2)
    }
    
    func testSelectingSameValueDoesNotTriggerObservable() {
        selectionState.selectButton(button1)
        selectionState.selectedTabItem.bind { item in
            XCTFail()
        }
    }
}
/**
 Manages selection states for buttons intended to behave in a tab bar-like fashion. Intended to be used in conjunction 
 with `TabItemViewControllerRouter` to easily implement something like `UITabController` without any boilerplate, 
 allowing you to only focus on your custom UI.
 
 When a button is selected, its selection state is updated (as is the selection state of the previously selected button). 
 Additionally, a new selected tab bar item is vended, which your UI should react to in order to update which view 
 controller is currently being shown on screen.
 */
public final class TabItemSelectionState {
    // MARK: - Public mutable state
    
    public let selectedTabItem: Observable<TabItem>
    
    // MARK: - Private mutable state
    
    private var selectedButton: UIControl
    
    // MARK: - Private immutable state
    
    private let buttonsToTabItems: [UIControl: TabItem]
    
    // MARK: - Initialization
    
    /**
     Create a new selection state i nstance.
     
     - parameter buttons:  Buttons. Must not be empty.
     - parameter tabItems: Tab items. Must not be empty and must have the same `.count` as `buttons`.
     
     - returns: New instance
     */
    public init(buttons: [UIControl], tabItems: [TabItem]) {
        assert(buttons.count == tabItems.count)
        guard let firstTabItem = tabItems.first else { fatalError("Need at least one tab bar item") }
        guard let firstButton = buttons.first else { fatalError("Need at least one button") }
        
        selectedTabItem = Observable(firstTabItem)
        
        selectedButton = firstButton
        selectedButton.selected = true
        
        buttonsToTabItems = Dictionary<UIControl, TabItem>(zip(buttons, tabItems))
    }
    
    // MARK: - Public
    
    public func selectButton(button: UIControl) {
        guard button != selectedButton else { return }
        guard let tabItem = buttonsToTabItems[button] else { fatalError("Unknown button") }
        
        selectedButton.selected = false
        selectedButton = button
        selectedButton.selected = true
        
        selectedTabItem.value = tabItem
    }
}
/**
 *  A class that can be part of a tabbed navigational interface (expected to be a `UIViewController` but can also be a 
 *  coordinator that proxies through to an underlying controller).
 */
public protocol TabComponent {
    /// The tab metadata
    var tabItem: TabItem { get }

    var viewController: UIViewController { get }
}

/**
 *  Only needed because Swift doesn’t currently have a way to allow you to specify that an instance both descends from 
 *  `UIViewController` and also conforms to a protocol.
 */
public extension TabComponent where Self: UIViewController {
    var viewController: UIViewController {
        return self
    }
}

/**
 *  Basically the same thing as `UITabBarItem` but a custom one that we control. For now, the only real reason we’re 
 *  using this custom class is to make `badgeValue` observable but we may add more stuff in the future, e.g. 
 * `badgeColor`.
 */
public final class TabItem: Hashable, Equatable {
    public let title: String
    public let badgeValue: Observable<Int?>
    
    // MARK: - Hashable
    
    public var hashValue: Int {
        return title.hashValue
    }
    
    // MARK: - Initialization
    
    public convenience init(title: String, badgeValue: Int? = nil) {
        self.init(title: title, badgeValue: Observable(badgeValue))
    }
    
    public init(title: String, badgeValue: Observable<Int?>) {
        self.title = title
        self.badgeValue = badgeValue
    }
}

public func ==(lhs: TabItem, rhs: TabItem) -> Bool {
    return lhs.title == rhs.title
}

以上是关于swift 轻松推出自己的`UITabBarController`替代品。这里是您需要的所有逻辑,而无需假设您的UI。的主要内容,如果未能解决你的问题,请参考以下文章

Swift进阶之内存模型和方法调度

微软 GitHub 推出新代码搜索工具,面向GitHub编程?

ruby 在Rails中轻松推出功能

SWIFT计划为亚太国家推出即时跨境电子支付系统

Chrome推出全新音乐制作工具 让任何人都可以轻松写歌

SWIFT将为亚太国家推出即时跨境支付系统。