I'm trying to customize the appearance of a UIBarButtonItem on a UISplitViewController divided into two UINavigationController's (the detail and master views), but keep hitting a brick wall. I first followed the advice here, but have been unable to achieve the desired results.
If if I implement the back button in the viewDidLoad method of my Detail View Controller in this way:
if let svc = splitViewController {
navigationItem.leftBarButtonItem = svc.displayModeButtonItem()
}
I get the default 'Back' button that navigates back to Master View controller. But if I try to customize the button using the following code:
if let svc = splitViewController {
let searchButton = UIBarButtonItem(
image: UIImage(named: "magnifying-glass"),
style: UIBarButtonItemStyle.Plain,
target: svc.displayModeButtonItem().target,
action: svc.displayModeButtonItem().action
)
navigationItem.leftBarButtonItem = searchButton
}
I get the desired icon in the navbar but it does nothing when clicked. I've debugged the action and target in the console and for both the action is "_triggerDisplayModeAction:" and the controller is an instance of the UISplitViewController.
Any ideas on what's going on here?
i suggest you to create and use your custom UIBarButtonItem in prepareForSeque in master view controller. split view controller by default will create new instance of your detail view controller!!! ignore the part with iPhone ios7support, that is another story :-) https://github.com/op183/MasterDetailDemo
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let object = objects[indexPath.row] as String
if let navController = (segue.destinationViewController as? UINavigationController) {
let controller = navController.topViewController as DetailViewController
controller.detailItem = object
let defaultBarButton = splitViewController?.displayModeButtonItem()
let searchButton = UIBarButtonItem(
image: UIImage(named: "magnifying-glass"),
style: UIBarButtonItemStyle.Plain,
target: defaultBarButton.target,
action: defaultBarButton.action
)
controller.navigationItem.leftBarButtonItem = searchButton
controller.navigationItem.leftItemsSupplementBackButton = true
} else {
// iPhone ios7support
(segue.destinationViewController as DetailViewController).detailItem = object
}
}
}
}
Related
I'm struggling with the problem which is when I'm switching view controllers connected with push segue then everything works as expected. The problem is that I have a search bar that executes Table View Controller from code and when I select the cell from that Table View Controller the next view controller is without tab bar.
I am using Table View Controller as a view that displays results from search bar. When I am selecting cell (result from searching) then I am changing view controller showing the results. But this one is done from storyboard.
I know that the Table View Controller executed from code does not "inherits" tab bar from previous controllers through the navigation bar. But this one should be executed from code.
Initialize Search Result Controller
override func viewDidLoad() {
super.viewDidLoad()
self.definesPresentationContext = true
tableView.separatorStyle = .none
self.extendedLayoutIncludesOpaqueBars = true
refreshControlUI = Refresher.configureRefresher()
tableView.refreshControl = refreshControlUI
refreshControlUI.addTarget(self, action: #selector(pullToRefresh), for: .valueChanged)
// Initialize search table
let priceSearchTable = storyboard?.instantiateViewController(withIdentifier: "CoinSearchTable") as! CoinSearchTableViewController
// asignle pricetable as a search results controller
resultSearchController = UISearchController(searchResultsController: priceSearchTable)
resultSearchController?.searchResultsUpdater = priceSearchTable
// Make navigation bar large
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationController?.navigationItem.largeTitleDisplayMode = .never
self.navigationItem.searchController = resultSearchController
// customize search bar
let searchBar = resultSearchController!.searchBar
searchBar.sizeToFit()
searchBar.placeholder = "Search for coins"
resultSearchController?.hidesNavigationBarDuringPresentation = false
resultSearchController?.dimsBackgroundDuringPresentation = true
}
This code is responsible just for passing values to selected view controller.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if shownAllCoins.count > 0 {
let coinString = shownAllCoins[indexPath.row]
picked = PickedCoin(symbols: [coinString])
picked.delegate = self
navigationController?.setNavigationBarHidden(false, animated: true)
picked.getDetail(name: coinString)
coin = picked.coins[0]
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToDetailsFromSearch" {
if let coin = sender as? [Any]{
if let SearchVC = segue.destination as? DetailPriceViewController{
SearchVC.coin = coin[0] as? Coin
SearchVC.isFromSearching = coin[1] as! Bool
}
}
}
}
Also In Detail View Controller I had to create navigation bar programmatically in case if the segue was from the Search Result Controller.
func addNavBar() {
view.backgroundColor = UIColor(hex: 0x001f3e)
let navBar: UINavigationBar = UINavigationBar(frame: CGRect(x: 0, y: 20, width: view.frame.size.width, height: 44))
navBar.barTintColor = UIColor(hex: 0x001f3e)
navBar.isTranslucent = false
self.view.addSubview(navBar);
guard let coin = coin else {return}
let navItem = UINavigationItem(title: "\(coin.symbol)")
let backButton = UIButton(type: .custom)
let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
navBar.titleTextAttributes = textAttributes
saveButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.bookmarks, target: self, action: #selector(self.selectorName(_ :)));
backButton.setTitle("Back", for: .normal)
backButton.setTitleColor(backButton.tintColor, for: .normal) // You can change the TitleColor
backButton.addTarget(self, action: #selector(self.backAction(_:)), for: .touchUpInside)
navItem.leftBarButtonItem = UIBarButtonItem(customView: backButton)
navItem.rightBarButtonItem = saveButton
navBar.setItems([navItem], animated: false)
}
Bellow is screenshot of storyboard connection. This storyboard without navigation bar is of course this SearchResultController (Table View Controller that displays results and switches to the detail controller).
What is the best way to set tab bar as a root controller or something. I just need the tab bar to be in all view controller doesn't matter if the controllers are initialize from storyboard or code.
I am trying all day to fix this but I don't know how..
I appreciate every help!
Thanks!
Ok, the problem was with segues. I tried to pass the value from another Controller, that has nothing in common with previous one so that was obvious that navigation bar and tab bar wasn't there. While dealing with separate serchable view controller, there should be added delegation, to pass the vale back to Main Controller. When interacting with searchable view controller, the procedure to pass the value to another view controller in my case looks like this:
When I start typing on my Main Controller the new Searchable View Controller Appear
When I find the item I needed Im selecting it by didSelectedRowAt
I'm Passing the selected value through the delegate function
Added delegate to Main Controller from where should be done segue
Now it works like it supposed to.
The simple code looks like this:
protocol SearchTableDelegate {
func passSelectedValue(selected stock: String)
}
In Searchable View Controller declare:
var delegate: SearchTableDelegate!
In DidSelectedRowAt:
delegate.passSelectedValue(selected: selectedValue)
Now in Main Controller:
class MainTableViewController: UITableViewController, SearchTableDelegate {...}
And use the function from protocol:
func passSelectedValue(selected value: String) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let destinationVC = storyboard.instantiateViewController(withIdentifier: "details") as? DetailsViewController{
destinationVC.value = value
self.navigationController?.pushViewController(destinationVC, animated: true)
}
}
Also when declaring the SearchableViewController in MainController don't forget to assign delegate from Searchable to self:
searchableTableController.delegate = self
I've created a CustomTabBarController class with 5 buttons that work perfectly fine.
Each button triggers an individual view controller.
Example: home button triggers the homeViewController and shows it on the screen perfectly.
In the homeViewController, I've created a right bar button item with the following code:
let homeButton = UIBarButtonItem(image: UIImage(named: "HomeButton"), style: .plain, target: self, action:#selector(homeViewController.goToHomeVC)
self.navigationItem.rightBarButtonItem = homeButton
func goToHomeVC(sender: UIButton) {
// Go to home page (tab bar index 0)
}
How can I implement my tappedHome function so it can access the tab bar item (Home) so it triggers the homeViewController and shows it on the screen?
Basically, That is not actual UITabBarController so, You have to maintain the push and pop manually.
Here the code of push and pop that will help you to move between your five view-controller classes.
func pushIfRequired(className:AnyClass) {
if (UIViewController.self != className) {
print("Your pushed class must be child of UIViewController")
return
}
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var isPopDone = false
let mainNavigation = UIApplication.shared.delegate?.window??.rootViewController as? UINavigationController
let viewControllers = mainNavigation!.viewControllers
for vc in viewControllers {
if (type(of: vc) == className) {
mainNavigation?.popToViewController(vc, animated: true)
isPopDone = true
break
}
}
if isPopDone == false{
let instanceSignUp = storyboard.instantiateViewController(withIdentifier: NSStringFromClass(className)) // Identifier must be same name as class
mainNavigation?.pushViewController(instanceSignUp, animated: true)
}
}
Uses
pushIfRequired(className: FirstTabVC.self)
If you don't mind checking the parent to get to your CustomTabBarController,
func navButtonPressedAction() {
guard let tabVC = self.parent?.parent as? UITabBarController else { return }
tabVC.selectedIndex = 2 //Index of the tab bar item here
}
Hi I have two View Controller. there is two segue from first vc to the second vc.I have implemented (with code) right bar button and back button and title in second vc in the view did load .when I segue with button I can see them but when I segue by view controller(for choosing table view cell (did select)) I don't see them
how should I fix them???
this is in the second view did load
override func viewDidLoad() {
super.viewDidLoad()
let img = UIImage(named: "delBtn")
if let top = self.navigationController?.navigationBar.topItem {
top.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
}
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: img, style: .plain, target: self, action: #selector(dele))
self.navigationItem.title = "Add/Edit"
pickerS.delegate = self
pickerS.dataSource = self
getMajor()
if preStudent != nil {
receivedData()
}
}
this is segue of first view controller
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let con = controller.fetchedObjects, con.count > 0 {
let obj = con[indexPath.row]
performSegue(withIdentifier: "edit", sender: obj)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "edit" {
if let des = segue.destination as? EditVC {
if let obj = sender as? Student {
des.preStudent = obj
}
}
}
}
Navigation bar is inherited from parent view controller. If your parent view controller is embedded in a navigation controller and you push to a child view controller, your child view controller will have the navigation bar automatically, and thus have the navigation bar items. In your storyboard, check to make sure your segue type is set to show & push. If it's set to present modally, you will lose your navigation bar in your child controller.
I have three view controllers, all with two buttons each on the right and left sides of the navigation bar, as seen below on one of them.
I'm creating these buttons programatically, and instead of writing the code in each respective view controller (VC) I decided to write a Helper class that creates the buttons.
// Note: I am using FontAwesome as a third-party library.
class Helper: NSObject {
static func loadNavBarItems(vc: UIViewController) {
let profileButton = UIBarButtonItem()
let addButton = UIBarButtonItem()
let attributes = [NSFontAttributeName: UIFont.fontAwesome(ofSize: 20)] as [String: Any]
profileButton.setTitleTextAttributes(attributes, for: .normal)
addButton.setTitleTextAttributes(attributes, for: .normal)
profileButton.title = String.fontAwesomeIcon(name: .userCircle)
addButton.title = String.fontAwesomeIcon(name: .plus)
vc.navigationItem.leftBarButtonItem = profileButton
vc.navigationItem.rightBarButtonItem = addButton
}
func segueToProfile(vc: UIViewController) { // I need help here. }
}
I then call Helper.loadNavBarItems(vc: self) from each VC's viewDidLoad().
What I'm trying to do now is to trigger a segue when one of the buttons is pressed (let's assume it's the profile button). So, I need to define profile.action. So, in the Helper class, I have to write a function segueToProfile that takes a view contoller (vc) and runs performSegueWithIdentifier.
The problem is, I'm not fully understanding how to pass in different types of parameters through selectors, and I may be bad at Googling but I cannot find any questions that are close enough to mine for me to understand how to achieve this.
Many thanks in advance for your help.
For reference, this is the structure of my Storyboard.
EDIT: As shown in the storyboard structure screenshot, I've already created a segue from each of the three view controllers to the destination view controller.
To create barButtonItem:
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "😱", style: .plain, target: self, action: #selector(ProfileButtonTapped))
To create action and segue for barButtonItem:
func ProfileButtonTapped() {
print("Button Tapped")
performSegue(withIdentifier: "YourSegueIdentifierName", sender: self)
//If you want pass data while segue you can use prepare segue method
}
Note : To perform segue you have to give segue identifier name from your storyboard.
Output:
Updated:
If you want to connect your destVC without segue you can use below method:
Note: To use below method you have to set storyBoard Id in identity inspector.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let DestVC = storyboard.instantiateViewController(withIdentifier: "DestVcName") as! DestVcName //UINavigationController
self.present(DestVC, animated: true, completion: nil)
i don't know if i understand your problem, but for passing data between viewControllers(embedded in a UINavigationController in your case) using segue, you can do it in:
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if(segue.identifier == "yourIdentifier"){
let navPreview : UINavigationController = segue.destination as! UINavigationController
let preview : YourViewController = navPreview.viewControllers[0] as! YourViewController
}}
To add an action to a UIBarButton you should use one of its initializers.
For example
let profileButton = UIBarButtonItem(title: "Your title", style: .done, target: self, action: #selector("Call your function to push View controller"))
Instead of title , you can also set an Image to that button.
You can create barButtonItems with selectors :
let leftBarButton = UIBarButtonItem(image: image, landscapeImagePhone: image, style: UIBarButtonItemStyle.bordered, target: self, action: #selector(didPressLeftButton))
Where the didPressLeftButton is a function :
func didPressLeftButton(){
print("Did press left button")
}
Here is my code where I register the UIBarButtonItems and its Selector Function.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
var edit = UIBarButtonItem(title: "Edit", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("editTruck"))
let delete = UIBarButtonItem(title: "Delete", style: UIBarButtonItemStyle.Plain, target: self, action: "deleteTruck")
let menu = UIBarButtonItem(image: UIImage(named: "menu"), style: UIBarButtonItemStyle.Plain, target: self.revealViewController(), action: "rightRevealToggle:")
self.navigationItem.rightBarButtonItems = [menu,delete,edit]
self.title = truckName.text
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
self.view.addGestureRecognizer(self.revealViewController().tapGestureRecognizer())
}
func editTruck(){
println("insdie edittruck")
self.performSegueWithIdentifier("editTruck", sender: self)
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?){
println("inside prepare")
if segue.identifier == "editTruck"{
let vc = segue.destinationViewController as? UINavigationController
let topView = vc?.topViewController as! AddTruckViewController
topView.truckNameValue = name.text!
topView.totalFuelupsValue = totalFuelups.text!
topView.totalDistanceTrackedValue = totalDistanceTracked.text!
topView.truckYearValue = truckYear.text!
topView.truckMakeValue = truckMake.text!
topView.truckModelValue = truckModel.text!
topView.truckImagevalue = truckImage.image!
topView.engineMakeValue = engineMake.text!
topView.engineModelValue = engineModel.text!
topView.horsePowerValue = horsePower.text!
topView.axleValue = axle.text!
topView.trailerTypeValue = trailerType.text!
topView.steerTireMakeValue = steerTireMake.text!
topView.steerTireModelValue = steerTireModel.text!
topView.steerTireSizeValue = steerTireSize.text!
topView.driveTireMakeValue = driveTireMake.text!
topView.driveTireModelValue = driveTireModel.text!
topView.driveTireSizeValue = driveTireSize.text!
topView.transmissionMakeValue = transmissionMake.text!
topView.transmissionModelValue = transmissionModel.text!
topView.distanceUnitValue = distanceUnit.text!
topView.volumeUnitValue = volumeUnit.text!
}
}
}
In IB I Control Dragged from the TruckSpecsViewController (the yellow icon at the top since my buttons are created programmatically),where my above code is from, to the Scene where I want to segue to. I added the identifier to that segue of "editTruck". In the code above the performSegueWithIdentifier works but the prepareForSegue doesnt. No errors or anything it just never gets called.
Any help would be much appreciated.
Move the prepareForSegue:sender method to the same level as your editTruck method in your View Controller. The change of scope will fix your problem.
iOS automatically calls the prepareForSegue:sender method on your View Controller either in response to a segue in a Storyboard or by calling performSegueWithIdentifier:sender: in code.
You never call prepareForSegue:sender yourself; you just implement this method in your View Controller.