I'm trying to change the title in Product but somehow the navigationItem is different. How come the navigationItem in Container is different compared to the one in Product?
class VC1: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let controller = Container()
let navigation = UINavigationController(rootViewController: controller)
navigationController?.pushViewController(navigation, animated: true)
}
}
class Container: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(navigationItem)
navigationItem.title = "test"
let controller = Product()
controller.didMove(toParentViewController: self)
self.addChildViewController(controller)
view.addSubview(controller.view)
}
}
class Product: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(navigationItem)
navigationItem.title = "" // Doesn't remove the title
}
}
I'm just reading the documentation for navigationItem, and it says this:
This is a unique instance of UINavigationItem created to represent the view controller when it is pushed onto a navigation controller.
However, in your case, the embedded VC is not a direct child of a navigation controller.
So, I tried the following code and it worked. The key part is I overrode navigationItem to return the parent's navigation item if there is a parent view controller.
override var navigationItem: UINavigationItem {
if let parentItem = parent?.navigationItem {
return parentItem
} else {
return super.navigationItem
}
}
Related
I've looked through a few online tutorials, but nothing is working.
That's the code of my viewController:
import UIKit
class ViewController: UINavigationController {
let textView = UITextView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// tried this
self.navigationItem.title = "AAA"
// and this
self.title = "AAA"
// and finally this
self.parent?.title = "AAA"
}
}
I don't understand why this isn't working (I haven't used a navigation bar before)
I didn't change anything in the main.storyboard.
Thanks for your answers.
First of all in your storyboard select your view controller and then
Editor -> Embed In -> Navigation Controller
then in your ViewController class add
self.title = "AAA"
in your viewDidLoad method and your code will look like:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.title = "AAA"
}
}
You need to replace UINavigationController with UIViewController
Select ViewController from the storyboard.
Go to the Editor and Embed with Navigation Controller
1) Select Navigation Item and set title from the storyboard.
2) By Programmatically
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Your Title"
}
}
I use navigation bar with tabbarcontroller. When i push one of my tabs my navigationbar right items are hiding automatically.
How i can move my items to childs controllers?
You can create base view controller and inherit your children classes from base view controller then call super.viewDidLoad()
1- Base controller
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let myButton = UIBarButtonItem(title: "LogOut", style: .done, target: self, action: #selector(self.logoutTapped(_:)))
self.navigationItem.rightBarButtonItem = myButton
}
#objc func logoutTapped(_ sender: UIBarButtonItem) {
print("Logout clicked :) ")
}
}
2- VC one
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}
3- VC two
class ViewController2: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}
Result
I have a splitview controller and the Master view has a button (Note) that pushes the Note view onto the Detail view. The Detail view makes used of navigation controller to help users navigate back and forth among multiple view controllers. Inside those view controllers, I have delegate methods that pushes Note view onto itself. This is what my UI look like :
The Note button works as I expected when the app is initially run. It still works when I tap one of the list elments and traverse to the views at the deeper level. However, it stops working when I go back to the very first view (which was working initially). I'm not sure what is causing this inconsistent behaviour and I appreciate much if you guys could help me figure this out.
This is excerpt of my code :
Master View
protocol ChildViewDelegate: class {
func updateView()
func pushOntoDetailViewNaviController(_ viewName: String)
}
class MasterViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate, GADBannerViewDelegate {
....
#IBAction func noteTapped(_ sender: UIBarButtonItem) {
pushOntoActiveNaviController("NoteGalleryView")
}
private func pushOntoActiveNaviController(_ viewName: String) {
guard let splitView = self.splitViewController else {
return
}
if splitView.viewControllers.count > 1 {
// Push view onto any active detailed view
self.delegate?.pushOntoDetailViewNaviController(viewName)
} else { //If active view is the master view (true for iphone), then push it onto the master view
if let vc = storyboard?.instantiateViewController(withIdentifier: viewName) {
self.navigationController?.pushViewController(vc, animated: true)
}
}
}
....
}
Detail View
class DetailTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, GADBannerViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
masterViewController = (self.splitViewController?.viewControllers.first as! UINavigationController).topViewController as? MasterViewController
masterViewController?.delegate = self
}
extension DetailTableViewController: ChildViewDelegate {
func updateUI() {
....
}
func updateView() {
DispatchQueue.main.async {
self.tableView.reloadData()
self.updateUI()
}
}
func pushOntoDetailViewNaviController(_ viewName: String) {
if let vc = storyboard?.instantiateViewController(withIdentifier: viewName) {
self.navigationController?.pushViewController(vc, animated: true)
}
}
}
In case somebody has the same issue, it's because I'm setting delegate variable inside viewDidLoad which is invoked when view is initially loaded. It isn't invoked when view appears again when user navigates back to the view from deeper view controllers. The delegate variable should have been set inside viewWillAppear.
I am having a bit of an issue with dismissing a modal view presented from a childviewController in a container view. I have a UINavigationController as the rootViewController (MainNavigationController), and present a modal from one of the childViewControllers from the selectedSegmentIndex 1 (secondViewController). The modal is presented fine, but when I dismiss the modal to go back to the secondViewController(a subclass of HomeController) it returns me back to selectedIndex 0, so not the selectedIndex 1 childViewController it was presented from. I would like the modal to dismiss and return the user back to childViewController it was presented from (the secondViewController) and not return back to selectedIndex 0. Thanks in advance!
// NavigationConroller as rootViewController
class MainNavigationController: UINavigationController {
var segmentedController: UISegmentedControl!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let vc1 = TravelersFeedVC()
let vc2 = ProfileVC()
if isLoggedIn() {
// assume user is logged in
let homeController = HomeController()
viewControllers = [homeController]
homeController.firstViewController = vc1
homeController.secondViewController = vc2
} else {
perform(#selector(showLoginController), with: nil, afterDelay: 0.01)
}
}
fileprivate func isLoggedIn() -> Bool {
return UserDefaults.standard.isLoggedIn()
}
func showLoginController() {
let loginController = LoginController()
present(loginController, animated: true, completion: {
// perhaps do something here later
})
}
}
// HomeController as parentViewController
class HomeController: UIViewController, FBSDKLoginButtonDelegate {
// child view controllers to put inside content view
var firstViewController: TravelersFeedVC?
var secondViewController: ProfileVC?
private var activeViewController: UIViewController? {
didSet {
removeInactiveViewController(inactiveViewController: oldValue)
updateActiveViewController()
}
}
private func removeInactiveViewController(inactiveViewController: UIViewController?) {
if let inActiveVC = inactiveViewController {
// call before removing child view controller's view from hierarchy
inActiveVC.willMove(toParentViewController: nil)
inActiveVC.view.removeFromSuperview()
// call after removing child view controller's view from hierarchy
inActiveVC.removeFromParentViewController()
}
}
private func updateActiveViewController() {
if let activeVC = activeViewController {
// call before adding child view controller's view as subview
addChildViewController(activeVC)
activeVC.view.frame = contentView.bounds
contentView.addSubview(activeVC.view)
// call before adding child view controller's view as subview
activeVC.didMove(toParentViewController: self)
}
}
// UI elements
lazy var contentView: UIView = {
let tv = UIView()
tv.backgroundColor = UIColor.purple
tv.translatesAutoresizingMaskIntoConstraints = false
tv.layer.masksToBounds = true
return tv
}()
var segmentedController: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
activeViewController = firstViewController
checkIfUserIsLoggedIn()
view.addSubview(contentView)
setupProfileScreen()
let items = ["Travelers", "Me"]
segmentedController = UISegmentedControl(items: items)
navigationItem.titleView = segmentedController
segmentedController.tintColor = UIColor.black
segmentedController.selectedSegmentIndex = 0
// Add function to handle Value Changed events
segmentedController.addTarget(self, action: #selector(HomeController.segmentedValueChanged(_:)), for: .valueChanged)
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Sign Out", style: .plain, target: self, action: #selector(handleSignOut))
navigationItem.leftBarButtonItem?.tintColor = UIColor.black
}
// reference to collectionViewController
var travelersFeedVC: TravelersFeedVC!
func segmentedValueChanged(_ sender:UISegmentedControl!)
{
switch segmentedController.selectedSegmentIndex {
case 0:
activeViewController = firstViewController
case 1:
activeViewController = secondViewController
default: // Do nothing
break
}
}
// secondViewcontroller in containerView where modal is presented from
class ProfileVC: UIViewController {
// button to present modal
lazy var placesButton: UIButton = {
let customButton = UIButton(type: .system)
customButton.backgroundColor = UIColor.clear
// customButton.frame = CGRect(x: 150, y: 50, width: 120, height: self.view.frame.height)
customButton.setTitle("## of Places", for: .normal)
customButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
customButton.setTitleColor(.white, for: .normal)
customButton.addTarget(self, action: #selector(handleShowPlacesVC), for: .touchUpInside)
return customButton
}()
// function to call to present modal
func handleShowPlacesVC() {
let placesVC = PlacesTableVC()
let navigationController = UINavigationController(rootViewController: placesVC)
present(navigationController, animated: true, completion: nil)
}
// modal view to dismiss
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "back", style: .plain, target: self, action: #selector(handleCancel))
}
// dismiss modal view to return to secondViewController in childViewController containerView
func handleCancel() {
dismiss(animated: true, completion: nil)
}
When closing the modal dialog the viewDidAppear function in MainNavigationController is called. There you set a new homeController with it's childs. This will trigger a viewDidload in the HomeController with setting of firstViewController. Try to set a breakpoint there and you will see it.
I suggest to avoid content creation in viewDidAppear, use viewDidLoad instead.
Another hint: 'dismiss' is defined as: 'Dismisses the view controller that was presented modally by the view controller.' - If you open for instance an alert above your modal vc it closes the alert, not the modal view (self). A correct implementation has to call dismiss on the presenting controller (same controller that opened it): "presentingViewController?.dismiss()"
It works in your code because apple has implemented a fallback for the case that nothing is presented, but it's a trap that cause some headache sometime.
The chances are that although you're calling present from the child view controller, it isn't in fact handling the presentation. From the Apple docs:
The object on which you call this method may not always be the one that handles the presentation. Each presentation style has different rules governing its behavior. For example, a full-screen presentation must be made by a view controller that itself covers the entire screen. If the current view controller is unable to fulfill a request, it forwards the request up the view controller hierarchy to its nearest parent, which can then handle or forward the request.
Since you're keeping a reference of the active view controller, one solution may be to explicitly set the index upon dismissal.
How to create programmatically tabs from any class extended by UIViewController:
class DashboardTabBarController: UITabBarController {
override func viewDidLoad() {
//here
}
...
}
UPDATE SWIFT 5
One example of how to create an UITabBarController programmatically could be like this:
First we create the UIViewControllers that will be the content for each tab of the tab bar interface. For this example we only create one very simple.
class Item1ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.green
self.title = "item1"
print("item 1 loaded")
}
}
Now, the UITabBarController:
We create the new instances of the UIViewControllers that we want to display in the tab bar. Then we create an icon for each instance we have created and then we create an array that contains all UIViewControllers that specify the content for each tab of the tab bar interface. The order of the view controllers in the array corresponds to the display order in the tab bar.
class DashboardTabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let item1 = Item1ViewController()
let icon1 = UITabBarItem(title: "Title", image: UIImage(named: "someImage.png"), selectedImage: UIImage(named: "otherImage.png"))
item1.tabBarItem = icon1
let controllers = [item1] //array of the root view controllers displayed by the tab bar interface
self.viewControllers = controllers
}
//Delegate methods
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
print("Should select viewController: \(viewController.title ?? "") ?")
return true;
}
}
If you are using storyboard for the viewcontrollers then you have to write like this in your tabbarcontroller class.
class CustomTabbarController : UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let firstViewController = FirstViewController()
let navigationController = UINavigationController(rootViewController: firstViewController)
navigationController.title = "First"
navigationController.tabBarItem.image = UIImage.init(named: "map-icon-1")
viewControllers = [navigationController]
if let secondViewController = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController {
let navgitaionController1 = UINavigationController(rootViewController: secondViewController)
navgitaionController1.title = "Second"
navgitaionController1.tabBarItem.image = UIImage.init(named: "second-icon-1")
var array = self.viewControllers
array?.append(navgitaionController1)
self.viewControllers = array
}
}
}
private lazy var tabbarViewController: UITabBarController = {
let tabbarViewController = UITabBarController()
tabbarViewController.setViewControllers([startVC,
offerVC,
benefitsVC,
shopVC,
recipesVC], animated: true)
return tabbarViewController
}()
window?.rootViewController = tabbarViewController