Have a UITabBar that worked fine until some changes that were needed where I am now loading the controller with the UITabBar as a subview. Loading the controller with the UITabView as a subview has caused the delegate for the UITabBar to no longer function. Walking through the code shows the delegate being set for the UITabView to the controller. Checked that the UITabView is still the top view and it is. User interaction is still active on the page but only UITabBar isn't working. This used to work fine until the insertSubview was used. Here is how things are loaded:
let storyBoard = UIStoryboard(name: "MainStoryBoard", bundle: nil)
let vc = storyBoard.instantiateViewController(withIdentifier:"HomeController") as! T
vc.view.tag = 99
view.insertSubview(vc.view, at: 1)
addChild(vc)
vc.didMove(toParent: self)
In the "HomeController" delegate is set self.tabBar.delegate = self in viewDidLoad. Also in this viewcontroller is the extension on UITabBarDelegate.
extension HomePageViewController: UITabBarDelegate {
func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
print(item)
}
}
Anyone have any ideas of what needs to changed so that the UITabView can work again?
Related
as I wrote in the title, I'm not sure why ViewWillAppear is not called when another VC is dismissed. I think my project is a little bit tricky, so I'm gonna explain what is going on in my project.
Mostly, I configure UIs in code. I have two VCs, ListVC and CameraVC, and I have a tab bar and a navigation var in ListVC, which I configured all in code.
So in the SceneDelegate
I wrote something like
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let scene = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: scene.coordinateSpace.bounds)
window?.windowScene = scene
window?.rootViewController = createTabBar()
window?.makeKeyAndVisible()
}
func createTabBar() -> CustomTabBarController {
let tabbar = CustomTabBarController()
tabbar.viewControllers = tabbar.setUpTabbarItems()
return tabbar
}
and in the CustomTabBarController class, since I just wanted to present the event list, I added
func createListNC() -> UINavigationController {
let ListVC = ListViewController()
ListVC.tabBarItem = UITabBarItem(title: "", image: UIImage(named: "add-icon"), tag: 0)
return UINavigationController(rootViewController: ListVC)
}
func setUpTabbarItems() -> [UIViewController]{
return [createListNC()]
}
So now I can display List VC on the home of the app. However, I made the CameraVC all in the storyboard and I added the following code when a user taps one of the Event cells in List VC and it presents CamearaVC.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "Camera", bundle: nil)
let CameraVC = storyboard.instantiateViewController(identifier: "Camera")
self.present(CameraVC, animated: true, completion: nil)
}
So I can also present the CameraVC from ListVC.
But the problem happens when I want to back from CameraVC to ListVC.
I added IBAction to the button saying "< back" in CameraVC, and I can dismiss the CameraVC, however, since the cameraVC's screen is horizontal, the ListVC also stack with horizontal, which I want to make the ListVC vertical.
#IBAction func unwindToLiveList (_ segue: UIStoryboardSegue) {
self.presentingViewController?.dismiss(animated: true, completion: nil)
}
So, I was planning to write a code for fixing the orientation of the app when a user back from CameraVC to ListVC.
I just wanted to know where I should put that fixing orientation code, so I added viewwillappear and some print statements to ListVC, but none of them are called when back from CameraVC to ListVC. I also wrote the same code in CustomTabBarController class, but it is never called...
So, I was wondering where and which file I can write the code for fixing the orientation to trigger when the user came back from CameraVC to ListVC.
Also, if anyone can explain why this is happening, please let me know.
The problem seems to be the way View controllers are presented now. Since the bottom view controller never actually leaves view will appear isn't called when dismissing anymore. I'm pretty sure it did in the past, but now that controllers present as overlays I guess it doesn't anymore.
However, you are in a navigation stack so if you just use that you'll get a free back button and viewWillAppear will get called.
let cameraVC = storyboard.instantiateViewController(identifier: "Camera")
self.navigationController?.pushViewController(cameraVC, animated: true)
As I said you'll get a free back button on cameraVC and that will pop cameraVC off the stack for you.
If you want to keep from using the navigationController for some reason take a look at
https://developer.apple.com/documentation/uikit/uicontentcontainer/1621466-viewwilltransition
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
print(size)
}
The reason is about the change they had made in iOS13 (or 14). Before the default modal presentation mode (it seems that you are presenting modally) was full screen, now they are presented over the current context without making the presenting view controller to disappear, that is why willAppear is not called, it' already there.
If you want to change that behavior you can setup a different presentation style as .fullScreen.
I created a custom search bar and embedding it in the navigation bar, it appears but after I push another view controller, the search bar does not get replaced with the title of the pushed view controller. The search bar stays persistent throughout all views, instead of getting replaced with a title. Perfect example is Instagram search tab, you search for a person and click on the cell, their profile is pushed and the search bar is replaced with the custom title, back button, etc.
First VC
self.customSearchBar.tag = 4
self.navigationController?.view.addSubview(customSearchBar)
Second VC
if let nav: UINavigationController = self.navigationController {
if let searchBar = nav.view.viewWithTag(4) {
searchBar.removeFromSuperview()
}
}
You shouldn't place the searchbar inside the navigationcontroller view as this view is the same instance on all pushed viewcontrollers.
Add the searchbar to the the depending view controllers ui.
To add a searchbar on navigationBar, this is the way.
self.navigationController?.navigationBar.addSubview(customSearchBar)
To remove it when you push it to other viewController. Write the following code in the secondVC that is pushed inside it's viewDidLoad() function. Also, set the tag of customSearchBar to any number (TAG)
if let nav: UINavigationController = self.navigationController {
let bar: UINavigationBar = nav.navigationBar
if let searchBar = bar.viewWithTag(TAG) {
searchBar.removeFromSuperview()
}
}
In the question, the customSearchBar is added to self.navigationController.view. To remove it, you can do the following:
if let nav: UINavigationController = self.navigationController {
if let searchBar = nav.view.viewWithTag(TAG) {
searchBar.removeFromSuperview()
}
}
Edit:
Adding and removing a UIViewController's view as a subview of other UIViewController
// for adding
let viewController: ViewController = ViewController()
self.addChildViewController(viewController)
self.view.addSubview(viewController.view)
viewController.view.bounds = self.view.bounds // better to use autolayout here
viewController.didMove(toParentViewController: self)
// for removing
if let vc = self.childViewControllers.last {
vc.willMove(toParentViewController: nil)
vc.view.removeFromSuperview()
vc.removeFromParentViewController()
}
I'm running into an issue where after changing the rootViewController on my UINavigationController and changing it back to my original UINavigationController, a UISplitViewController begins to show both it's master and detail view in a phone device on compact/portrait orientation (so not only on plus size phones, but also others).
Basic overview of architecture:
A TabBarController houses several tabs. One of these tabs is a UISplitViewController. I currently override the following to ensure that the MasterViewController is shown on compact orientations:
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
// this prevents phone from going straight to detail on showing the split view controller
return true
}
This works fine and displays the master on portrait as expected. At any point pressing a button on another tab can create a new UINavigationController instance and display it, in which I'm doing the below to change the rootViewController to the newly created UINavigationController to display:
let appDelegate = UIApplication.shared.delegate
appDelegate?.window??.rootViewController = newNavVC
On dismiss, I'm just swapping the UINavigationController back to the original one through the same code above. However, once I do this one time (create nav/display/dismiss), and I switch my tab back to the one with the UISplitViewController, it changes itself to show a side-by-side master detail view. I didn't know this was possible in portrait mode for compact sizing. I tried changing to any of the 4 preferred display modes in the UISplitViewController, but that didn't fix it.
Below is what it looks like (iPhone 6 simulator), am I missing delegates or misunderstanding collapsing?
Before:
After:
You can replace the the logic that assigned the rootViewController with the code snippet found at this link:
Leaking views when changing rootViewController inside transitionWithView
Basically you just create an extension for the UIWindow class that will set the root view controller correctly.
extension UIWindow {
/// Fix for https://stackoverflow.com/a/27153956/849645
func set(rootViewController newRootViewController: UIViewController, withTransition transition: CATransition? = nil) {
let previousViewController = rootViewController
if let transition = transition {
// Add the transition
layer.add(transition, forKey: kCATransition)
}
rootViewController = newRootViewController
// Update status bar appearance using the new view controllers appearance - animate if needed
if UIView.areAnimationsEnabled {
UIView.animate(withDuration: CATransaction.animationDuration()) {
newRootViewController.setNeedsStatusBarAppearanceUpdate()
}
} else {
newRootViewController.setNeedsStatusBarAppearanceUpdate()
}
/// The presenting view controllers view doesn't get removed from the window as its currently transistioning and presenting a view controller
if let transitionViewClass = NSClassFromString("UITransitionView") {
for subview in subviews where subview.isKind(of: transitionViewClass) {
subview.removeFromSuperview()
}
}
if let previousViewController = previousViewController {
// Allow the view controller to be deallocated
previousViewController.dismiss(animated: false) {
// Remove the root view in case its still showing
previousViewController.view.removeFromSuperview()
}
}
}
Having an issue with my app crashing when I try and set the UITabBarDelegate to self in subclass of UIViewController. I have a UITabBarViewController with several TabItems linked to View Controllers. One of those View Controllers is HomeViewController. I have the following code in HomeViewController:
class HomeViewController: UIViewController, UITabBarDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.tabBar.translucent = false
self.extendedLayoutIncludesOpaqueBars = true
self.tabBarController?.tabBar.delegate = self //This is causing crash
}
}
If I delete the self.tabBarController?.tabBar.delegate = self line everything works fine and my tabBar behaves as expected, but when I re-add that line I get the following crash:
ibc++abi.dylib: terminating with uncaught exception of type NSException
Not entirely sure how to resolve this. Have found some other answers on SO but they still seemed a little unclear on what the process is to make this work.
Thanks!
Your UITabBarViewController is already a delegate to your UITabBar. Instead of making your viewController your tabBarDelegate, use your tabBarViewController and put your logic there.
You can do something like this. In the tabBarViewController's didSelectItem delegate method
override func tabBar(tabBar: UITabBar, didSelectItem item: UITabBarItem)
{
let index = tabBar.items?.indexOf(item)
if (index == /*the required index of HomeViewController*/)
{
let homeVC = self.viewControllers.objectAtIndex(index) as! HomeViewController
homeVC.myMethod()
}
}
My Situation:
There are a View_A (UICollectionViewController&UICollectionViewCell) and View_B (UIViewController). I want to switch to the View_B when I touched one of cell in the View_A with Segue in the StoryBoard.
In the StoryBoard, I connected View_A and View_B with the Push Segue which identifier is SegueToView_B.
And the function of switchViews just worked fine.
My Problem:
With the Push Segue, I do not need to add a BackButton (NavigationItem) to turn back to the View_A, because there is a 'NavigationItem' be crated automatically by system. And I tried other type segues, like Modal, Popover, and the NavigationItem was not created automatically. I want to ask why?
I want to set the specific color, not the default blue, for that NavigationItem which be created by system automatically, but I failed to find it. After that I just set the color in the prepareForSegue(), but it did not work. Please tell how to set the specific color for it?
My Code:
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
self.collectionView?.setPresenting(true, animated: true, completion: nil)
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
self.selectedCard = delegate.otherCards[indexPath.row]
self.performSegueWithIdentifier("SegueToView_B", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
if identifier == "SegueToView_B" {
let myOtherCardViewController = segue.destinationViewController as? View_BViewController
myOtherCardViewController!.otherCard = self.selectedCard
myOtherCardViewController!.navigationItem.backBarButtonItem?.tintColor = UIColor.whiteColor() // Failed to work!!!
myOtherCardViewController?.navigationItem.leftBarButtonItem?.tintColor = UIColor.whiteColor() // Failed to work, too!!!
}
}
}
Thanks for your help.
Ethan Joe
To set the tintColor for that navigation bar:
myOtherCardViewController.navigationBar.tintColor = .whiteColor()
Why there is no Navigationbar when you use the Modal or PopOver? Because thats how Modal and Popover work! You have to create another Navigation controller for the view you are connecting with the Modal segue, like this:
Another technique I am using is, to create a single NavigationController class, and set all the desired properties (color, font etc.) and then link all the NavigationControllers in the Storyboard to that NavigationController class.
With that you wont have to reconfigure every NavigationController.
Your Solution
You can set back button hidden in View_B controller in viewDidLoad method like this.
class View_BViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true;
// Do any additional setup after loading the view.
}
}
To set tint color, you have to create subclass of UINavigationController, and assign that class to your UINavigationController in UIStoryboard
You subclass will look like this, to set tint color,
class navigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
//self.navigationBar.barStyle = UIBarStyle.Default
self.navigationBar.tintColor = UIColor.redColor()
// Do any additional setup after loading the view.
}
//Other stuff
}
May this help you!!