Persistent view with UITabBarController - ios

I'm trying to build an app with layout similar to Apple Music - a tab bar navigation with a persistent view, accessible from everywhere in the app. The view can be expanded to take up the whole screen or minimised with a static height of 80. The UI is built in a storyboard with a normal UITabBarController. Here's a first draft:
This is how I've build it:
class TabbarViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
embedLiveFeedbackController()
}
private func embedLiveFeedbackController() {
guard let feedbackController = UIStoryboard(name: "LiveFeedback", bundle: nil).instantiateInitialViewController() as? LiveFeedbackViewController else { return }
feedbackController.stateDelegate = self
addChildViewController(feedbackController)
view.addSubview(feedbackController.view)
feedbackController.view.translatesAutoresizingMaskIntoConstraints = false
feedbackController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
feedbackController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
feedbackController.view.bottomAnchor.constraint(equalTo: tabBar.topAnchor).isActive = true
liveFeedbackTopConstraint = feedbackController.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
liveFeedbackHeightConstraint = feedbackController.view.heightAnchor.constraint(equalToConstant: Constants.minimizedHeight)
liveFeedbackHeightConstraint?.isActive = true
liveFeedbackTopConstraint?.isActive = false
}
}
The problem I have is that the content of the view controllers goes behind the persistent view and is not completely visible. One of the things I've tried is to constraint the view controllers to the top of the persistent view:
private func constraintViewControllers() {
guard let vcs = viewControllers else { return }
guard let topAnchor = liveFeedbackTopAnchor else { return } // a reference to the top anchor of the persistent view
for viewController in vcs {
viewController.view.translatesAutoresizingMaskIntoConstraints = false
viewController.view.bottomAnchor.constraint(equalTo: topAnchor).isActive = true
}
}
Of course, I get the following error:
Unable to activate constraint with anchors <NSLayoutYAxisAnchor:0x600000864640 "UILayoutContainerView:0x7fc813f0ea60.bottom"> and <NSLayoutYAxisAnchor:0x60400047b980 "UIView:0x7fc813f08af0.bottom"> because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal.'
Any suggestions how to go about implementing this?

Here is my suggestion, I tested it and worked fine
create a main viewController and put 2 view inside it (as in picture) :
-startViewController
--containerView (your app root viewController/tabBarController goes here)
--persistentView
in startViewController
class ViewController: UIViewController {
var tabController : TabController!
#IBOutlet weak var containerView: UIView!
#IBOutlet weak var persistentView: UIView!
#IBOutlet weak var persistentBottomConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
tabController = TabController.initFromStoryBord()
self.addChild(tabController)
tabController.view.frame = containerView.bounds
containerView.addSubview(tabController.view)
tabController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
persistantBottomConstraint.constant = tabController.tabBar.frame.height
}
}
and then a class for TabBarController:
make sure to set Stroyboard ID for tabController
class TabController : UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
}
static func initFromStoryBord() -> TabController {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "TabController") as! TabController
return vc
}
}

Related

Add a view controller as a subview in another view controller

I have already read this LINK , but not working for me. I want to show a viewController as a subview in another viewController.
Here is my code -
import UIKit
import CarbonKit
class ViewController: UIViewController, CarbonTabSwipeNavigationDelegate {
#IBOutlet weak var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let items = ["All", "WOMEN", "MEN", "KIDS", "HOME", "CITY"]
let carbonTabSwipeNavigation = CarbonTabSwipeNavigation(items: items, delegate: self)
carbonTabSwipeNavigation.insert(intoRootViewController: self)
}
func carbonTabSwipeNavigation(_ carbonTabSwipeNavigation: CarbonTabSwipeNavigation, viewControllerAt index: UInt) -> UIViewController {
// let screen = self.storyboard?.instantiateViewController(withIdentifier: "demo") as! demo
// showSubViewContrller(subViewController: vc)
// return screen
let storyBoard = getStoryBoardByIndentifier(identifier: "All")
let vc = storyBoard.instantiateViewController(withIdentifier: "AllViewController") as! AllViewController
showSubViewContrller(subViewController: vc)
return vc
}
//Subview Controller
func showSubViewContrller(subViewController:UIViewController) {
self.addChildViewController(subViewController)
subViewController.view.frame = containerView.frame
self.containerView.addSubview(subViewController.view)
subViewController.didMove(toParentViewController: self)
}
func getStoryBoardByIndentifier(identifier:String)->UIStoryboard {
return UIStoryboard.init(name: identifier, bundle: nil)
}
}
I have a NavigationBar and a tapBar. Would like to show the viewController inside the view in a container.
But when the view loads it's coverUp/hide the tabBar.
How to solve this and show the viewController in my specified container.
Project Link - GitHub
Somehow i am able to fix your issue with below changes:
Replace this method carbonTabSwipeNavigation.insert(intoRootViewController: self) with carbonTabSwipeNavigation.insert(intoRootViewController: self, andTargetView: containerView) in viewDidLoad
Note : Give UITaBar bottom constraint to SuperView not SafeArea:
Add below code in ViewController:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tabbar.invalidateIntrinsicContentSize()
}
After doing this when you run you will UITabBar:

