Swift Tab bar hidden but custom button still active - ios

I have a custom tab Bar where i add a button in the middle:
class CustomTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
setupMiddleButton()
}
func setupMiddleButton() {
let numberOfItems = CGFloat(tabBar.items!.count)
let tabBarItemSize = CGSize(width: tabBar.frame.width / numberOfItems, height: tabBar.frame.height)
menuButton.frame = CGRect(x: 0, y: 0, width: tabBarItemSize.width, height: tabBar.frame.size.height)
var menuButtonFrame = menuButton.frame
menuButtonFrame.origin.y = self.view.bounds.height - menuButtonFrame.height - self.view.safeAreaInsets.bottom
menuButtonFrame.origin.x = self.view.bounds.width/2 - menuButtonFrame.size.width/2
menuButton.frame = menuButtonFrame
menuButton.backgroundColor = UIColor.clear
menuButton.addTarget(self, action: #selector(menuButtonAction), for: UIControlEvents.touchUpInside)
self.view.addSubview(menuButton)
self.view.layoutIfNeeded()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
menuButton.frame.origin.y = self.view.bounds.height - menuButton.frame.height - self.view.safeAreaInsets.bottom
}
}
This bar is shown in multiple controller.
However i have a specific controller where i'd like the tab bar to be hidden.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.isHidden = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.tabBarController?.tabBar.isHidden = false
}
This code works fine and the bar is actually hidden.
However If i click in the middle (where the menuButton is added) the button action is called (a segue is performed).
How can I disable the button when hiding the Tab bar?
Thank you for the help!
--------------UPDATE Solution
I am not sure this is the best solution because i'm new to swift, but it seems to work...
in my CustomTabBarController I have added to function:
func hideTabBar() {
self.tabBar.isHidden = true
self.menuButton.isHidden = true
}
func showTabBar() {
self.tabBar.isHidden = false
self.menuButton.isHidden = false
}
the whenever i need to hide/display it i call this functions.
In my case in the controller where i'd like to hide it i do so:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let tabBar = self.tabBarController as! FishBookTabBarController
tabBar.hideTabBar()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
let tabBar = self.tabBarController as! FishBookTabBarController
tabBar.showTabBar()
}

You are adding your button to self.view, so it is not "part of" your tab bar.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.isHidden = true
self.menuButton.isHidden = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.tabBarController?.tabBar.isHidden = false
self.menuButton.isHidden = false
}
That should do it.

Related

How to change Status Bar Style inside ViewContoller if app based on SwiftUI

I'll try to change the status bar style inside the view controller because I want to use a different style depending on ViewController, and I try each of these recommendations How to change Status Bar text color in iOS, and I change option inside Info.plist , also not have an effect, what is wrong?
My code:
import SwiftUI
import UIKit
#main
struct TestAppearanceApp: App {
var body: some Scene {
WindowGroup {
WrapperUIVC_Hello().edgesIgnoringSafeArea(.all)
}
}
}
struct WrapperUIVC_Hello: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<WrapperUIVC_Hello>) -> some UIViewController {
let controller = ViewController()
controller.modalPresentationStyle = .automatic
return controller
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: UIViewControllerRepresentableContext<WrapperUIVC_Hello>) {
}
}
class ViewController: UIViewController {
lazy var button: UIButton = {
let view = UIButton()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .brown
view.addTarget(self, action: #selector(addInteraction(_:)), for: .touchUpInside)
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
view.addSubview(button)
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
button.widthAnchor.constraint(equalToConstant: 100).isActive = true
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
#objc func addInteraction(_ sender: UIButton) {
let vc = ViewControllerTest()
vc.modalPresentationStyle = .fullScreen
present(vc, animated: true)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override func viewDidAppear(_ animated: Bool) {
setNeedsStatusBarAppearanceUpdate()
}
}
class ViewControllerTest: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
setNeedsStatusBarAppearanceUpdate()
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
self.dismiss(animated: true)
}
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .darkContent
}
override func viewDidAppear(_ animated: Bool) {
setNeedsStatusBarAppearanceUpdate()
}
}

Large UINavigationBar resize smoothly when popping back too rootViewController

hey guys I've used large UINavigationBar in a child ViewController and i want to resize my navBar to default size when popping back to rootViewController smoothly.
vc's gif:https://giphy.com/gifs/1P0HwqlIqqMnzibxbH
EDIT
I don't want to remove largeNavBar from parent vc, i only want to disappear it gradually and with animation like app store:https://giphy.com/gifs/YXsTA6I5r0lGik1gC8
here is the child vc code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.initUI()
super.enableLargeNavigationTitle(title: (self.favorty?.sellerProduct?.product?.name)!)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
super.removeTitleImage()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(true)
}
here is the enableLargeNavigationBar function:
func enableLargeNavigationTitle(title: String) {
self.navigationController?.view.backgroundColor = VVUtility.splashBackGroundColor()
self.navigationItem.title = "\(title)".localized()
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor : UIColor.white, NSAttributedStringKey.font : VVUtility.normalFontWithPlusSize(increaseSize: -2.0)]
if #available(iOS 11.0, *) {
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationController?.navigationBar.backgroundColor = VVUtility.splashBackGroundColor()
self.navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedStringKey.foregroundColor : UIColor.white, NSAttributedStringKey.font : VVUtility.normalFontWithPlusSize(increaseSize: 0.0)]
} else {
// Fallback on earlier versions
}
}
disableLargeNavigation function:
func disableLargeNavigationTitle() {
if #available(iOS 11.0, *) {
self.navigationController?.navigationItem.largeTitleDisplayMode = .never
self.navigationController?.navigationBar.prefersLargeTitles = false
} else {
// Fallback on earlier versions
}
}
here is parent vc code:
override func viewDidLoad() {
super.viewDidLoad()
self.initUI()
self.getData()
super.disableLargeNavigationTitle()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.addSubview(searchBarBoxView)
self.timerDelegate?.startTimer()
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.searchBarBoxView.removeFromSuperview()
self.timerDelegate?.stopTimer()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.tabBarController?.delegate = self
super.disableLargeNavigationTitle()
}
This worked for me. Try putting this code in awakeFromNib() for each view controller, with the settings changed as you need.
override func awakeFromNib() {
// Large titles
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = false // This could be true for other view controller
navigationItem.largeTitleDisplayMode = .never // This could be .always for other view controller
navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.black] // Or whatever you want
} else {
// Handle iOS 10 and below (no large titles)
}
}
Alternatively, I think you can do this just in Storyboard, but that didn’t work for me.

UINavigationBar slides away instead of staying on place

I created demo project to show the problem.
We have two view controllers inside UINavigationController.
MainViewController which is the root.
class MainViewController: UIViewController {
lazy var button: UIButton = {
let button = UIButton()
button.setTitle("Detail", for: .normal)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Main"
view.backgroundColor = .blue
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
button.widthAnchor.constraint(equalToConstant: 150).isActive = true
button.heightAnchor.constraint(equalToConstant: 42).isActive = true
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
}
#objc func buttonTapped(_ sender: UIButton) {
navigationController?.pushViewController(DetailViewController(), animated: true)
}
}
And DetailViewController which is pushed.
class DetailViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.setNavigationBarHidden(false, animated: animated)
}
}
As you can see I want to hide UINavigationBar in DetailViewController:
Question
The problem is that, UINavigationBar slides away instead of stay of his place together with whole MainViewController. How can I change that behavior and keep pop gesture?
in your MainViewController add that method
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 0) {
self.navigationController?.setNavigationBarHidden(false, animated: false)
}
}
and replace your method with below method in DetailViewController
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
}
The following code is hacking.
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 0) {
self.navigationController?.setNavigationBarHidden(false, animated: false)
}
}
Do not write this bizarre code, as suggested by #sagarbhut in his post (in this thread).
You have two choices.
Hack
Do not hack.
Use convenience functions like this one
https://developer.apple.com/documentation/uikit/uiview/1622562-transition
Create a custom segue, if you are using storyboards.
https://www.appcoda.com/custom-segue-animations/
Implement the UIViewControllerAnimatedTransitioning protocol
https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning
You can get some great results but I'm afraid you will need to work hard. There are numerous tutorials online that discuss how to implement the above.
Twitter's navigation transition where the pushed ViewController's view seems to take the entire screen "hiding the navigationBar", but still having the pop gesture animation and the navigationBar visible in the pushing ViewController even during the transition animation obviously cannot be achieved by setting the bar's hidden property.
Implementing a custom navigation system is one way to do it but I suggest a simple solution by playing on navigationBar's layer and its zPosition property. You need two steps,
Set the navigationBar's layer zPosition to a value that'd place it under its siblings which include the current visible view controller's view in the navigation stack: navigationController?.navigationBar.layer.zPosition = -1
The pushing VC's viewDidLoad could be a good place to do that.
Now that the navigationBar is placed behind the VC's view, you'll need to adjust the view's frame to make sure it doesn't overlap with the navigationBar (that'd cause navigationBar to be covered). You can use viewWillLayoutSubviews to change the view's origin.y to start under navigationBar's floor (statusBarHeight + navigationBarHeight).
That'll do the job. You don't need to modify the pushed VC unless you wanna add e.g. a custom back button like in the Twitter's profile screen case. The detail controller's view will be on top of navigation bar while letting you keep the pop gesture transition. Below is your sample code modified with this changes:
class MainViewController: UIViewController {
lazy var button: UIButton = {
let button = UIButton()
button.setTitle("Detail", for: .normal)
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Main"
view.backgroundColor = .blue
// Default value of layer's zPosition is 0 so setting it to -1 will place it behind its siblings.
navigationController?.navigationBar.layer.zPosition = -1
// The `view` will be under navigationBar so lets set a background color to the bar
// as the view's backgroundColor to simulate the default behaviour.
navigationController?.navigationBar.backgroundColor = view.backgroundColor
// Hide the back button transition image.
navigationController?.navigationBar.backIndicatorImage = UIImage()
navigationController?.navigationBar.backIndicatorTransitionMaskImage = UIImage()
view.addSubview(button)
addConstraints()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
// Place `view` under navigationBar.
let statusBarPlusNavigationBarHeight: CGFloat = (navigationController?.navigationBar.bounds.height ?? 0)
+ UIApplication.shared.statusBarFrame.height
let viewHeight = UIScreen.main.bounds.height - statusBarPlusNavigationBarHeight
view.frame = CGRect(origin: .zero, size: CGSize(width: view.bounds.width, height: viewHeight))
view.frame.origin.y = statusBarPlusNavigationBarHeight
}
#objc func buttonTapped(_ sender: UIButton) {
navigationController?.pushViewController(DetailViewController(), animated: true)
}
private func addConstraints() {
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
button.widthAnchor.constraint(equalToConstant: 150).isActive = true
button.heightAnchor.constraint(equalToConstant: 42).isActive = true
}
}
class DetailViewController: UIViewController {
// Some giant button to replace the navigationBar's back button item :)
lazy var button: UIButton = {
let b: UIButton = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 80, height: 40)))
b.frame.origin.y = UIApplication.shared.statusBarFrame.height
b.backgroundColor = .darkGray
b.setTitle("back", for: .normal)
b.addTarget(self, action: #selector(DetailViewController.backButtonTapped), for: .touchUpInside)
return b
}()
#objc func backButtonTapped() {
navigationController?.popViewController(animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(button)
}
}
This might be what you're looking for...
Start the NavBar hide / show animations before starting the push / pop:
class MainViewController: UIViewController {
lazy var button: UIButton = {
let button = UIButton()
button.setTitle("Detail", for: .normal)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Main"
view.backgroundColor = .blue
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
button.widthAnchor.constraint(equalToConstant: 150).isActive = true
button.heightAnchor.constraint(equalToConstant: 42).isActive = true
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
}
#objc func buttonTapped(_ sender: UIButton) {
navigationController?.setNavigationBarHidden(true, animated: true)
navigationController?.pushViewController(DetailViewController(), animated: true)
}
}
class DetailViewController: UIViewController {
lazy var button: UIButton = {
let button = UIButton()
button.setTitle("Go Back", for: .normal)
button.backgroundColor = .red
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
button.widthAnchor.constraint(equalToConstant: 150).isActive = true
button.heightAnchor.constraint(equalToConstant: 42).isActive = true
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
}
#objc func buttonTapped(_ sender: UIButton) {
navigationController?.setNavigationBarHidden(false, animated: true)
navigationController?.popViewController(animated: true)
}
}
Use the custom push transition from this post stackoverflow.com/a/5660278/7270113. The in order to eliminate the back gesture (that's what I understand is what you want to do), just kill the navigation stack. You will have to provide an alternative way to exit the DetailViewController, as even if you unhide the navigation controller, the backbitten will be gone since the navigation stack is empty.
#objc func buttonTapped(_ sender: UIButton) {
let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionFade
navigationController?.view.layer.add(transition, forKey: nil)
let storyboard = UIStoryboard(name: "NameOfYourStoryBoard", bundle: .main)
let viewController = storyboard.instantiateViewController(withIdentifier: "IdentifierOfDetailViewController") as! DetailViewController
navigationController?.setViewControllers([viewController], animated: true) // This method will perform a push
}
Your navigation controller will from now on use this transition animation, if you want to remove it you could use
navigationController?.view.layer.removeAllAnimations()

UISegmented Control hides below the UINavigationController

I have a firstViewController which has a UISegmentedControl and SegmentedControl has two tabs button. its all working fine but when i go to next SecondViewController. On SecondViewController there is a link for webView when i push to that webView & then back to SecondViewController and then back to firstViewController by tapping back button. The SegmentedControl leave its position & it moves below the NavigationController
firstViewController Code is :
override func viewDidLoad() {
super.viewDidLoad()
setupMenuBar()
setupViewControllerUI()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setupNavigationBarUI()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewDidAppear(animated)
// to avoid getting a black navigationBar while transition
navigationController?.navigationBar.barTintColor = UIColor.white
self.navigationController?.navigationBar.setBackgroundImage(UIColor.white.convertImage(), for: UIBarMetrics.default)
navigationController!.navigationBar.titleTextAttributes = [ NSFontAttributeName: UIFont.appThemeRegularFontWithSize(20.0), NSForegroundColorAttributeName: UIColor.lightGray]
}
// MARK: - UIViewController Helper Method
func setupViewControllerUI() {
if isFirstVC {
AppUtility.switchToViewController(viewController: firstViewController!, in: self)
segmentedControl.selectedSegmentIndex = 0
} else {
LoadingViewController.sharedLoader.showLoading(self.navigationController!)
AppUtility.switchToViewController(viewController: secondViewController, in: self)
segmentedControl.selectedSegmentIndex = 1
}
}
func setupMenuBar() {
SegmentedControlContainerView.backgroundColor = UIColor.ButtonColorWithAlpha(1.0)
segmentedControl.tintColor = UIColor.white
}
func setupNavigationBarUI() {
navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName : UIColor.white]
navigationController?.navigationBar.barTintColor = UIColor.ButtonColorWithAlpha(1.0)
navigationController?.navigationBar.setBackgroundImage(UIColor.ButtonColorWithAlpha(1.0).convertImage(), for: UIBarMetrics.default)
navigationController?.navigationBar.shadowImage = UIColor.white.withAlphaComponent(0.0).convertImage()
navigationController?.view.backgroundColor = UIColor.ButtonColorWithAlpha(1.0)
segmentedControl.setTitle("first", forSegmentAt: 1)
segmentedControl.setTitle("second", forSegmentAt: 0)
self.navigationController?.navigationBar.tintColor = UIColor.white
navigationController?.navigationBar.barStyle = UIBarStyle.default
self.title = "Screen Name"
navigationController?.navigationBar.setNeedsDisplay()
}

Execute action when back bar button of UINavigationController is pressed

I need to execute an action (emptying an array), when the back button of a UINavigationController is pressed, while the button still causes the previous ViewController on the stack to appear. How could I accomplish this using swift?
Replacing the button to a custom one as suggested on another answer is possibly not a great idea as you will lose the default behavior and style.
One other option you have is to implement the viewWillDisappear method on the View Controller and check for a property named isMovingFromParentViewController. If that property is true, it means the View Controller is disappearing because it's being removed (popped).
Should look something like:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParentViewController {
// Your code...
}
}
In swift 4.2
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParent {
// Your code...
}
}
One option would be implementing your own custom back button. You would need to add the following code to your viewDidLoad method:
- (void) viewDidLoad {
[super viewDidLoad];
self.navigationItem.hidesBackButton = YES;
UIBarButtonItem *newBackButton = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStyleBordered target:self action:#selector(back:)];
self.navigationItem.leftBarButtonItem = newBackButton;
}
- (void) back:(UIBarButtonItem *)sender {
// Perform your custom actions
// ...
// Go back to the previous ViewController
[self.navigationController popViewControllerAnimated:YES];
}
UPDATE:
Here is the version for Swift:
override func viewDidLoad {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true
let newBackButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Bordered, target: self, action: "back:")
self.navigationItem.leftBarButtonItem = newBackButton
}
#objc func back(sender: UIBarButtonItem) {
// Perform your custom actions
// ...
// Go back to the previous ViewController
self.navigationController?.popViewControllerAnimated(true)
}
UPDATE 2:
Here is the version for Swift 3:
override func viewDidLoad {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true
let newBackButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.plain, target: self, action: #selector(YourViewController.back(sender:)))
self.navigationItem.leftBarButtonItem = newBackButton
}
#objc func back(sender: UIBarButtonItem) {
// Perform your custom actions
// ...
// Go back to the previous ViewController
_ = navigationController?.popViewController(animated: true)
}
override func willMove(toParent parent: UIViewController?)
{
super.willMove(toParent: parent)
if parent == nil
{
print("This VC is 'will' be popped. i.e. the back button was pressed.")
}
}
I was able to achieve this with the following :
Swift 3
override func didMoveToParentViewController(parent: UIViewController?) {
super.didMoveToParentViewController(parent)
if parent == nil {
println("Back Button pressed.")
delegate?.goingBack()
}
}
Swift 4
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if parent == nil {
debugPrint("Back Button pressed.")
}
}
No need of custom back button.
If you want to have back button with back arrow you can use an image and code below
backArrow.png backArrow#2x.png backArrow#3x.png
override func viewDidLoad() {
super.viewDidLoad()
let customBackButton = UIBarButtonItem(image: UIImage(named: "backArrow") , style: .plain, target: self, action: #selector(backAction(sender:)))
customBackButton.imageInsets = UIEdgeInsets(top: 2, left: -8, bottom: 0, right: 0)
navigationItem.leftBarButtonItem = customBackButton
}
func backAction(sender: UIBarButtonItem) {
// custom actions here
navigationController?.popViewController(animated: true)
}
I created this (swift) class to create a back button exactly like the regular one, including back arrow. It can create a button with regular text or with an image.
Usage
weak var weakSelf = self
// Assign back button with back arrow and text (exactly like default back button)
navigationItem.leftBarButtonItems = CustomBackButton.createWithText("YourBackButtonTitle", color: UIColor.yourColor(), target: weakSelf, action: #selector(YourViewController.tappedBackButton))
// Assign back button with back arrow and image
navigationItem.leftBarButtonItems = CustomBackButton.createWithImage(UIImage(named: "yourImageName")!, color: UIColor.yourColor(), target: weakSelf, action: #selector(YourViewController.tappedBackButton))
func tappedBackButton() {
// Do your thing
self.navigationController!.popViewControllerAnimated(true)
}
CustomBackButtonClass
(code for drawing the back arrow created with Sketch & Paintcode plugin)
class CustomBackButton: NSObject {
class func createWithText(text: String, color: UIColor, target: AnyObject?, action: Selector) -> [UIBarButtonItem] {
let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FixedSpace, target: nil, action: nil)
negativeSpacer.width = -8
let backArrowImage = imageOfBackArrow(color: color)
let backArrowButton = UIBarButtonItem(image: backArrowImage, style: UIBarButtonItemStyle.Plain, target: target, action: action)
let backTextButton = UIBarButtonItem(title: text, style: UIBarButtonItemStyle.Plain , target: target, action: action)
backTextButton.setTitlePositionAdjustment(UIOffset(horizontal: -12.0, vertical: 0.0), forBarMetrics: UIBarMetrics.Default)
return [negativeSpacer, backArrowButton, backTextButton]
}
class func createWithImage(image: UIImage, color: UIColor, target: AnyObject?, action: Selector) -> [UIBarButtonItem] {
// recommended maximum image height 22 points (i.e. 22 #1x, 44 #2x, 66 #3x)
let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FixedSpace, target: nil, action: nil)
negativeSpacer.width = -8
let backArrowImageView = UIImageView(image: imageOfBackArrow(color: color))
let backImageView = UIImageView(image: image)
let customBarButton = UIButton(frame: CGRectMake(0,0,22 + backImageView.frame.width,22))
backImageView.frame = CGRectMake(22, 0, backImageView.frame.width, backImageView.frame.height)
customBarButton.addSubview(backArrowImageView)
customBarButton.addSubview(backImageView)
customBarButton.addTarget(target, action: action, forControlEvents: .TouchUpInside)
return [negativeSpacer, UIBarButtonItem(customView: customBarButton)]
}
private class func drawBackArrow(frame frame: CGRect = CGRect(x: 0, y: 0, width: 14, height: 22), color: UIColor = UIColor(hue: 0.59, saturation: 0.674, brightness: 0.886, alpha: 1), resizing: ResizingBehavior = .AspectFit) {
/// General Declarations
let context = UIGraphicsGetCurrentContext()!
/// Resize To Frame
CGContextSaveGState(context)
let resizedFrame = resizing.apply(rect: CGRect(x: 0, y: 0, width: 14, height: 22), target: frame)
CGContextTranslateCTM(context, resizedFrame.minX, resizedFrame.minY)
let resizedScale = CGSize(width: resizedFrame.width / 14, height: resizedFrame.height / 22)
CGContextScaleCTM(context, resizedScale.width, resizedScale.height)
/// Line
let line = UIBezierPath()
line.moveToPoint(CGPoint(x: 9, y: 9))
line.addLineToPoint(CGPoint.zero)
CGContextSaveGState(context)
CGContextTranslateCTM(context, 3, 11)
line.lineCapStyle = .Square
line.lineWidth = 3
color.setStroke()
line.stroke()
CGContextRestoreGState(context)
/// Line Copy
let lineCopy = UIBezierPath()
lineCopy.moveToPoint(CGPoint(x: 9, y: 0))
lineCopy.addLineToPoint(CGPoint(x: 0, y: 9))
CGContextSaveGState(context)
CGContextTranslateCTM(context, 3, 2)
lineCopy.lineCapStyle = .Square
lineCopy.lineWidth = 3
color.setStroke()
lineCopy.stroke()
CGContextRestoreGState(context)
CGContextRestoreGState(context)
}
private class func imageOfBackArrow(size size: CGSize = CGSize(width: 14, height: 22), color: UIColor = UIColor(hue: 0.59, saturation: 0.674, brightness: 0.886, alpha: 1), resizing: ResizingBehavior = .AspectFit) -> UIImage {
var image: UIImage
UIGraphicsBeginImageContextWithOptions(size, false, 0)
drawBackArrow(frame: CGRect(origin: CGPoint.zero, size: size), color: color, resizing: resizing)
image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
private enum ResizingBehavior {
case AspectFit /// The content is proportionally resized to fit into the target rectangle.
case AspectFill /// The content is proportionally resized to completely fill the target rectangle.
case Stretch /// The content is stretched to match the entire target rectangle.
case Center /// The content is centered in the target rectangle, but it is NOT resized.
func apply(rect rect: CGRect, target: CGRect) -> CGRect {
if rect == target || target == CGRect.zero {
return rect
}
var scales = CGSize.zero
scales.width = abs(target.width / rect.width)
scales.height = abs(target.height / rect.height)
switch self {
case .AspectFit:
scales.width = min(scales.width, scales.height)
scales.height = scales.width
case .AspectFill:
scales.width = max(scales.width, scales.height)
scales.height = scales.width
case .Stretch:
break
case .Center:
scales.width = 1
scales.height = 1
}
var result = rect.standardized
result.size.width *= scales.width
result.size.height *= scales.height
result.origin.x = target.minX + (target.width - result.width) / 2
result.origin.y = target.minY + (target.height - result.height) / 2
return result
}
}
}
SWIFT 3.0
class CustomBackButton: NSObject {
class func createWithText(text: String, color: UIColor, target: AnyObject?, action: Selector) -> [UIBarButtonItem] {
let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
negativeSpacer.width = -8
let backArrowImage = imageOfBackArrow(color: color)
let backArrowButton = UIBarButtonItem(image: backArrowImage, style: UIBarButtonItemStyle.plain, target: target, action: action)
let backTextButton = UIBarButtonItem(title: text, style: UIBarButtonItemStyle.plain , target: target, action: action)
backTextButton.setTitlePositionAdjustment(UIOffset(horizontal: -12.0, vertical: 0.0), for: UIBarMetrics.default)
return [negativeSpacer, backArrowButton, backTextButton]
}
class func createWithImage(image: UIImage, color: UIColor, target: AnyObject?, action: Selector) -> [UIBarButtonItem] {
// recommended maximum image height 22 points (i.e. 22 #1x, 44 #2x, 66 #3x)
let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
negativeSpacer.width = -8
let backArrowImageView = UIImageView(image: imageOfBackArrow(color: color))
let backImageView = UIImageView(image: image)
let customBarButton = UIButton(frame: CGRect(x: 0, y: 0, width: 22 + backImageView.frame.width, height: 22))
backImageView.frame = CGRect(x: 22, y: 0, width: backImageView.frame.width, height: backImageView.frame.height)
customBarButton.addSubview(backArrowImageView)
customBarButton.addSubview(backImageView)
customBarButton.addTarget(target, action: action, for: .touchUpInside)
return [negativeSpacer, UIBarButtonItem(customView: customBarButton)]
}
private class func drawBackArrow(_ frame: CGRect = CGRect(x: 0, y: 0, width: 14, height: 22), color: UIColor = UIColor(hue: 0.59, saturation: 0.674, brightness: 0.886, alpha: 1), resizing: ResizingBehavior = .AspectFit) {
/// General Declarations
let context = UIGraphicsGetCurrentContext()!
/// Resize To Frame
context.saveGState()
let resizedFrame = resizing.apply(CGRect(x: 0, y: 0, width: 14, height: 22), target: frame)
context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY)
let resizedScale = CGSize(width: resizedFrame.width / 14, height: resizedFrame.height / 22)
context.scaleBy(x: resizedScale.width, y: resizedScale.height)
/// Line
let line = UIBezierPath()
line.move(to: CGPoint(x: 9, y: 9))
line.addLine(to: CGPoint.zero)
context.saveGState()
context.translateBy(x: 3, y: 11)
line.lineCapStyle = .square
line.lineWidth = 3
color.setStroke()
line.stroke()
context.restoreGState()
/// Line Copy
let lineCopy = UIBezierPath()
lineCopy.move(to: CGPoint(x: 9, y: 0))
lineCopy.addLine(to: CGPoint(x: 0, y: 9))
context.saveGState()
context.translateBy(x: 3, y: 2)
lineCopy.lineCapStyle = .square
lineCopy.lineWidth = 3
color.setStroke()
lineCopy.stroke()
context.restoreGState()
context.restoreGState()
}
private class func imageOfBackArrow(_ size: CGSize = CGSize(width: 14, height: 22), color: UIColor = UIColor(hue: 0.59, saturation: 0.674, brightness: 0.886, alpha: 1), resizing: ResizingBehavior = .AspectFit) -> UIImage {
var image: UIImage
UIGraphicsBeginImageContextWithOptions(size, false, 0)
drawBackArrow(CGRect(origin: CGPoint.zero, size: size), color: color, resizing: resizing)
image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
private enum ResizingBehavior {
case AspectFit /// The content is proportionally resized to fit into the target rectangle.
case AspectFill /// The content is proportionally resized to completely fill the target rectangle.
case Stretch /// The content is stretched to match the entire target rectangle.
case Center /// The content is centered in the target rectangle, but it is NOT resized.
func apply(_ rect: CGRect, target: CGRect) -> CGRect {
if rect == target || target == CGRect.zero {
return rect
}
var scales = CGSize.zero
scales.width = abs(target.width / rect.width)
scales.height = abs(target.height / rect.height)
switch self {
case .AspectFit:
scales.width = min(scales.width, scales.height)
scales.height = scales.width
case .AspectFill:
scales.width = max(scales.width, scales.height)
scales.height = scales.width
case .Stretch:
break
case .Center:
scales.width = 1
scales.height = 1
}
var result = rect.standardized
result.size.width *= scales.width
result.size.height *= scales.height
result.origin.x = target.minX + (target.width - result.width) / 2
result.origin.y = target.minY + (target.height - result.height) / 2
return result
}
}
}
In Swift 5 and Xcode 10.2
Please don't add custom bar button item, use this default behaviour.
No need of viewWillDisappear, no need of custom BarButtonItem etc...
It's better to detect when the VC is removed from it's parent.
Use any one of these two functions
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
if parent == nil {
callStatusDelegate?.backButtonClicked()//Here write your code
}
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if parent == nil {
callStatusDelegate?.backButtonClicked()//Here write your code
}
}
If you want stop default behaviour of back button then add custom BarButtonItem.
If you are using navigationController then add the UINavigationControllerDelegate protocol to class and add the delegate method as follows:
class ViewController:UINavigationControllerDelegate {
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController,
animated: Bool) {
if viewController === self {
// do here what you want
}
}
}
This method is called whenever the navigation controller will slide to a new screen. If the back button was pressed, the new view controller is ViewController itself.
You can subclass UINavigationController and override popViewController(animated: Bool). Beside being able to execute some code there you can also prevent the user from going back altogether, for instance to prompt to save or discard his current work.
Sample implementation where you can set a popHandler that gets set/cleared by pushed controllers.
class NavigationController: UINavigationController
{
var popHandler: (() -> Bool)?
override func popViewController(animated: Bool) -> UIViewController?
{
guard self.popHandler?() != false else
{
return nil
}
self.popHandler = nil
return super.popViewController(animated: animated)
}
}
And sample usage from a pushed controller that tracks unsaved work.
let hasUnsavedWork: Bool = // ...
(self.navigationController as! NavigationController).popHandler = hasUnsavedWork ?
{
// Prompt saving work here with an alert
return false // Prevent pop until as user choses to save or discard
} : nil // No unsaved work, we clear popHandler to let it pop normally
As a nice touch, this will also get called by interactivePopGestureRecognizer when the user tries to go back using a swipe gesture.
NO
override func willMove(toParentViewController parent: UIViewController?) { }
This will get called even if you are segueing to the view controller in which you are overriding this method. In which check if the "parent" is nil of not is not a precise way to be sure of moving back to the correct UIViewController. To determine exactly if the UINavigationController is properly navigating back to the UIViewController that presented this current one, you will need to conform to the UINavigationControllerDelegate protocol.
YES
note: MyViewController is just the name of whatever UIViewController you want to detect going back from.
1) At the top of your file add UINavigationControllerDelegate.
class MyViewController: UIViewController, UINavigationControllerDelegate {
2) Add a property to your class that will keep track of the UIViewController that you are segueing from.
class MyViewController: UIViewController, UINavigationControllerDelegate {
var previousViewController:UIViewController
3) in MyViewController's viewDidLoad method assign self as the delegate for your UINavigationController.
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.delegate = self
}
3) Before you segue, assign the previous UIViewController as this property.
// In previous UIViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "YourSegueID" {
if let nextViewController = segue.destination as? MyViewController {
nextViewController.previousViewController = self
}
}
}
4) And conform to one method in MyViewController of the UINavigationControllerDelegate
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
if viewController == self.previousViewController {
// You are going back
}
}
In my case the viewWillDisappear worked best. But in some cases one has to modify the previous view controller. So here is my solution with access to the previous view controller and it works in Swift 4:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if isMovingFromParentViewController {
if let viewControllers = self.navigationController?.viewControllers {
if (viewControllers.count >= 1) {
let previousViewController = viewControllers[viewControllers.count-1] as! NameOfDestinationViewController
// whatever you want to do
previousViewController.callOrModifySomething()
}
}
}
}
Before leave current controller I need to show alert. So I did it this way:
Add extention to UINavigationController with UINavigationBarDelegate
Add selector to your controller navigationShouldPopOnBack(completion:)
It's worked)
extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
if let items = navigationBar.items, viewControllers.count < items.count {
return true
}
let clientInfoVC = topViewController as? ClientInfoVC
if clientInfoVC?.responds(to: #selector(clientInfoVC?.navigationShouldPopOnBack)) ?? false {
clientInfoVC?.navigationShouldPopOnBack(completion: { isAllowPop in
if isAllowPop {
DispatchQueue.main.async {
self.popViewController(animated: true)
}
}
})
}
DispatchQueue.main.async {
self.popViewController(animated: true)
}
return false
}
}
#objc func navigationShouldPopOnBack(completion: #escaping (Bool) -> ()) {
let ok = UIAlertAction(title: R.string.alert.actionOk(), style: .default) { _ in
completion(true)
}
let cancel = UIAlertAction(title: R.string.alert.actionCancel(), style: .cancel) { _ in
completion(false)
}
let alertController = UIAlertController(title: "", message: R.string.alert.contractMessage(), preferredStyle: .alert)
alertController.addAction(ok)
alertController.addAction(cancel)
present(alertController, animated: true, completion: nil)
}
When back button is pressed, ignore interactive pop with screen edge gesture.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if isMovingFromParent, transitionCoordinator?.isInteractive == false {
// code here
}
}
It's not difficult as we thing. Just create a frame for UIButton with clear background color, assign action for the button and place over the navigationbar back button. And finally remove the button after use.
Here is the Swift 3
sample code done with UIImage instead of UIButton
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView()
imageView.backgroundColor = UIColor.clear
imageView.frame = CGRect(x:0,y:0,width:2*(self.navigationController?.navigationBar.bounds.height)!,height:(self.navigationController?.navigationBar.bounds.height)!)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(back(sender:)))
imageView.isUserInteractionEnabled = true
imageView.addGestureRecognizer(tapGestureRecognizer)
imageView.tag = 1
self.navigationController?.navigationBar.addSubview(imageView)
}
write the code need to be executed
func back(sender: UIBarButtonItem) {
// Perform your custom actions}
_ = self.navigationController?.popViewController(animated: true)
}
Remove the subView after action is performed
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
for view in (self.navigationController?.navigationBar.subviews)!{
if view.tag == 1 {
view.removeFromSuperview()
}
}
This is my solution
extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
if let shouldBlock = self.topViewController?.shouldPopFromNavigation() {
return shouldBlock
}
return true
}
}
extension UIViewController {
#objc func shouldPopFromNavigation() -> Bool {
return true
}
}
In your view controller, you can handle like this:
#objc override func shouldPopFromNavigation() -> Bool {
// Your dialog, example UIAlertViewController or whatever you want
return false
}
Swift 4.2:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParent {
// Your code...
}
}
For Swift 5, we can check it in view will disappear
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParent {
delegate?.passValue(clickedImage: selectedImage)
}
}
Swift 3:
override func didMove(toParentViewController parent: UIViewController?) {
super.didMove(toParentViewController: parent)
if parent == nil{
print("Back button was clicked")
}
}
just do control + drag the bar item to below func. work like charm
#IBAction func done(sender: AnyObject) {
if((self.presentingViewController) != nil){
self.dismiss(animated: false, completion: nil)
print("done")
}
}
Swift 5 __ Xcode 11.5
In my case I wanted to make an animation, and when it finished, go back.
A way to overwrite the default action of the back button
and call your custom action is this:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setBtnBack()
}
private func setBtnBack() {
for vw in navigationController?.navigationBar.subviews ?? [] where "\(vw.classForCoder)" == "_UINavigationBarContentView" {
print("\(vw.classForCoder)")
for subVw in vw.subviews where "\(subVw.classForCoder)" == "_UIButtonBarButton" {
let ctrl = subVw as! UIControl
ctrl.removeTarget(ctrl.allTargets.first, action: nil, for: .allEvents)
ctrl.addTarget(self, action: #selector(backBarBtnAction), for: .touchUpInside)
}
}
}
#objc func backBarBtnAction() {
doSomethingBeforeBack { [weak self](isEndedOk) in
if isEndedOk {
self?.navigationController?.popViewController(animated: true)
}
}
}
private func doSomethingBeforeBack(completion: #escaping (_ isEndedOk:Bool)->Void ) {
UIView.animate(withDuration: 0.25, animations: { [weak self] in
self?.vwTxt.alpha = 0
}) { (isEnded) in
completion(isEnded)
}
}
Or you can use this method one time to explore the NavigationBar view hierarchy, and get the indexes to access to the _UIButtonBarButton view, cast to UIControl, remove the target-action, and add your custom targets-actions:
private func debug_printSubviews(arrSubviews:[UIView]?, level:Int) {
for (i,subVw) in (arrSubviews ?? []).enumerated() {
var str = ""
for _ in 0...level {
str += "\t"
}
str += String(format: "%2d %#",i, "\(subVw.classForCoder)")
print(str)
debug_printSubviews(arrSubviews: subVw.subviews, level: level + 1)
}
}
// Set directly the indexs
private func setBtnBack_method2() {
// Remove or comment the print lines
debug_printSubviews(arrSubviews: navigationController?.navigationBar.subviews, level: 0)
let ctrl = navigationController?.navigationBar.subviews[1].subviews[0] as! UIControl
print("ctrl.allTargets: \(ctrl.allTargets)")
ctrl.removeTarget(ctrl.allTargets.first, action: nil, for: .allEvents)
print("ctrl.allTargets: \(ctrl.allTargets)")
ctrl.addTarget(self, action: #selector(backBarBtnAction), for: .touchUpInside)
print("ctrl.allTargets: \(ctrl.allTargets)")
}
override public func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.topItem?.title = GlobalVariables.selectedMainIconName
let image = UIImage(named: "back-btn")
image = image?.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(image: image, style: UIBarButtonItemStyle.Plain, target: self, action: #selector(Current[enter image description here][1]ViewController.back) )
}
func back() {
self.navigationController?.popToViewController( self.navigationController!.viewControllers[ self.navigationController!.viewControllers.count - 2 ], animated: true)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingToParent {
//your code backView
}
}
Try this .
self.navigationItem.leftBarButtonItem?.target = "methodname"
func methodname ( ) {
// enter code here
}
Try on this too.
override func viewWillAppear(animated: Bool) {
//empty your array
}
As I understand you want to empty your array as you press your back button and pop to your previous ViewController let your Array which you loaded on this screen is
let settingArray = NSMutableArray()
#IBAction func Back(sender: AnyObject) {
self. settingArray.removeAllObjects()
self.dismissViewControllerAnimated(true, completion: nil)
}
Here is the simplest possible Swift 5 solution that doesn't require you to create a custom back button and give up all that UINavigationController left button functionality you get for free.
As Brandon A recommends above, you need need to implement UINavigationControllerDelegate in the view controller you want to interact with before returning to it. A good way is to create an unwind segue that you can perform manually or automatically and reuse the same code from a custom done button or the back button.
First, make your view controller of interest (the one you want to detect returning to) a delegate of the navigation controller in its viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
}
Second, add an extension at the bottom of the file that overrides navigationController(willShow:animated:)
extension PickerTableViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController,
willShow viewController: UIViewController,
animated: Bool) {
if let _ = viewController as? EditComicBookViewController {
let selectedItemRow = itemList.firstIndex(of: selectedItemName)
selectedItemIndex = IndexPath(row: selectedItemRow!, section: 0)
if let selectedCell = tableView.cellForRow(at: selectedItemIndex) {
performSegue(withIdentifier: "PickedItem", sender: selectedCell)
}
}
}
}
Since your question included a UITableViewController, I included a way to get the index path of the row the user tapped.
I accomplished this by calling/overriding viewWillDisappear and then accessing the stack of the navigationController like this:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
let stack = self.navigationController?.viewControllers.count
if stack >= 2 {
// for whatever reason, the last item on the stack is the TaskBuilderViewController (not self), so we only use -1 to access it
if let lastitem = self.navigationController?.viewControllers[stack! - 1] as? theViewControllerYoureTryingToAccess {
// hand over the data via public property or call a public method of theViewControllerYoureTryingToAccess, like
lastitem.emptyArray()
lastitem.value = 5
}
}
}
You can do something in your Viewcontroller like
override func navigationShouldPopOnBackButton() -> Bool {
self.backAction() //Your action you want to perform.
return true
}
For complete answer use
Detecting when the 'back' button is pressed on a navbar
My preference was to override the popViewController in the Navigation Controller. The advantages of this is:
Your app keeps the default Back Button look and animations, and you don't have to manage it. This is particularly helpful if a user has Large Text set on their phone, since the default back button will increase or decrease in size based on the user settings.
You can stop the view from popping altogether, unlike using viewWillDisappear.
First, create a custom Navigation Controller class (and be sure to assign it to the Navigation Controller in your Story Board or wherever your navigation controller is created):
class NavControllerWithBackButtonOverride: UINavigationController {
var backButtonOverride: (() -> Void)? = nil
override func popViewController(animated: Bool) -> UIViewController? {
if backButtonOverride != nil {
//if anything is assigned to the backButtonOverride the override will run
self.backButtonOverride!()
return nil
} else {
//otherwise the default popViewController will run
return super.popViewController(animated: animated)
}
}
}
Then enable/disable the override in your View Controller by assigning a value to the backButtonOverride variable:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.enableCustomBackButton()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.disableCustomBackButton()
}
/**
Custom Back Button
*/
func customBackButtonAction() {
print("DO THIS INSTEAD")
}
func enableCustomBackButton() {
if let nav = self.navigationController as? NavControllerWithBackButtonOverride {
nav.backButtonOverride = { self.customBackButtonAction() }
nav.interactivePopGestureRecognizer?.isEnabled = false
}
}
func disableCustomBackButton() {
if let nav = self.navigationController as? NavControllerWithBackButtonOverride {
nav.backButtonOverride = nil
nav.interactivePopGestureRecognizer?.isEnabled = true
}
}
Note: I also disabled interactivePopGestureRecognizer because it was causing issues with the custom setup.
Swift 5+ (Back button with alert control)
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true
let newBackButton = UIBarButtonItem(title: "<Back", style: UIBarButtonItem.Style.plain, target: self, action: #selector(PGWebViewController.back(sender:)))
self.navigationItem.leftBarButtonItem = newBackButton
}
#objc func back(sender: UIBarButtonItem) {
let alert = UIAlertController(title: "Warning!", message: "Your payment process is not completed yet. Do you want to go back?", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: { action in
_ = self.navigationController?.popViewController(animated: true)
})
alert.addAction(ok)
let cancel = UIAlertAction(title: "Cancel", style: .default, handler: { action in
})
alert.addAction(cancel)
DispatchQueue.main.async(execute: {
self.present(alert, animated: true)
})}
You can simply remove unnecessary controllers from the stack, something like this:
self.navigationController?.viewControllers.removeAll(where: {
$0 is FirstViewController || $0 is SecondWithPinController
})

Resources