// Copyright 2019-present the Material Components for iOS authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import CoreGraphics import UIKit import MaterialComponents.MaterialAppBar import MaterialComponents.MaterialAppBar_Theming import MaterialComponents.MaterialTabs import MaterialComponents.MaterialContainerScheme // This example demonstrates issues with flexible header tabs and animations. class AppBarInheritedAnimatedJumpExample: UIViewController { lazy var appBarViewController: MDCAppBarViewController = self.makeAppBar() @objc var containerScheme: MDCContainerScheming = MDCContainerScheme() fileprivate let tabs = [ ChildOfTrackingScrollViewViewController(title: "First"), ChildOfTrackingScrollViewViewController(title: "Second"), ChildOfTrackingScrollViewViewController(title: "Third"), ] private var currentTab: ChildOfTrackingScrollViewViewController? = nil lazy var tabBar: MDCTabBar = { let tabBar = MDCTabBar() tabBar.items = self.tabs.map { $0.tabBarItem } tabBar.delegate = self return tabBar }() override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) self.title = "Manual Tabs Jump (Animated, UITableViewController)" } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() for (offset, tab) in zip(0..., tabs) { tab.tabBarItem.tag = offset } appBarViewController.applyPrimaryTheme(withScheme: containerScheme) // Need to update the status bar style after applying the theme. appBarViewController.view.isOpaque = false setNeedsStatusBarAppearanceUpdate() view.isOpaque = false view.backgroundColor = containerScheme.colorScheme.backgroundColor view.addSubview(appBarViewController.view) appBarViewController.didMove(toParent: self) switchToTab(tabs[0], animated: false) } fileprivate func switchToTab( _ tab: ChildOfTrackingScrollViewViewController, animated: Bool = true ) { appBarViewController.headerView.trackingScrollWillChange(toScroll: tab.tableView) // Hide old tab. let removeOld: (() -> Void) let animateOut: (() -> Void) if let currentTab = currentTab { currentTab.willMove(toParent: nil) animateOut = { currentTab.view.alpha = 0 } removeOld = { currentTab.headerView = nil currentTab.view.removeFromSuperview() currentTab.removeFromParent() } } else { removeOld = {} animateOut = {} } if let tabView = tab.view { tabView.autoresizingMask = [.flexibleWidth, .flexibleHeight] tabView.frame = view.bounds } // Show new tab. view.addSubview(tab.view) view.sendSubviewToBack(tab.view) tab.didMove(toParent: self) tab.headerView = self.appBarViewController.headerView tab.view.alpha = 0 let animateIn = { tab.view.alpha = 1 } let finishMove = { self.appBarViewController.headerView.trackingScrollView = tab.tableView self.currentTab = tab } if animated { UIView.animate( withDuration: 1, animations: { animateOut() animateIn() }, completion: { _ in removeOld() finishMove() }) } else { animateOut() removeOld() animateIn() finishMove() } } @objc func changeAlignmentDidTouch(sender: UIButton) { tabs[0].title = "First" switchToTab(tabs[0]) } @objc func changeAppearance(fromSender sender: UIButton) { tabs[1].title = "Second" switchToTab(tabs[1]) } // MARK: Private private func makeAppBar() -> MDCAppBarViewController { let appBarViewController = MDCAppBarViewController() addChild(appBarViewController) // Give the tab bar enough height to accommodate all possible item appearances. appBarViewController.headerView.minMaxHeightIncludesSafeArea = false appBarViewController.inferTopSafeAreaInsetFromViewController = true appBarViewController.headerView.sharedWithManyScrollViews = true appBarViewController.headerView.minimumHeight = 56 appBarViewController.headerView.maximumHeight = 128 appBarViewController.headerStackView.bottomBar = tabBar return appBarViewController } override var childForStatusBarStyle: UIViewController? { return appBarViewController } } extension AppBarInheritedAnimatedJumpExample: MDCTabBarDelegate { func tabBar(_ tabBar: MDCTabBar, didSelect item: UITabBarItem) { switchToTab(tabs[item.tag]) } } extension AppBarInheritedAnimatedJumpExample { @objc class func catalogMetadata() -> [String: Any] { return [ "breadcrumbs": ["App Bar", "Manual Tabs Jump (Animated, UITableViewController)"], "primaryDemo": false, "presentable": false, ] } @objc func catalogShouldHideNavigation() -> Bool { return true } }