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
})
Related
I want to enable interactive modal dismissal that pans along with a users finger on a fullscreen modally presented view controller .fullscreen.
I've seen that it's fairly trivial to do so on the .pageSheet and the .formSheet which have it built in but have not seen a clear example for the full screen.
I'm guessing I'd need to have a pan gesture added to my vc within the body of it's code and then adjust for the states myself but wondering if anyone knows what exactly needs to be done / if there's a simpler way to do it as it seems much more complicated for the .fullscreen case
It can be done with creating your custom UIPresentationController and UIViewControllerTransitioningDelegate. Lets say we have TestViewController and we want to present SecondViewController with total presentedHeight of 1.0 (fullScreen). Presentation will be triggered with #IBAction func buttonPressed and can be dismissed by dragging controller down (as we are used to it). It would be also nice to add some backgroundEffect to be gradually changed while user is sliding down the SecondViewController (especially when used only presentedHeight of 0.6).
Firstly we define OverlayViewController which will be later superclass of presented SecondViewControllerand will contain UIPanGestureRecognizer.
class OverlayViewController: UIViewController {
var hasSetPointOrigin = false
var pointOrigin: CGPoint?
var delegate: OverlayViewDelegate?
override func viewDidLoad() {
super.viewDidLoad()
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognizerAction))
view.addGestureRecognizer(panGesture)
}
override func viewDidLayoutSubviews() {
if !hasSetPointOrigin {
hasSetPointOrigin = true
pointOrigin = self.view.frame.origin
}
}
#objc func panGestureRecognizerAction(sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: view)
// Not allowing the user to drag the view upward
guard translation.y >= 0 else { return }
let currentPosition = translation.y
let originPos = self.pointOrigin
delegate?.userDragged(draggedPercentage: translation.y/originPos!.y)
// setting x as 0 because we don't want users to move the frame side ways!! Only want straight up or down
view.frame.origin = CGPoint(x: 0, y: self.pointOrigin!.y + translation.y)
if sender.state == .ended {
let dragVelocity = sender.velocity(in: view)
if dragVelocity.y >= 1100 {
self.dismiss(animated: true, completion: nil)
} else {
// Set back to original position of the view controller
UIView.animate(withDuration: 0.3) {
self.view.frame.origin = self.pointOrigin ?? CGPoint(x: 0, y: 400)
self.delegate?.animateBlurBack(seconds: 0.3)
}
}
}
}
}
protocol OverlayViewDelegate: AnyObject {
func userDragged(draggedPercentage: CGFloat)
func animateBlurBack(seconds: TimeInterval)
}
Next we define custom PresentationController
class PresentationController: UIPresentationController {
private var backgroundEffectView: UIView?
private var backgroundEffect: BackgroundEffect?
private var viewHeight: CGFloat?
private let maxDim:CGFloat = 0.6
private var tapGestureRecognizer: UITapGestureRecognizer = UITapGestureRecognizer()
convenience init(presentedViewController: UIViewController,
presenting presentingViewController: UIViewController?,
backgroundEffect: BackgroundEffect = .blur,
viewHeight: CGFloat = 0.6)
{
self.init(presentedViewController: presentedViewController, presenting: presentingViewController)
self.backgroundEffect = backgroundEffect
self.backgroundEffectView = returnCorrectEffectView(backgroundEffect)
self.tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissController))
self.backgroundEffectView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.backgroundEffectView?.isUserInteractionEnabled = true
self.backgroundEffectView?.addGestureRecognizer(tapGestureRecognizer)
self.viewHeight = viewHeight
}
private override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
}
override var frameOfPresentedViewInContainerView: CGRect {
CGRect(origin: CGPoint(x: 0, y: self.containerView!.frame.height * (1-viewHeight!)),
size: CGSize(width: self.containerView!.frame.width, height: self.containerView!.frame.height *
viewHeight!))
}
override func presentationTransitionWillBegin() {
self.backgroundEffectView?.alpha = 0
self.containerView?.addSubview(backgroundEffectView!)
self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) in
switch self.backgroundEffect! {
case .blur:
self.backgroundEffectView?.alpha = 1
case .dim:
self.backgroundEffectView?.alpha = self.maxDim
case .none:
self.backgroundEffectView?.alpha = 0
}
}, completion: { (UIViewControllerTransitionCoordinatorContext) in })
}
override func dismissalTransitionWillBegin() {
self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) in
self.backgroundEffectView?.alpha = 0
}, completion: { (UIViewControllerTransitionCoordinatorContext) in
self.backgroundEffectView?.removeFromSuperview()
})
}
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
}
override func containerViewDidLayoutSubviews() {
super.containerViewDidLayoutSubviews()
presentedView?.frame = frameOfPresentedViewInContainerView
backgroundEffectView?.frame = containerView!.bounds
}
#objc func dismissController(){
self.presentedViewController.dismiss(animated: true, completion: nil)
}
func graduallyChangeOpacity(withPercentage: CGFloat) {
self.backgroundEffectView?.alpha = withPercentage
}
func returnCorrectEffectView(_ effect: BackgroundEffect) -> UIView {
switch effect {
case .blur:
var blurEffect = UIBlurEffect(style: .dark)
if self.traitCollection.userInterfaceStyle == .dark {
blurEffect = UIBlurEffect(style: .light)
}
return UIVisualEffectView(effect: blurEffect)
case .dim:
var dimView = UIView()
dimView.backgroundColor = .black
if self.traitCollection.userInterfaceStyle == .dark {
dimView.backgroundColor = .gray
}
dimView.alpha = maxDim
return dimView
case .none:
let clearView = UIView()
clearView.backgroundColor = .clear
return clearView
}
}
}
extension PresentationController: OverlayViewDelegate {
func userDragged(draggedPercentage: CGFloat) {
graduallyChangeOpacity(withPercentage: 1-draggedPercentage)
switch self.backgroundEffect! {
case .blur:
graduallyChangeOpacity(withPercentage: 1-draggedPercentage)
case .dim:
graduallyChangeOpacity(withPercentage: maxDim-draggedPercentage)
case .none:
self.backgroundEffectView?.alpha = 0
}
}
func animateBlurBack(seconds: TimeInterval) {
UIView.animate(withDuration: seconds) {
switch self.backgroundEffect! {
case .blur:
self.backgroundEffectView?.alpha = 1
case .dim:
self.backgroundEffectView?.alpha = self.maxDim
case .none:
self.backgroundEffectView?.alpha = 0
}
}
}
}
enum BackgroundEffect {
case blur
case dim
case none
}
Create SecondViewController subclassing OverlayViewController:
class SecondViewController: OverlayViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .blue
// Do any additional setup after loading the view.
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
addSlider()
}
func addSlider() {
let sliderWidth:CGFloat = 100
let centerOfScreen = self.view.frame.size.width / 2
let rect = CGRect(x: centerOfScreen - sliderWidth/2, y: 80, width: sliderWidth, height: 10)
let slider = UIView(frame: rect)
slider.backgroundColor = .black
self.view.addSubview(slider)
}
Add showOverlay() function that will be triggered after buttonPressed and conform your presenting UIViewController (TestViewController) to UIViewControllerTransitioningDelegate :
class TestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func buttonPressed(_ sender: Any) {
showOverlay()
}
func showOverlay() {
let secondVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "secondVC") as! SecondViewController
secondVC.modalPresentationStyle = .custom
secondVC.transitioningDelegate = self
self.present(secondVC, animated: true, completion: nil)
}
}
extension TestViewController: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController,
presenting: UIViewController?,
source: UIViewController) -> UIPresentationController?
{
let presentedHeight: CGFloat = 1.0
let controller = PresentationController(presentedViewController: presented,
presenting: presenting,
backgroundEffect: .dim,
viewHeight: presentedHeight)
if let vc = presented as? OverlayViewController {
vc.delegate = controller
}
return controller
}
}
Now we should be able to present SecondViewController with showOverlay() function setting its presentedHeight to 1.0 and .dim background effect. We can dismiss SecondViewController similar to another modal presentations.
I’m trying to create a custom, short menu that slides up from the bottom of the screen and stays at the bottom (like the iOS share sheet). I’m having a hard time trying to figure out how to do it. I tried presenting a view controller as a modal and setting the preferred content size, but it still presents it as full screen. How can I present a short, modal-like overlay?
You could use a UIPresentationController and a UIViewControllerTransitioningDelegate.
As a starting point here a few lines of code:
UIViewControllerTransitioningDelegate
class OverlayTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return OverlayPresentationController(presentedViewController:presented, presenting:presenting)
}
}
UIPresentationController
class OverlayPresentationController: UIPresentationController {
private let dimmedBackgroundView = UIView()
private let height: CGFloat = 200.0
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(backgroundTapped))
self.dimmedBackgroundView.addGestureRecognizer(tapGestureRecognizer)
}
override var frameOfPresentedViewInContainerView: CGRect {
var frame = CGRect.zero
if let containerBounds = containerView?.bounds {
frame = CGRect(x: 0,
y: containerBounds.height - height,
width: containerBounds.width,
height: height)
}
return frame
}
override func presentationTransitionWillBegin() {
if let containerView = self.containerView, let coordinator = presentingViewController.transitionCoordinator {
containerView.addSubview(self.dimmedBackgroundView)
self.dimmedBackgroundView.backgroundColor = .black
self.dimmedBackgroundView.frame = containerView.bounds
self.dimmedBackgroundView.alpha = 0
coordinator.animate(alongsideTransition: { _ in
self.dimmedBackgroundView.alpha = 0.5
}, completion: nil)
}
}
override func dismissalTransitionDidEnd(_ completed: Bool) {
self.dimmedBackgroundView.removeFromSuperview()
}
#objc private func backgroundTapped() {
self.presentedViewController.dismiss(animated: true, completion: nil)
}
}
How to call it
let overlayTransitioningDelegate = OverlayTransitioningDelegate()
#IBAction func onOpenModalOverlay(_ sender: Any) {
let overlayVC = OverlayViewController()
overlayVC.transitioningDelegate = self.overlayTransitioningDelegate
overlayVC.modalPresentationStyle = .custom
self.present(overlayVC, animated: true, completion: nil)
}
Demo
The OverlayViewController is a normal ViewController. Here I used an ugly green background color to make it easier to recognize the overlay.
I have a selector on my UIBarButton referencing a function to segue to another view controller but the function never gets called when clicked on. Through testing breakpoints I can see the function, segueToCartViewController, never gets called.
Thanks in advance!
UIBarButtonItem init
private let reuseIdentifier = "ItemCell"
private let SegueCartIdentifier = "CatalogToCart"
final class CatalogViewController: UICollectionViewController {
//MARK: -properties
var brand: Brand!
var cart: [Item]!
fileprivate let itemsPerRow:CGFloat = 3
fileprivate let sectionInsets = UIEdgeInsets(top: 30, left: 20, bottom: 30, right: 20)
private var cartItem: UIBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "Cart"), style: .plain, target: self, action: #selector(segueToCartViewController(_:)))
var selectedItemIndexPath: IndexPath?{
didSet{
var indexPaths = [IndexPath]()
if let selectedItemIndexPath = selectedItemIndexPath{
indexPaths.append(selectedItemIndexPath)
}
if let oldValue = oldValue{
indexPaths.append(oldValue)
}
collectionView?.performBatchUpdates({
self.collectionView?.reloadItems(at: indexPaths)
}) { completed in
if let selectedItemIndexPath = self.selectedItemIndexPath{
self.collectionView?.scrollToItem(at: selectedItemIndexPath, at: .centeredVertically, animated: true)
}
}
}
}
override func viewDidAppear(_ animated: Bool) {
self.navigationController?.setToolbarHidden(false, animated: false)
let flex = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
self.navigationController?.toolbar.items = [flex,cartItem,flex]
}
}
call for segue
//MARK: CartNavigation
extension CatalogViewController: CartDelegate{
func segueToCartViewController(_ sender: AnyObject){
super.performSegue(withIdentifier: SegueCartIdentifier, sender: sender)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destination = segue.destination as? UINavigationController else{
return
}
cartVC.delegate = self
}
func closeModallyPresentedViewController() {
dismiss(animated: true, completion: nil)
}
}
The target of your UIBarButtonItem is nil because self is nil during it's initialization.
You can initialize it like this instead
final class CatalogViewController: UICollectionViewController {
lazy final private var cartItem: UIBarButtonItem = { [unowned self] in
return UIBarButtonItem(image: #imageLiteral(resourceName: <#T##String#>), style: .plain, target: self, action: #selector(segueToCartViewController(_:)))
}()
override function viewDidAppear(_ animated: Bool) {
//blah blah, the rest of your code
}
}
See here for a good explanation about the value of self during initialization of properties.
I am trying to get a menu controller to display. I think I am meant to call canBecomeAFirstResponder() and override it but it is no longer a function, it is now a computed variable.
I have done the following:
class DrawView: UIView {
func tap(gestureRecognizer: UIGestureRecognizer) {
let menu = UIMenuController.shared
becomeFirstResponder() // This gives the warning "Result of call to becomeFirstResponder() not used"
let deleteItem = UIMenuItem(title: "Delete", action: #selector(DrawView.deleteLine))
menu.menuItems = [deleteItem]
menu.setTargetRect(CGRect(x: point.x, y: point.y, width: 20, height: 20), in: self)
menu.setMenuVisible(true, animated: true)
setNeedsDisplay()
}
func deleteLine(sender: AnyObject) {
}
override var canBecomeFirstResponder: Bool {
return true
}
}
The problem is that when I tap it recognises the tap but the menu doesn't get displayed.
When I first load my application and log in. Everything is fine.
However when I log out, then log back in. The height of my view has been decreased. Here's a screenshot of the bug:
I havn't been able to find the cause of this. Making this quite a difficult question to ask help with as I can't specify the precise section of code causing the issue. But I'll try.
The problematic setup is like so:
I have a containerViewController, with 2 childViewControllers, a menu and a UITabBarController. The UITabBarController has 2 UIViewControllers.
To better explain it, here's a visual representation.
_______________________
App Start ->
NavigationController(rootViewController LandingPageVC)
LandingPageVC -> push -> SignInVC(this is where I login from)
SignInVC -> push -> ContainerViewController(this has my UITabBarController and my menu)
ContainerViewController (sets up my menuTabBarController and menu)
menuTabBarController (this tabBarController is used to switch out my content from the menu)
SidePanelViewController (this is my menu)
ContainerViewController -> push(signing out) -> LandingPageVC
_______________________
Here's how I push the containerViewController when a successful login is called.
let mainTableViewController = ContainerViewController()
mainTableViewController.navigationItem.setHidesBackButton(true, animated: false)
navigationController!.pushViewController(mainTableViewController, animated: true)
menuEnabled = true
here's the function called from the containerViewController I use to log out.
func signOut() {
// Set up the landing page as the main viewcontroller again.
let mainTableViewController = LandingPageVC()
mainTableViewController.navigationItem.setHidesBackButton(true, animated: false)
mainTableViewController.skipView = false
self.navigationController!.pushViewController(mainTableViewController, animated: true)
// Disable menu access
menuEnabled = false
}
by printing the height of ContainerViewController and menuTabBarController, I found that it is the UITabBarController's height that's decreasing and not the ContainerViewController.
Here's the code that has to do with the UITabBarController
import UIKit
import QuartzCore
let menuTabBarController = UITabBarController()
var menuButton = UIBarButtonItem()
var menuEnabled = false
class ContainerViewController: UIViewController, CenterViewControllerDelegate, SidePanelViewControllerDelegate, UIGestureRecognizerDelegate {
func needsSignOut(sender: SidePanelViewController) {
// toggling left panel
self.toggleLeftPanel()
// signing out
self.signOut()
}
var centerViewController: UITabBarController!
var leftViewController: SidePanelViewController?
let centerPanelExpandedOffset: CGFloat = 60
override func viewDidLoad() {
super.viewDidLoad()
menuTabBarController.tabBar.hidden = true
menuButton = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.Plain, target: self, action: "toggleLeftPanel")
if let font = UIFont(name: "FontAwesome", size: 20) {
menuButton.setTitleTextAttributes([NSFontAttributeName: font], forState: UIControlState.Normal)
}
self.navigationItem.leftBarButtonItem = menuButton
//let tabBarController = UITabBarController()
let suggestionsVC = SuggestionsViewController()
let testVC = detaiLSuggestion_VC()
let controllers = [suggestionsVC,testVC]
menuTabBarController.setViewControllers(controllers, animated: false)
centerViewController = menuTabBarController
view.addSubview(menuTabBarController.view)
addChildViewController(menuTabBarController)
//centerNavigationController.didMoveToParentViewController(self)
}
// MARK: CenterViewController delegate methods
func toggleLeftPanel() {
let notAlreadyExpanded = (currentState != .LeftPanelExpanded)
if notAlreadyExpanded {
addLeftPanelViewController()
}
animateLeftPanel(shouldExpand: notAlreadyExpanded)
}
func collapseSidePanels() {
switch (currentState) {
case .LeftPanelExpanded:
toggleLeftPanel()
default:
break
}
}
func addLeftPanelViewController() {
if (leftViewController == nil) {
leftViewController = SidePanelViewController()
leftViewController!.delegate = self
addChildSidePanelController(leftViewController!)
}
}
func addChildSidePanelController(sidePanelController: SidePanelViewController) {
view.insertSubview(sidePanelController.view, atIndex: 0)
addChildViewController(sidePanelController)
sidePanelController.didMoveToParentViewController(self)
}
func animateLeftPanel(#shouldExpand: Bool) {
if (shouldExpand) {
currentState = .LeftPanelExpanded
animateCenterPanelXPosition(targetPosition: CGRectGetWidth(centerViewController.view.frame) - centerPanelExpandedOffset)
} else {
animateCenterPanelXPosition(targetPosition: 0) { finished in
self.currentState = .BothCollapsed
self.leftViewController!.view.removeFromSuperview()
self.leftViewController = nil;
}
}
}
func animateCenterPanelXPosition(#targetPosition: CGFloat, completion: ((Bool) -> Void)! = nil) {
UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .CurveEaseInOut, animations: {
self.centerViewController.view.frame.origin.x = targetPosition
}, completion: completion)
}
Any help deducing where this is coming from or how I can go about fixing it would be greatly appreciated! And again I apologize for the dumb of code. I'll update it further if I am able to rule out parts of it.
rdelmar found a solution for me in chat.
The problem was fixed by specifying the menuTabBarController.view.frame like so:
menuTabBarController.view.frame = self.view.frame