I am trying to using a custom delegate in swift but when I want to call its methods, it did not get called

I have been searching for how the delegate works and I tried to do it in my project. Unfortunately, the delegate method I implement does not get called ever. I am trying to do a slide-out navigation panel. so what I did is that I put two uicontainerviews, one is for slide-out navigation panel and the other for main view controller
enter image description here
The code is that
For main view controller
protocol MainViewControllerDelegate {
func toggleSideMenu()
}
class MainViewController: UIViewController {
var delegate: MainViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Slide Action
#IBAction func slideMenuTapped(_ sender: UIBarButtonItem){
delegate?.toggleSideMenu()
print("Slide Menu has been tapped")
}
}
For container view controller
class ContainerVC: UIViewController {
#IBOutlet weak var SideMenuConstraint: NSLayoutConstraint!
#IBOutlet weak var slideMenuContainer: UIView!
#IBOutlet weak var mainViewContainer: UIView!
var mainViewController: MainViewController?
var isSideMenuOpened = false
override func viewDidLoad() {
super.viewDidLoad()
mainViewController = UIStoryboard.mainViewController()
mainViewController?.delegate = self
}
}
extension ContainerVC: MainViewControllerDelegate{
func toggleSideMenu() {
print("It works")
if isSideMenuOpened{
isSideMenuOpened = false
SideMenuConstraint.constant = -260
mainViewContainer.layer.shadowOpacity = 0
} else {
isSideMenuOpened = true
SideMenuConstraint.constant = 0
mainViewContainer.layer.shadowOpacity = 0.59
}
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
}
extension UIStoryboard{
static func mainStoryboard() -> UIStoryboard { return UIStoryboard(name: "Main", bundle: Bundle.main) }
static func mainViewController() -> MainViewController? {
return mainStoryboard().instantiateViewController(withIdentifier: "MainViewController") as? MainViewController
}
}
Please let know what's wrong
I think the reason is that you embed your main view controller in navigation controller :
let navigationController = self.childViewControllers.last as! UINavigationController
let mainViewController = navigationController.topViewController as! MainViewController
mainViewController?.delegate = self
Here is where you got wrong:
mainViewController = UIStoryboard.mainViewController()
mainViewController?.delegate = self
this mainViewController is not the same as the child of the container view controller, so setting its delegate doesn't really do anything.
You need to first get the VC that is the child of the container view controller:
mainViewController = self.childViewControllers.last as! MainViewController
mainViewController.delegate = self

Animate parent VC object from a child VC

Is it possible to animate views of a parent VC in Swift?
I've got a root/master VC with a UIView which I'm using as a sort of a UITabBarController, so the rest of my 4 main VCs are children of the root.
On some of the child VCs, I have subviews that should take up the whole screen, without seeing the custom tab bar (UIView) from the root VC, but it still floats above.
I would like to have it slide off the screen via Y axis whenever I open the fullscreen subviews, but I can't seem to access or manipulate the root VCs properties as it returns nil on runtime.
Here's the custom tab bar root VC so you can understand the structure of the code:
class RootVC: UIViewController {
//This is where we pull all of our content from other VCs
//when a tab bar button is selected
#IBOutlet weak var contentView: UIView!
//The custom tab bar itself with an array of button outlets
#IBOutlet public weak var customTabBarContainer: UIView!
#IBOutlet var tabBarButtons: [UIButton]!
//4 main view VCs that are reflected in the tab bar
public var mapVC: UIViewController!
public var favoritesVC: UIViewController!
public var chatVC: UIViewController!
public var profileVC: UIViewController!
//Array for the VCs above
public var viewControllers: [UIViewController]!
//Index of the selected button determend by their tags
public var selectedIndex: Int = 0
#IBOutlet weak var loadingLogo: UIImageView!
override public func viewDidLoad() {
//Populating viewControllers array with
//initiated VCs in Main storyboard
let storyboard = UIStoryboard(name: "Main", bundle: nil)
mapVC = storyboard.instantiateViewController(withIdentifier: "MapVC")
favoritesVC = storyboard.instantiateViewController(withIdentifier: "FavoritesVC")
chatVC = storyboard.instantiateViewController(withIdentifier: "ChatVC")
profileVC = storyboard.instantiateViewController(withIdentifier: "ProfileVC")
viewControllers = [mapVC, favoritesVC, chatVC, profileVC]
//Custom tab bar + buttons visual properties
customTabBarContainer.layer.cornerRadius = customTabBarContainer.frame.height / 2
customTabBarContainer.layer.shadowColor = UIColor.darkGray.cgColor
customTabBarContainer.layer.shadowOffset = CGSize.zero
customTabBarContainer.layer.shadowRadius = 10
customTabBarContainer.layer.shadowOpacity = 0.9
tabBarButtons[0].imageView?.contentMode = .scaleAspectFit
tabBarButtons[1].imageView?.contentMode = .scaleAspectFit
tabBarButtons[2].imageView?.contentMode = .scaleAspectFit
tabBarButtons[3].imageView?.contentMode = .scaleAspectFit
}
override public func viewDidAppear(_ animated: Bool) {
loadingLogo.popOut()
//Loads the initial VC
contentView.addSubview(mapVC.view)
mapVC.view.frame = self.view.frame
mapVC.didMove(toParentViewController: self)
customTabBarContainer.isHidden = false
//Selects the inital home button
tabBarButtons[0].isSelected = true
}
#IBAction func didTabButton(_ sender: UIButton) {
//Keeps a track of which bar button is selected
let previousIndex = selectedIndex
selectedIndex = sender.tag
//Deselects the previous bar button
tabBarButtons[previousIndex].isSelected = false
//Removes the previous VC
let previousVC = viewControllers[previousIndex]
previousVC.view.removeFromSuperview()
previousVC.removeFromParentViewController()
print("switced to \(viewControllers[selectedIndex])")
//Selects the tapped bar button
tabBarButtons[selectedIndex].isSelected = true
tabBarButtons[selectedIndex].popIn()
//Brings up the selected VC
let nextVC = viewControllers[selectedIndex]
contentView.addSubview(nextVC.view)
nextVC.view.frame = self.view.frame
nextVC.didMove(toParentViewController: self)
}
}
And here's the code I'm trying to use to manipulate the customTabBarContainer from a child of the MapVC:
UIView.animate(withDuration: 0.4, animations: {
let root = self.parent?.parent as! RootVC
root.customTabBarContainer.frame.origin.y -= root.customTabBarContainer.frame.height
}, completion: nil)
why are you trying to access the parent of the parent then?
self.parent?.parent as RootVC
assuming you are using an extension like this one to find your parentVC:
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if parentResponder is UIViewController {
return parentResponder as! UIViewController!
}
}
return nil
}
}
you should be able to access parent via
let root = self.parentViewController as! RootVC
I've figured out an answer, just in case anyone else encounters a similar problem. It will not take you to the VCs immediate parent, but instead, to its most distant ancestor, which solves my particular problem in this case.
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let rootVC = appDelegate.window?.rootViewController as! RootVC
rootVC.customTabBarContainer.isHidden = true

