Created
December 16, 2019 09:08
-
-
Save cipolleschi/4ff61f0a60fb3eafc774af31c8b32017 to your computer and use it in GitHub Desktop.
Custom Tabbar with Containment API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
// MARK: - Tabbar VC | |
// Base view controller of the tabbar | |
class TabbarViewController: UIViewController { | |
// list of all the supported tabs | |
enum Tab: String { | |
case tab1, tab2, tab3 | |
} | |
// local state of the tabbar | |
struct LocalState { | |
var selectedTab: Tab = .tab1 | |
} | |
// MARK: - Variable declaration | |
var localState: LocalState = LocalState() | |
var rootView: TabbarView { | |
return self.view as! TabbarView | |
} | |
lazy var vcs: [Tab: UIViewController] = { | |
return [.tab1: VC1(), | |
.tab2: VC2(), | |
.tab3: VC3()] | |
}() | |
// MARK: - Initialization | |
override func loadView() { | |
self.view = TabbarView() | |
self.setupInteractions() | |
self.add(vcs[.tab1]!, frame: self.rootView.frame) | |
} | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
// Do any additional setup after loading the view | |
} | |
// MARK: - Interactions | |
func setupInteractions() { | |
self.rootView.tab1Interaction = { [weak self] in | |
self?.changeTab(to: .tab1) | |
} | |
self.rootView.tab2Interaction = { [weak self] in | |
self?.changeTab(to: .tab2) | |
} | |
self.rootView.tab3Interaction = { [weak self] in | |
self?.changeTab(to: .tab3) | |
} | |
} | |
// This function makes the magic happens. | |
// It verifies that the new tab is valid | |
// and then removes the current tab for and | |
// adds the new one. | |
func changeTab(to tab: Tab) { | |
// changing tab to the same tab | |
guard tab != self.localState.selectedTab, | |
let oldVC = self.vcs[self.localState.selectedTab], | |
let newVC = self.vcs[tab] else { | |
return | |
} | |
oldVC.remove() | |
self.localState.selectedTab = tab | |
self.add(newVC, frame: self.rootView.frame) | |
} | |
} | |
// MARK: Containment API primitives | |
@nonobjc extension UIViewController { | |
/// Add a view controller as child of the tabbar | |
/// | |
/// - parameter child: the child vc to add | |
/// - parameter frame: the available frame for the child vc | |
func add(_ child: UIViewController, frame: CGRect? = nil) { | |
addChild(child) | |
if let frame = frame { | |
child.view.frame = frame | |
} | |
view.addSubview(child.view) | |
view.sendSubviewToBack(child.view) | |
child.didMove(toParent: self) | |
} | |
/// Remove a vc previously added from the children | |
func remove() { | |
willMove(toParent: nil) | |
view.removeFromSuperview() | |
removeFromParent() | |
} | |
} | |
// MARK: - MainTabbar View | |
class TabbarView: UIView { | |
// Variables | |
let container = UIView() | |
let tab1 = UIButton(type: .contactAdd) | |
let tab2 = UIButton(type: .system) | |
let tab3 = UIButton(type: .infoLight) | |
// Interactions | |
var tab1Interaction: (()->())? | |
var tab2Interaction: (()->())? | |
var tab3Interaction: (()->())? | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
self.setup() | |
self.style() | |
} | |
required init?(coder: NSCoder) { | |
super.init(coder: coder) | |
self.setup() | |
self.style() | |
} | |
func setup() { | |
self.addSubview(self.container) | |
self.container.addSubview(self.tab1) | |
self.container.addSubview(self.tab2) | |
self.container.addSubview(self.tab3) | |
self.tab1.addTarget(self, action: #selector(self.tab1Tapped(_:)), for: .touchUpInside) | |
self.tab2.addTarget(self, action: #selector(self.tab2Tapped(_:)), for: .touchUpInside) | |
self.tab3.addTarget(self, action: #selector(self.tab3Tapped(_:)), for: .touchUpInside) | |
} | |
@objc func tab1Tapped(_ sender: UIButton) { | |
self.tab1Interaction?() | |
} | |
@objc func tab2Tapped(_ sender: UIButton) { | |
self.tab2Interaction?() | |
} | |
@objc func tab3Tapped(_ sender: UIButton) { | |
self.tab3Interaction?() | |
} | |
func style() { | |
self.backgroundColor = .black | |
self.container.backgroundColor = UIColor.white.withAlphaComponent(0.3) | |
self.tab1.setTitle("Tab 1", for: .normal) | |
self.tab2.setTitle("Tab 2", for: .normal) | |
self.tab3.setTitle("Tab 3", for: .normal) | |
} | |
func update() { | |
} | |
override func layoutSubviews() { | |
super.layoutSubviews() | |
self.container.frame = CGRect(x: 0, | |
y: self.bounds.height - 50 - self.safeAreaInsets.bottom, | |
width: self.bounds.width, | |
height: 50 + self.safeAreaInsets.bottom) | |
let buttonWidth = self.bounds.width / 3.0 | |
self.tab1.frame = CGRect(x: 0, | |
y: 10, | |
width: buttonWidth, | |
height: 30) | |
self.tab2.frame = CGRect(x: buttonWidth, | |
y: 10, | |
width: buttonWidth, | |
height: 30) | |
self.tab3.frame = CGRect(x: 2*buttonWidth, | |
y: 10, | |
width: buttonWidth, | |
height: 30) | |
} | |
} | |
// MARK: - Other VCs | |
class VC1: UIViewController { | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
self.view.backgroundColor = .red | |
} | |
} | |
class VC2: UIViewController { | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
self.view.backgroundColor = .green | |
} | |
} | |
class VC3: UIViewController { | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
self.view.backgroundColor = .blue | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment