I've created a UINavigationController with a UIToolbar. Inside the UIToolbar there are multiple UIBarButtonItems. The UIToolbar has a subclass which i use to set the toolbar settings and create the UIBarButtonItems.
By pressing a UIBarButtonItem I want to navigate to another ViewController. As you can see in the code below, I've created a function for .addTarget, called "settingsPressed".
//SetToolbar
class ToolbarClass: UIToolbar {
//Set height of toolbar
override func sizeThatFits(_ size: CGSize) -> CGSize {
var size = super.sizeThatFits(size)
size.height = 60
return size
}
//Toolbar settings
override func layoutSubviews() {
super.layoutSubviews()
//Default
self.isTranslucent = false
self.barTintColor = UIColor(red: 48/255, green: 148/255, blue: 172/255, alpha: 1)
//Buttons
//Settings
let settingsBtn = UIButton()
settingsBtn.frame = CGRect(x: 0, y: 0, width: 46, height: 46)
settingsBtn.setImage(UIImage(named: "Settings-Button")?.withRenderingMode(.alwaysOriginal), for: .normal)
settingsBtn.addTarget(self, action: #selector(self.settingsPressed), for: .touchUpInside)
let settingsButton = UIBarButtonItem()
settingsButton.customView = settingsBtn
self.setItems([settingsButton], animated: false)
}
func settingsPressed() {
//How to navigate to a viewcontroller?
}
}
I've found some swift codes to navigate to another viewcontroller, but these codes don't work in my situation because i'm using a subclass. In this case the ".self.storyboard?" doesn't make sense:
let secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "ClassesOverviewViewController") as! ClassesOverviewViewController
self.navigationController?.pushViewController(secondViewController, animated: true)
Your implementation breaks MVC principles and causes the problems which you shouldn't face at all.
You should not add controller logic (creating new VC and navigating to it) into view (UIToolbar and it's subclass are view elements).
First way to fix it: add UIToolbar to storyboard view with added settings bar button item, connect action of bar button item with function from your VC, implement this function to navigate.
Second way to fix it: leave subclass of UIToolbar (it is not preferable if you only adding bar button item in subclass), declare public property for settings bar button item, use target and action to set your VC as target and function from VC as action for settings bar button item, implement this function to navigate.
Related
I have a simple VC:
class ViewController: UIViewController {
lazy var button = UIButton(frame: CGRect(x: view.frame.width/2-100, y: view.frame.height/2-25, width: 200, height: 50))
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
title = "ViewController"
view.addSubview(button)
button.configuration = .tinted()
button.configuration?.title = "Click me"
button.configuration?.baseBackgroundColor = .systemPink
button.configuration?.baseForegroundColor = .systemPink
button.addTarget(self, action: #selector(click), for: .touchUpInside)
}
And the function for the button:
#objc
func click() {
let newVC = ViewController()
newVC.title = "ViewController 2"
newVC.view.backgroundColor = .systemCyan
var copyVCS = self.navigationController!.viewControllers
print("\n-----Copied Stack------\n\(copyVCS)")
copyVCS = copyVCS.dropLast()
copyVCS.append(newVC)
print("\n-----Mutated Stack------\n\(copyVCS)")
self.navigationController!.setViewControllers(copyVCS, animated: true)
print("\n-----New Navigation Stack-----\n\(navigationController!.viewControllers)")
}
Basically, I am testing a bug I have in a larger app I'm working on.
The issue is where I am setting the new navigation stack by calling
self.navigationController?.setViewControllers(copyVCS, animated: true)
Seems like the new navigation stack isn't the same as the copyVCS that I pass to the above method's argument.
The console after clicking the button:
-----Copied Stack------
[<VCBugReproduce.ViewController: 0x149d090f0>] // ✓
-----Mutated Stack------
[<VCBugReproduce.ViewController: 0x149f04440>] // ✓
-----New Navigation Stack-----
[<VCBugReproduce.ViewController: 0x149d090f0>, // ˟
<VCBugReproduce.ViewController: 0x149f04440>]
Is there a reason the new navigation stack isn't the same as the mutated stack? for some reason, the popped ViewController still appears in the navigation stack, but it appears now at the first index of the navigation stack array.
The docs say:
If animations are enabled, this method decides which type of
transition to perform based on whether the last item in the items
array is already in the navigation stack. (either a push or a pop...)
Only one transition is performed, but when that transition
finishes, the entire contents of the stack are replaced with the new
view controllers.
Is it that you're reading the value of viewControllers before the animation transition has completed, and that is before the value has in fact changed to the 'after animation' value.
I am using SecondView programmatically. I click the button in ViewController to open SecondView controller, but now I want to back to ViewController from SecondView. I do not have storyboard in SecondView and I want to click the closeButton to go back to ViewController. My code work but when I click the close button it does not work. Any idea?
import UIKit
class SecondView: UIViewController {
var closeButton = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
closeButton.addTarget(self, action: #selector(dismissActionSheet), for: .touchUpInside)
}
#objc func dismissActionSheet() {
self.navigationController?.popViewController(animated: true)
}
}
You are instantiating a new UIButton, adding a target, but never adding the button to the UI. If you are seeing a button in your UI, it must be one that you added in Interface Builder, not the one you reference here. I suspect your intent was not:
var closeButton = UIButton()
but rather:
#IBOutlet var closeButton: UIButton!
… and hook that button’s outlet in IB.
Or better, rather than adding a target in viewDidLoad, hook up an #IBAction for the button directly in IB:
#IBAction func didTapButton(_ sender: Any) {
navigationController?.popViewController(animated: true)
}
You don't appear to need the closeButton outlet (at least for what you have shared with us thus far).
By the way, popViewController only works if this is embedded in a navigation controller. If you are not using a navigation controller (i.e., you “presented” the view controller rather than “pushing” one), the navigationController will be nil (and navigationController?.popViewController will do nothing). If not using navigation controller, call dismiss rather than popViewController.
If you have created the Second view programmatically:
Why not set a frame to the button and add it to self.view?
eg:
let button = UIButton(frame: CGRect(x: 50, y: 100, width: 80, height: 40))
button.setTitle("Pop VC", for: .normal)
button.addTarget(self, action: #selector(btnTouched), for: .touchUpInside)
self.view.addSubview(button)
then try to pop back:
func btnTouched(sender: UIButton!) {
if let navController = self.navigationController {
navController.popViewController(animated: true)
}
}
I have 2 views and the navigation bar colour for View 1 is white and the navigation bar colour for view 2 is green. So when i navigate to view2 from view1 the color changes. But when i tap back the navigation bar colour changes to white and that is expected, but initially a green overlay stays on and disappears quickly.
STEP 1 (View1)
STEP 2 (View2)
STEP 3 (Moving to View1 from View2)
then it suddenly changed to this
Code that i am using is as follows
For view1
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.navigationBar.isTranslucent = false
navigationController?.navigationBar.barTintColor = UIColor(hexString: "#FFFFFF")
self.navigationController?.isNavigationBarHidden = false
self.navigationItem.hidesBackButton = true
let image = UIImage(named: "navBardTitleLogoBG")
self.navigationItem.titleView = UIImageView(image: image)
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
imageView.contentMode = .center
}
For view2 the code is as follows
override func viewWillAppear(_ animated: Bool) {
UIView.animate(withDuration: 0.5)
{
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.barTintColor = UIColor(hexString: "#14B80E")
self.navigationController?.navigationBar.shadowImage = UIImage()
self.setStatusBarBackgroundColor(color: UIColor(hexString: "#14B80E"))
self.navigationItem.title = "Recipe Book"
self.navigationController?.navigationBar.layoutIfNeeded()
}
Can someone guide me how to get rid of that green overlay thats coming on while tapping the back button.
Thanks in advance
From the way you implement navigationBar (without seeing your code) I assume the way you implement UINavigationController is by manually add it from controller or by dragging navigationBar from storyboard object library instead of embed in NavigationController on your storyboard. If that is correct, you can try from your Xcode menu bar:
Editor > Embed In > NavigationController
It will handle the navigation and navigationBar itself.
If it's not, you can customize the size of your navigationBar/color in viewDidAppear()
I hope it answered your question.
I have a turbolinks-ios app that I've inherited and need to add a native nav.
At first I used the storyboard to embed the main application controller within a navigation controller, but turbolinks has it's own navigation bar so that creates two stacked navigation bars. I then deleted the nav controller from the storyboard, but now I cannot add my button to the default turbolinks nav bar. There's no errors, and if I step through the code it hits every line within my function, but nothing is displayed in the bar. What am I doing wrong?
My function which is called from viewDidLoad() within the UINavigationController class is as follows...
func addSlideMenuButton() {
let callback = #selector(toggleNav)
let btnShowMenu = UIButton(type: UIButtonType.system)
btnShowMenu.setImage(self.defaultMenuImage(), for: UIControlState())
btnShowMenu.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
btnShowMenu.addTarget(self, action: callback, for: UIControlEvents.touchUpInside)
let customBarItem = UIBarButtonItem(customView: btnShowMenu)
navigationController?.navigationItem.rightBarButtonItem = customBarItem
}
Any nudge in the right direction would be greatly appreciated.
You have to call sizeToFit on the customView first.
So add btnShowMenu.sizeToFit just before creating the UIBarButtonItem.
I'm developing an iOS app with swift in which I have a TabBarController with 5 tab bar items. All of them points to a navigation controller and then to a view controller. One of them I want to show a view controller without the tab bar and when the user press cancel it should go back to the previous tab bar item/view that was selected (previously - sorry for the redundancy). They are all linked/referenced by a "Relationship "view controllers" to "name of the view", but I don't have any specific segue or whatsoever.
This is the code for that specific "button" which I call in the viewDidLoad function:
func setupMiddleButton() {
let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: 64, height: 64))
var menuButtonFrame = menuButton.frame
menuButtonFrame.origin.y = self.view.bounds.height - menuButtonFrame.height
menuButtonFrame.origin.x = self.view.bounds.width/2 - menuButtonFrame.size.width/2
menuButton.frame = menuButtonFrame
menuButton.backgroundColor = UIColor.white
menuButton.layer.cornerRadius = menuButtonFrame.height/2
menuButton.setImage(UIImage(named: "klein_fototoestel_2"), for: UIControlState.normal) // 450 x 450px
menuButton.contentMode = .scaleAspectFit
menuButton.addTarget(self, action: #selector(menuButtonAction), for: UIControlEvents.touchUpInside)
self.view.addSubview(menuButton)
self.view.layoutIfNeeded()
}
func menuButtonAction(sender: UIButton) {
self.selectedIndex = 2
}
I tried to perform the behaviour I want by delegating the tab bar controller with the following code but this function is never called when the central button is selected (though the correct view shows up..!):
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
print("the selected index is : \(tabBar.items?.index(of: item))")
}
What I really want to know is what is the correct way to implement that behaviour I want. Remembering that all views have a navigationController before. I read a lot of people suggesting using UserDefaults to store the index of the previous controller but to be honest I really don't think that's appropriate.
Any help is appreciated.
Thanks in advance.
I think you were on the right track - just need to get the correct connections.
In this diagram (it's kinda big - easier to read if you open it in a new tab), you see a "standard" UITabBar structure. The key is putting a default "do-nothing" view controller as the 3rd tab, and then adding a "special" view controller which will be loaded via code:
Then, your "action" function will look something like this:
func menuButtonAction(sender: UIButton) {
// Don't navigate to the tab index
//self.selectedIndex = 2
// instead, load and present the view you really want to see
if let vc = storyboard?.instantiateViewController(withIdentifier: "SpecialVC") as? SpecialViewController {
vc.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
self.present(vc, animated: true, completion: nil)
}
}
You can see and download a working example here: https://github.com/DonMag/SWTabsWithSpecialTab