I'm trying to check if the back button on my view controller was pressed but I'm having a hard time detecting this in Swift.
With this code:
if (contains(self.navigationController?.viewControllers, self)) {
println("Back button not pressed")
} else {
self.updateSearchQueryModel()
}
I am getting the error:
Could not find an overload for contains that accepts the supplied arguments.
I did get the result that I wanted in another fashion but I am still confused as to why this error is happening.
Why is this happening? Can I not check if self exists in an array?
Source of original code in Objective C that I couldn't translate to Swift:
Setting action for back button in navigation controller
-(void) viewWillDisappear:(BOOL)animated {
if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) {
// back button was pressed. We know this is true because self is no longer
// in the navigation stack.
}
[super viewWillDisappear:animated];
}
Please do not tell me how to detect that the back button was pressed. I already figured that out here.
Source of solution to objective: Detecting when the 'back' button is pressed on a navbar
If you look at the declaration of the viewControllers property, you notice that it's [AnyObject]! and not [UIViewController]!.
The contains function requires that the sequence element implements the Equatable protocol, which AnyObject doesn't.
The solution is to make an explicit downcast of that array, using optional binding:
if let viewControllers = self.navigationController?.viewControllers as? [UIViewController] {
if (contains(viewControllers, self)) {
println("Back button not pressed")
} else {
self.updateSearchQueryModel()
}
}
I'm new in Swift but try this:
if let controllers = self.navigationController?.viewControllers as? [UIViewController] {
if contains(controllers, self) {
DLog("!")
}
}
You're getting error because you're passing optional as contains()'s first argument
Related
When we perform a segue, it is easy to get the destination view controller so we can pass data to it using the prepare(for:) method.
I'd like to know the correct way to do this when the back button of a navigation controller is pressed.
I've managed to piece together something that works, but it feels wrong to be using my knowledge of the hierarchy of the view controllers within the navigation controller rather than getting the destination dynamically. Maybe i'm overthinking it?
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
// This method is called more than once - parent is only nil when back button is pressed
if (parent == nil) {
guard let destination = self.navigationController?.viewControllers.first as? MyTableViewController else {
return
}
print("destination is \(destination)")
// set delegate of my networking class to destination here
// call async method on networking class to retrieve updated data for my table view
}
}
As a general rule, trying to do that makes for too tight of a coupling. The VC you are navigating away from shouldn't need to know about where it's headed.
Probably better to implement your networking class methods in viewWillAppear or viewDidAppear inside your MyTableViewController class.
However, you could give this a try:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// make sure we're in a navigation controller
guard let navC = navigationController else { return }
if navC.viewControllers.firstIndex(of: self) != nil {
// we're still in the nav stack, so we've either
// pushed to another VC, or presented a full-screen VC
// (or maybe something else)
return
}
// we've been removed from the nav stack
// so user tapped Back button
print("back button tapped")
// see if we're navigating back to an instance of MyTableViewController
guard let destVC = navC.viewControllers.last as? MyTableViewController else {
return
}
// do something with destVC...
print("on our way to", destVC)
}
Note: I just did this as an example... it would need plenty of testing and possibly additional case handling.
I connected UITableViews with Show segue. I want to change some variable of destination tableView whenever I tap the Back button on navigation bar. Let's say I'm not allowed to create a new back button, how can I detect/call the destination tableView when the back button is tapped? Like in prepareforsegue we have segue.destination, is there some thing like "backbutton.destination"?
Thanks!
What you should be doing here (I think) is not trying to find the destination of the back button but passing in the interested object when going forwards.
On you detail view you could have something like...
protocol DetailViewDelegate {
func detailViewDidChange()
}
Then in the detail view controller...
class DetailViewController: UIViewController {
weak var delegate: DetailViewDelegate?
// you could call this from viewWillDisappear or something
func doThisWhenYouWantToUpdateTheTableView() {
delegate?.detailViewDidChange()
}
}
Now in the tableview...
func prepareForSegue(...) {
if let vc = segue.destination as? DetailViewController {
vc.delegate = self
}
}
and...
extension MyTableViewController: DetailViewDelegate {
func detailViewDidChange() {
// respond to the change here.
}
}
I need to check if the back button is pressed in Swift. The action does not fire programmatically, so the approach appears to be to check if self is in the viewcontroller stack. I think the solution is here:
How to check for self with contains() in Swift?
However for each of the answers Swift tells me that contains is an unresolved identifier. I understand that is it in the Swift standard library so I assume I don't know how to use it.
Therefore what is the proper form of the following function?
override func viewDidDisappear(_ animated: Bool) {
if let viewControllers = self.navigationController?.viewControllers as? [UIViewController] {
if (contains(viewControllers, self)) {
print("Back button not pressed")
} else {
print ("Back button pressed")
}
}
}
Similar to RAJAMOHAN-S I look at where I'm transitioning from. However, the function
override func willMove(toParentViewController parent: UIViewController?)
provides a nice way of doing that; I combined with a simple bool variable to differentiate if the back button was really used or whether another programmatic method meant a transition event.
override func willMove(toParentViewController parent: UIViewController?) {
super.willMove(toParentViewController: parent)
if parent == nil {
// The back button was pressed, can be acted on
}
}
else{
print ("Coming here from a child view")
}
}
assuming I have a viewcontroller (vcA) that pushes QRCodeScannerViewcontroller (vcB). When (vcB) scanned something, It will push ResultviewController (vcC).
-Those 3 views is connected to a UInavigation controller
-the user clicks on the back button on (vcC)
my question is:
1)how can I know if (vcB) is visible without changing code on (vcB)? (vcB) is a pod
2)where will I put this code? I can only access (vcA)
i tried adding this code on (vcA) but nothing happened
override func viewDidDisappear(_ animated: Bool) {
if (vcB.isViewLoaded && (vcB.view.window != nil)){
print("vcb did appear!")
}
}
To know if an instance of cvB's class exists in the navigation stack, you could use this piece of code:
let result = self.navigationController?.viewControllers.filter({
if let vcB = $0 as? UIViewController { // Replace UIViewController with your class, for example ViewControllerB
return true
}
return false
})
if result.isEmpty {
print("An instance of vcB's class hasn't been pused before")
} else {
print("An instance of vcB's class has been pused before")
}
How can I do an optional binding in Swift and check for a negative result? Say for example I have an optional view controller that I'd like to lazy load. When it's time to use it, I'd like to check if it's nil, and initialize it if it hasn't been done yet.
I can do something like this:
if let vc = viewController? {
// do something with it
} else {
// initialize it
// do something with it
}
But this is kludgey and inefficient, and requires me to put the "do something with it" code in there twice or bury it in a closure. The obvious way to improve on this from objC experience would be something like this:
if !(let vc = viewController?) {
// initialize it
}
if let vc = viewController? {
// do something with it
}
But this nets you a "Pattern variable binding cannot appear in an expression" error, which is telling me not to put the binding inside the parenthesis and try to evaluate it as an expression, which of course is exactly what I'm trying to do...
Or another way to write that out that actually works is:
if let vc = viewController? {
} else {
// initialize it
}
if let vc = viewController? {
// do something with it
}
But this is... silly... for lack of a better word. There must be a better way!
How can I do an optional binding and check for a negative result as the default? Surely this is a common use case?
you can implicitly cast Optional to boolean value
if !viewController {
viewController = // something
}
let vc = viewController! // you know it must be non-nil
vc.doSomething()
Update: In Xcode6-beta5, Optional no longer conform to LogicValue/BooleanType, you have to check it with nil using == or !=
if viewController == nil {
viewController = // something
}
Would this work for you?
if viewController == nil {
// initialize it
}
// do something with it
One way might be to create a defer statement that handles the actions. We can ensure those actions occur after the creation of our object by checking for nil. If we run into nil, instantiate the object and return. Before returning our deference will occur within the scope of some function.
func recreateAndUse() {
defer {
viewController?.view.addSubview(UIView())
viewController!.beginAppearanceTransition(true, animated: true)
}
guard viewController != nil else {
viewController = UIViewController()
return
}
}