Unable to see controller added to UITabBar programmatically swift

I added 2 tabBar items from storyboard and one UITabBarItem - Menu programmatically. I am successfully able to open the controllers corresponding to tabBarItems which I created using storyboard. However, when I click on "Menu" a blank black screen appears,
#objc public class MainScreenTabsController : UITabBarController {
public override func viewDidLoad() {
super.viewDidLoad()
let tabController = MyViewController()
let tabBarItem = UITabBarItem(title: "Menu", image: UIImage(named: "more-options.png"), selectedImage: UIImage(named: "more-options"))
tabController.tabBarItem = tabBarItem
var array = self.viewControllers
array?.append(tabController)
self.viewControllers = array
}
public func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
return true;
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
}
I followed couple of tutorials for adding tab bar item but all of them had the code I wrote. Am I missing out something very basic?
EDIT:
Class for Menu Controller
#objc public class MyViewController:UIViewController {
public override func viewDidLoad() {
super.viewDidLoad()
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
}
Your app is doing exactly what your code is telling it to do. You are creating an instance of MyViewController and adding it to the UITabBarController's array of View Controllers.
Your MyViewController class file simply defines a blank, black view.
I'm guessing you created a ViewController in your Storyboard that you want to use as MyViewController? If so, you need to instantiate that from the storyboard.
When you're editing your storyboard, assign the MyViewController class to the VC you want to use, and also give it a Storyboard ID - such as MyVC. Then, edit your viewDidLoad function to this:
public override func viewDidLoad() {
super.viewDidLoad()
// wrong way
// let tabController = MyViewController()
if let tabController = storyboard?.instantiateViewController(withIdentifier: "MyVC") as? MyViewController {
let tabBarItem = UITabBarItem(title: "Menu", image: UIImage(named: "more-options.png"), selectedImage: UIImage(named: "more-options"))
tabController.tabBarItem = tabBarItem
var array = self.viewControllers
array?.append(tabController)
self.viewControllers = array
}
}
Since you're creating the ViewController programatically ie. without nib/storyboard, you are responsible for instantiating a UIView object and setting the view property of the view controller. to do that implement the loadView method and assign the view object to view property of the viewController. then you can add custom views to the view object, check the code below.
class MyViewController: UIViewController {
override func loadView() {
// super.loadView() // DO NOT CALL SUPER
//create view
view = UIView()
view.backgroundColor = UIColor.white
//Add a custom view with red color
let customView = UIView()
customView.translatesAutoresizingMaskIntoConstraints = false
customView.backgroundColor = UIColor.red
view.addSubview(customView)
NSLayoutConstraint.activate(
[customView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
customView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
customView.topAnchor.constraint(equalTo: view.topAnchor),
customView.bottomAnchor.constraint(equalTo: view.bottomAnchor)]
)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
It would be good to use Storyboard/Nib for this purpose as you can easily configure custom views/controls using the autolayout in interface builder rather than doing it programmatically. :)
Edit:
if your'e using storyboard then instantiate view controller like given in below code
class MainScreenTabsController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let tabController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MainViewController") as! MainViewController //if using storyboard
let icon = UITabBarItem(title: "Menu", UIImage(named: "more-options.png"), selectedImage: UIImage(named: "more-options")))
tabController.tabBarItem = icon
var controllers = self.viewControllers
controllers?.append(tabController)
self.setViewControllers(controllers!, animated: true)
}
}

swift subview does not full-fill parent view

When app starts, I use this method to set the root view controller.
func showRootViewController() {
let sb = UIStoryboard.init(name: "Main", bundle: nil)
let mainViewController = sb.instantiateInitialViewController()!
self.window?.rootViewController = mainViewController
self.window?.makeKeyAndVisible()
}
In main view controller, I've code to set the view to container view
class MainViewController: UIViewController {
#IBOutlet weak var headerView: UIView!
#IBOutlet weak var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if(userManager.hasRegistered()) {
let sb = UIStoryboard.init(name: "Login", bundle: nil)
self.setContentViewController(sb.instantiateInitialViewController()!)
} else {
let sb = UIStoryboard.init(name: "Registration", bundle: nil)
self.setContentViewController(sb.instantiateInitialViewController()!)
}
}
func setContentViewController(_ contentViewController: UIViewController) {
print(self.containerView.frame)
let contentView = contentViewController.view!
contentView.frame = self.containerView.bounds
self.containerView.addSubview(contentView)
}
}
Let look at the view, I have container view what is used to add subviews. I set the background to help everyone address the frame of container view
The subview is designed as below:
The result is not as my expected. ContainerView has gone somewhere (I really don't know), the subview is move on the top.
Please help me.
contentView frame is setted in the wrong position.
You should move this line of code:
contentView.frame = self.containerView.bounds
in
override func func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
contentView.frame = self.containerView.bounds
}
You cannot rely on dimensions in viewDidLoad.
Try viewDidLayoutSubviews instead. Be aware that it can be called multiple times
I have solved this type of issue.
i used a tableview with a single row(your subview)to wrap content

Resources