I currently have three view controllers appended to one single view controller called V1. V1 has a horizontal scrollview which can push the other three views on the screen when scrolled.
I would like to embed a Navigation Bar in each of the three view controllers so that each one can have its own independent bar buttons and titles based on which page is scrolled to.
My problem is determining where I embed the navigation bar. I tried V1 but it is not giving the results I would like. How can I make each view have its own navigation bar?
class ViewController: UIViewController {
#IBOutlet var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
//Changes Navi bar
// navigationController!.navigationBar.barTintColor = UIColor(red: 237/255, green: 15/255, blue: 140/255, alpha: 1)
// navigationController!.navigationBar.barStyle = UIBarStyle.Black
// Do any additional setup after loading the view, typically from a nib.
let v1 = storyboard!.instantiateViewControllerWithIdentifier("A") as! Offers
let v2 : View2 = View2(nibName: "View2", bundle: nil)
let v3 = storyboard!.instantiateViewControllerWithIdentifier("C") as! DiscoveryPage
self.addChildViewController(v1)
self.scrollView.addSubview(v1.view)
v1.didMoveToParentViewController(self)
self.addChildViewController(v2)
self.scrollView.addSubview(v2.view)
v2.didMoveToParentViewController(self)
self.addChildViewController(v3)
self.scrollView.addSubview(v3.view)
v3.didMoveToParentViewController(self)
//Set the frame
var v2Frame : CGRect = v2.view.frame
v2Frame.origin.x = self.view.frame.width
v2.view.frame = v2Frame
var v3Frame : CGRect = v3.view.frame
v3Frame.origin.x = self.view.frame.width * 2
v3.view.frame = v3Frame
self.scrollView.contentSize = CGSizeMake(self.view.frame.width * 3, self.view.frame.height)
}
}
A little explanation here, hope it'll answer your question.
If you're using a UINavigationController to manage your flow of view controllers. Then you don't need to provide custom navigation bar for every view controller, the top left and right bar button item and title of the view controller are part of UINavigationItem and UINavigationItem is a property of UIViewController.
Every time you push a view controller onto a UINavigationController, the navigation controller takes the navigation item from view controller and setup its item onto the navigation bar. The navigation bar is managed by UINavigationController automatically.
You just need to edit and provide your navigation related info in the associated UINavigationItem.
In the storyboard, if you don't see a navigation item associated to a view controller, you can drag an item from objects and drop it inside view controller:
If you click on a navigation item, you can edit these three option from property inspector:
If you drag a UIBarButtonItem from objects to your UINavigationItem associated to your UIViewController, it'll automatically add it to your left bar button.
Related
I'm trying to add a UIView beneath the UINavigationBar in my UINavigationController.
The view will serve as a placeholder for information messages (for example if we are having issues and content is not getting updated).
Adding the view it self and setting it's constraints is not an issue, but it is overlapping the content of the views that is contained in the navigation controller, which is not want I want. How can I set the content of the contained viewcontroller to respect the space which this new view takes up?
The screenshot is showing my custom (orange) view overlapping the content of the viewController that was pushed on to the navigation controller.
Try Subclassing the UINavigationController and then add your orange view's height constraint to it. and call the function whenever you need it
import UIKit
class CustomNavigationController: UINavigationController{
#IBOutlet weak var topViewHeight: NSLayoutConstraint!
func animateHeight(height: CGFloat){
UIView.animate(withDuration: 0.2) {
self.viewControllers.forEach{ vc in
let v = vc.view.frame
vc.view?.frame = CGRect(x: 0, y: height, width: v.width, height: v.height)
}
self.topViewHeight.constant = height
}
}
}
how to use it?
in your VC where you want to show/hide it:
(self.navigationController as? CustomNavigationController)?.animateHeight(height: 50)
I have 2 ViewController for both implemented:
let searchBar = UISearchBar()
func viewDidLoad() {
navigationItem.titleView = searchBar
}
When I push second view controller and try go back with swipe gesture, all my navigation items disappearing irrevocably.
Maybe someone has an idea how to fix it?
Images:
SearchBar First VC
https://imgur.com/QJxflWP.png
SearchBar Second VC
https://imgur.com/FUBo0t6.png
NavigationBar When starting back swipe
https://imgur.com/G2FXrnq.png
A navigation item is associated with the view controller. So if you want search bar in both view controllers you have to set the search bar for both the view controllers.
Also, you can use searchController property to show the search bar in the navigation controller. So that you can show the title and search bar in the navigation bar.
A navigation item is associated with the view controller. So if you want to show search bar in both view controllers you have to add it separately in the viewDidLoad method.
And also you can use searchController property to show the search bar in the navigation bar. So that you can show both title and search bar in the navigation bar.
I finally solve this problem by handling UINavigationBarDelegate methods.
Use your own UINavigationController because UINavigationController is the default delegate of UINavigationBar, i don't need to reassign delegate to other class to handle.
implement these code and get the searchField inside of UISearchBar to minor change its frame.
extension YourNavigationController: UINavigationBarDelegate {
func navigationBar(_ navigationBar: UINavigationBar, shouldPop item:
UINavigationItem) -> Bool {
if let inSearchBar = item.titleView as? UISearchBar,
let searchField = inSearchBar.value(forKey: "searchField") as? UIView {
searchField.frame = CGRect(x: 0, y: 0, width: searchField.frame.width + 1, height: 36)
}
}
}
Why i do this is because i found that if the frame of searchField don't change, then the search bar would disappear. It is not a good way to do but a kind of workaround to show the correct view. In iOS 13, i believe we can check to use searchTextField to replace searchField, a property provided by Apple doc.
I have subclassed UITabBarController to allow for some customization specific to my app. It is the root view controller of my UIWindow and displays itself correctly on launch, and even shows the correct tab's view hierarchy as well.
The problem is with the selected tabbar item's tint color. Inside viewDidLoad of the custom tab bar controller subclass, I have set both the unselected and selected tint colors for the tab bar. See below:
override func viewDidLoad() {
super.viewDidLoad()
tabBar.tintColor = .tabBarItemActiveTint
tabBar.unselectedItemTintColor = .tabBarItemInactiveTint
tabBar.barTintColor = .tabBarBg
let dashboardVC = DashboardViewController.build()
let settingsVC = SettingsTableViewController.build()
let settingsNavC = UINavigationController(rootViewController: settingsVC)
settingsNavC.navigationBar.barStyle = .black
viewControllers = [dashboardVC, settingsNavC]
selectedViewController = dashboardVC
// Accessing the view property of each tab's root view controller forces
// the system to run "viewDidLoad" which will configure the tab icon and
// title in the tab bar.
let _ = dashboardVC.view
let _ = settingsVC.view
}
As you can see, the controller has its child view hierarchies set, and the views are loaded at the bottom so their respective viewDidLoad methods run where I have code that sets the tabBarItem. Here's an example from the dashboard view controller:
tabBarItem = UITabBarItem(title: "Dashboard", image: UIImage(named: Theme.dashboardTabBarIcon), tag: 0)
Everything about this works except for the selected icon and title. When the app launches I can see the tab bar, the first view hierarchy (the dashboard) is visible onscreen and the tabs all function properly. But the dashboard's icon and title are in an unselected state. I have to actually tap the tab bar icon to get the state to change such that it is selected.
Once you tap one of the tabs, the selected state works as normal. The issue is only on the first presentation of the tab bar.
Here is an image showing the initial state of the tab bar on launch. Notice the dashboard icon is not selected, even though it is the presented view controller.
UPDATE
Skaal's answer below solved the problem for me.
For future reference: the key difference between the code presented here in my question and his sample in the answer is that the tabBarItem is set in viewDidLoad of his custom TabBarController class. By contrast, that code was placed within the viewDidLoad method of each constituent view controller class in my project. There must be a timing issue of when things are called that causes the tint color to not be set in one scenario and work properly in the other.
Key Takeaway: If you set up a tab bar controller programmatically, be sure to set your tabBarItem properties early on to ensure tint colors work properly.
You can use :
selectedIndex = 0 // the index of your dashboardVC
instead of selectedViewController
EDIT - Here is a working sample of UITabBarController:
class TabBarController: UITabBarController {
private lazy var firstController: UIViewController = {
let controller = UIViewController()
controller.title = "First"
controller.view.backgroundColor = .lightGray
return controller
}()
private lazy var secondController: UIViewController = {
let controller = UIViewController()
controller.title = "Second"
controller.view.backgroundColor = .darkGray
return controller
}()
private var controllers: [UIViewController] {
return [firstController, secondController]
}
override func viewDidLoad() {
super.viewDidLoad()
tabBar.tintColor = .magenta
tabBar.unselectedItemTintColor = .white
tabBar.barTintColor = .black
firstController.tabBarItem = UITabBarItem(title: "First", image: UIImage(), tag: 0) // replace with your image
secondController.tabBarItem = UITabBarItem(title: "Second", image: UIImage(), tag: 1) // replace with your image
viewControllers = controllers
selectedViewController = firstController
}
}
I'm using this Framework to have a moving UINavigationBar. I have the following problem with every View (since every view has a UITableView or UICollectionView - Yes this bug appears with UICollectionView, too)
The bottom of every Screen is missing in the size of the UINavigationBar.
The controller is a subclass of UIViewController.
open class SLPagingViewSwift: UIViewController, UIScrollViewDelegate
The UIViewControllers are created globally:
var settingsVC: UserSettingsVC?
Instantiated within the class that creates the controller:
appDelegate.window = UIWindow(frame: UIScreen.main.bounds)
settings = settingsStb.instantiateViewController(withIdentifier: "UserSettingsVC") as? UserSettingsV
// among other VCs
self.setItems() //sets the images at the navigationbar
let items = [itemsArray]
let controllers = [arrayOfVCs] as [UIViewController]
controller = SLPagingViewSwift(items: items, controllers: controllers, showPageControl: false)
controller.indexSelected = 1
nav = UINavigationController(rootViewController: controller)
appDelegate.window?.rootViewController = nav
appDelegate.window?.backgroundColor = cachedBlack
appDelegate.window?.makeKeyAndVisible()
The example controller is a UITableViewController. Same bug appears in every other UIViewController with a UITableView as well as in one UITableViewController with a UICollectionView.
What am I missing? Help is very appreciated.
What you need to do is create a global constant and set the UIEdgeInsetsMake(), for example:
let collectionViewInset = UIEdgeInsetsMake(0, 0, 44, 0)
44 is the height of the navigation bar, and you need to start it after the navigationBar, so y = 44.0.
After doing that you need to set:
collectionView.contentInset = collectionViewInset
And that's it, sorted!
My initial view controller is a navigation controller and its root view controller is a UIViewController which conforms to the UIPageViewControllerDataSource protocol. The content for my pages are three unique view controllers which have both a scene in the storyboard and a swift file.
I am hiding the navigation bar in the root view controller's viewDidLoad().
self.navigationController?.navigationBar.hidden = true
When the app launches, the status bar is white and opaque. As soon as I scroll to the next page, it becomes translucent and the background color of the page shows through.
Appearance on Launch
Appearance While Swiping to Next Page
Can someone please help me understand what is happening here and how to fix it? In case it is not obvious, I want the second behavior from launch; the status bar is translucent and the page background shows through.
I have tried
In viewDidLoad(): UIApplication.sharedApplication().statusBarStyle = .LightContent
In the root view controller:
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return .Default
}
Followed by self.setNeedsStatusBarAppearanceUpdate() in viewDidLoad()
In the root view controller:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
self.navigationController?.navigationBar.barStyle = .Default
self.navigationController?.navigationBar.barTintColor = UIColor.clearColor()
}
In viewDidLoad() (above navigationBar.hidden):
self.navigationController?.navigationBar.barStyle = .Default
self.navigationController?.navigationBar.barTintColor = UIColor.clearColor()
As a note, when I remove the navigation controller and just make the root view controller the initial view controller, the status bar appears as expected- translucent.
This question is similar, but none of the solutions worked, it is over a year old, and when I contacted the poster, he said that he thought he put a view underneath where the status bar would be. I'm not sure that I can manage the the view in such a way that it works seamlessly with the scroll aspect of the page view controller.
I found a solution. A friend of mine made a suggestion which led me to start thinking about the frame of the UIPageViewController. This led me to grab the height of the status bar and then adjust the frame down by that much.
In viewDidLoad:
let pageViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PageViewController") as! UIPageViewController
// More pageViewController setup
let statusBarHeight: CGFloat = UIApplication.sharedApplication().statusBarFrame.height
let x = pageViewController.view.bounds.origin.x
let y = pageViewController.view.bounds.origin.y - statusBarHeight
let width = pageViewController.view.bounds.width
let height = pageViewController.view.bounds.height + statusBarHeight
let frame = CGRect(x: x, y: y, width: width, height: height)
pageViewController.view.frame = frame
I still do not understand why this needs to be manually adjusted. (Hopefully someone else will come along with a solution/explanation.) But I hope this bit of code helps someone.