Objective-c method is not being called - iphone

a custom method i have made is not being called and i cant figure out why.
it is a method that takes in a boolean parameter, but when i call the command from a button:
[self myMethod:TRUE];
the system doesnt do any thing. i can tell that the function works because when i run the command from:
- (void) viewDidLayoutSubviews
{
[self myMethod:TRUE];
}
the program functions as i would expect given the different boolean valued.
my method is defined similarly to:
- (void)myMethod:(BOOL) theBool
{
if (theBool){
//stuff if theBool is true
} else {
//stuff if theBool is false
}
}
i don't get any errors but it just doesn't change anything on the screen.

Related

How to call navigationBar(_:shouldPop:) of the parent class in a subclass of UINavigationController?

I have written the following code in Objective-C:
#interface UINavigationController(UINavigationBar) <UINavigationBarDelegate>
#end
#implementation TestNavigationViewController
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
...
return [super navigationBar:navigationBar shouldPopItem:item];
}
#end
Now I need to implement the swift version of the above code, but I find that I can't do it.
I have tried the following two methods, but neither of them works:
Call super.navigationBar(_:shouldPop:) directly.
super.perform(#selector(UINavigationBarDelegate.navigationBar(_:shouldPop:)), with: navigationBar, with: item)
But they don't work:
The first method does not pass the compiler, it reports an error.
The second method leads to an infinite loop where method navigationBar(_:shouldPop:) keeps getting called.
In the end I chose the following method and it works fine in some cases. But in some cases it can cause catastrophic problems. (This is not the point of this question, but I will add it if you are interested)
extension NavigationViewController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
if #available(iOS 13.0, *) { return true }
DispatchQueue.main.async {
self.popViewController(animated: true)
}
return true
}
}
Since the Objective-C code at the beginning doesn't cause any problems, I decided to let the problem go back to the beginning, instead of trying to solve another bug caused by one bug.
How do I implement that Objective-C code in swift?
Supplement1:
The override keyword was added by auto-completion, but an error was reported...The ? behind the navigationBar is also added by auto-completion.

Currently messing with Swift Reachability, should I do all my code in viewDidLoad or in viewDidAppear?

I'm currently trying to implement Reachability into my current project. I followed a tutorial on YouTube that worked but I'm unsure whether or not its the correct way of doing it. In the Reachability documentation (https://github.com/ashleymills/Reachability.swift) it shows two examples first one being 'Example - closures' where I assume it's done in the viewDidLoad?
//declare this property where it won't go out of scope relative to your listener
let reachability = Reachability()!
reachability.whenReachable = { reachability in
// this is called on a background thread, but UI updates must
// be on the main thread, like this:
DispatchQueue.main.async {
if reachability.isReachableViaWiFi() {
print("Reachable via WiFi")
} else {
print("Reachable via Cellular")
}
}
}
reachability.whenUnreachable = { reachability in
// this is called on a background thread, but UI updates must
// be on the main thread, like this:
DispatchQueue.main.async {
print("Not reachable")
}
}
do {
try reachability.startNotifier()
} catch {
print("Unable to start notifier")
}
and the last example was 'Example - notifications', this is where I get confused the creator says to do that all in viewDidAppear. Is there really a big difference if I just do everything inside viewDidLoad? Does it change the outcome of anything? It currently works fine but I'm not sure whether it's right, I don't want it affecting me in the future. Any help would be great! Thanks.
It depends on your needs.
If you want to use Reachability...
... dynamically only if this particular view is frontmost, startNotifier() in viewWillAppear and stopNotifier() in viewDidDisappear.
... in this particular view as long as the view is alive/loaded startNotifier() in viewDidLoad.
... globally in all views put the entire code in AppDelegate and post notifications.

performSegueWithIdentifier not executed

I'm trying to use a segue, here's my code:
if let status_code = response.response?.statusCode {
if status_code == 200 {
// register is ok
// cancel all warnings
self.usernameField.hidden = false;
self.usernameFieldError.hidden = true;
self.usernameShortError.hidden = true;
self.usernameTakenError.hidden = true;
self.performSegueWithIdentifier("registerToFeed", sender: self);
}
}
(I've cut the code for length)
So here's the problem: the segue is simply not executed. I've put a breakpoint on it to see what happens, and the execution flow just passes on it, and do nothing, quite frustrating.
I've read that performSegueWithIdentifier needs to be called on the main thread, so I called NSThread.isMainThread()just before my segue call, and it returned me true.
I also double checked if the identifier name was the same as the one i'm calling and that's the case.
I tried to do
dispatch_async(dispatch_get_main_queue()){
self.performSegueWithIdentifier("registerToFeed", sender: self)
}
as I've seen in another SO thread, but still not working, nothing gets executed.
So I don't know why this simple line doesn't get executed. Thank you for your help.
EDIT: so here's the full RegisterViewController.swift file:
http://pastebin.com/dpBBXhe8
The problem is that the program crashes at line 190 because the performSegue is not executed.
Is this code in the viewDidLoad method? If so, you must move it to viewDidAppear.
Where was the viewController (self) in this code originated from. The
performSegueWithIdentifier(_:sender:) requires the calling viewController to be loaded from a storyboard (storyboard must not be nil).
Also, have you tried implementing preapreForSegue to see if this is called when you call performSegue? The default implementation of 'prepareForSegue' does nothing.

Detecting when the 'back' button is pressed on a navbar

I need to perform some actions when the back button(return to previous screen, return to parent-view) button is pressed on a Navbar.
Is there some method I can implement to catch the event and fire off some actions to pause and save data before the screen disappears?
UPDATE: According to some comments, the solution in the original answer does not seem to work under certain scenarios in iOS 8+. I can't verify that that is actually the case without further details.
For those of you however in that situation there's an alternative. Detecting when a view controller is being popped is possible by overriding willMove(toParentViewController:). The basic idea is that a view controller is being popped when parent is nil.
Check out "Implementing a Container View Controller" for further details.
Since iOS 5 I've found that the easiest way of dealing with this situation is using the new method - (BOOL)isMovingFromParentViewController:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController) {
// Do your stuff here
}
}
- (BOOL)isMovingFromParentViewController makes sense when you are pushing and popping controllers in a navigation stack.
However, if you are presenting modal view controllers you should use - (BOOL)isBeingDismissed instead:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isBeingDismissed) {
// Do your stuff here
}
}
As noted in this question, you could combine both properties:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController || self.isBeingDismissed) {
// Do your stuff here
}
}
Other solutions rely on the existence of a UINavigationBar. Instead like my approach more because it decouples the required tasks to perform from the action that triggered the event, i.e. pressing a back button.
While viewWillAppear() and viewDidDisappear() are called when the back button is tapped, they are also called at other times. See end of answer for more on that.
Using UIViewController.parent
Detecting the back button is better done when the VC is removed from its parent (the NavigationController) with the help of willMoveToParentViewController(_:) OR didMoveToParentViewController()
If parent is nil, the view controller is being popped off the navigation stack and dismissed. If parent is not nil, it is being added to the stack and presented.
// Objective-C
-(void)willMoveToParentViewController:(UIViewController *)parent {
[super willMoveToParentViewController:parent];
if (!parent){
// The back button was pressed or interactive gesture used
}
}
// Swift
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
if parent == nil {
// The back button was pressed or interactive gesture used
}
}
Swap out willMove for didMove and check self.parent to do work after the view controller is dismissed.
Stopping the dismiss
Do note, checking the parent doesn't allow you to "pause" the transition if you need to do some sort of async save. To do that you could implement the following. Only downside here is you lose the fancy iOS styled/animated back button. Also be careful here with the interactive swipe gesture. Use the following to handle this case.
var backButton : UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
// Disable the swipe to make sure you get your chance to save
self.navigationController?.interactivePopGestureRecognizer.enabled = false
// Replace the default back button
self.navigationItem.setHidesBackButton(true, animated: false)
self.backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Plain, target: self, action: "goBack")
self.navigationItem.leftBarButtonItem = backButton
}
// Then handle the button selection
func goBack() {
// Here we just remove the back button, you could also disabled it or better yet show an activityIndicator
self.navigationItem.leftBarButtonItem = nil
someData.saveInBackground { (success, error) -> Void in
if success {
self.navigationController?.popViewControllerAnimated(true)
// Don't forget to re-enable the interactive gesture
self.navigationController?.interactivePopGestureRecognizer.enabled = true
}
else {
self.navigationItem.leftBarButtonItem = self.backButton
// Handle the error
}
}
}
### More on view will/did appear
If you didn't get the `viewWillAppear` `viewDidDisappear` issue, Let's run through an example. Say you have three view controllers:
ListVC: A table view of things
DetailVC: Details about a thing
SettingsVC: Some options for a thing
Lets follow the calls on the detailVC as you go from the listVC to settingsVC and back to listVC
List > Detail (push detailVC) Detail.viewDidAppear <- appear
Detail > Settings (push settingsVC) Detail.viewDidDisappear <- disappear
And as we go back...
Settings > Detail (pop settingsVC) Detail.viewDidAppear <- appear
Detail > List (pop detailVC) Detail.viewDidDisappear <- disappear
Notice that viewDidDisappear is called multiple times, not only when going back, but also when going forward. For a quick operation that may be desired, but for a more complex operation like a network call to save, it may not.
Those who claim that this doesn't work are mistaken:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParent {
print("we are being popped")
}
}
That works fine. So what is causing the widespread myth that it doesn’t?
The problem seems to be due to an incorrect implementation of a different method, namely that the implementation of willMove(toParent:) forgot to call super.
If you implement willMove(toParent:) without calling super, then self.isMovingFromParent will be false and the use of viewWillDisappear will appear to fail. It didn't fail; you broke it.
NOTE: The real problem is usually the second view controller detecting that the first view controller was popped. Please see also the more general discussion here: Unified UIViewController "became frontmost" detection?
EDIT A comment suggests that this should be viewDidDisappear rather than viewWillDisappear.
First Method
- (void)didMoveToParentViewController:(UIViewController *)parent
{
if (![parent isEqual:self.parentViewController]) {
NSLog(#"Back pressed");
}
}
Second Method
-(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];
}
I've playing (or fighting) with this problem for two days. IMO the best approach is just to create an extension class and a protocol, like this:
#protocol UINavigationControllerBackButtonDelegate <NSObject>
/**
* Indicates that the back button was pressed.
* If this message is implemented the pop logic must be manually handled.
*/
- (void)backButtonPressed;
#end
#interface UINavigationController(BackButtonHandler)
#end
#implementation UINavigationController(BackButtonHandler)
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
UIViewController *topViewController = self.topViewController;
BOOL wasBackButtonClicked = topViewController.navigationItem == item;
SEL backButtonPressedSel = #selector(backButtonPressed);
if (wasBackButtonClicked && [topViewController respondsToSelector:backButtonPressedSel]) {
[topViewController performSelector:backButtonPressedSel];
return NO;
}
else {
[self popViewControllerAnimated:YES];
return YES;
}
}
#end
This works because UINavigationController will receive a call to navigationBar:shouldPopItem: every time a view controller is popped. There we detect if back was pressed or not (any other button).
The only thing you have to do is implement the protocol in the view controller where back is pressed.
Remember to manually pop the view controller inside backButtonPressedSel, if everything is ok.
If you already have subclassed UINavigationViewController and implemented navigationBar:shouldPopItem: don't worry, this won't interfere with it.
You may also be interested in disable the back gesture.
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
This works for me in iOS 9.3.x with Swift:
override func didMoveToParentViewController(parent: UIViewController?) {
super.didMoveToParentViewController(parent)
if parent == self.navigationController?.parentViewController {
print("Back tapped")
}
}
Unlike other solutions here, this doesn't seem to trigger unexpectedly.
You can use the back button callback, like this:
- (BOOL) navigationShouldPopOnBackButton
{
[self backAction];
return NO;
}
- (void) backAction {
// your code goes here
// show confirmation alert, for example
// ...
}
for swift version you can do something like in global scope
extension UIViewController {
#objc func navigationShouldPopOnBackButton() -> Bool {
return true
}
}
extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
return self.topViewController?.navigationShouldPopOnBackButton() ?? true
}
}
Below one you put in the viewcontroller where you want to control back button action:
override func navigationShouldPopOnBackButton() -> Bool {
self.backAction()//Your action you want to perform.
return true
}
For the record, I think this is more of what he was looking for…
UIBarButtonItem *l_backButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRewind target:self action:#selector(backToRootView:)];
self.navigationItem.leftBarButtonItem = l_backButton;
- (void) backToRootView:(id)sender {
// Perform some custom code
[self.navigationController popToRootViewControllerAnimated:YES];
}
The best way is to use the UINavigationController delegate methods
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
Using this you can know what controller is showing the UINavigationController.
if ([viewController isKindOfClass:[HomeController class]]) {
NSLog(#"Show home controller");
}
For Swift with a UINavigationController:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if self.navigationController?.topViewController != self {
print("back button tapped")
}
}
You should check out the UINavigationBarDelegate Protocol.
In this case you might want to use the navigationBar:shouldPopItem: method.
As Coli88 said, you should check the UINavigationBarDelegate protocol.
In a more general way, you can also use the - (void)viewWillDisapear:(BOOL)animated to perform custom work when the view retained by the currently visible view controller is about to disappear. Unfortunately, this would cover bother the push and the pop cases.
As purrrminator says, the answer by elitalon is not completely right, since your stuff would be executed even when popping the controller programmatically.
The solution I have found so far is not very nice, but it works for me. Besides what elitalon said, I also check whether I'm popping programmatically or not:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if ((self.isMovingFromParentViewController || self.isBeingDismissed)
&& !self.isPoppingProgrammatically) {
// Do your stuff here
}
}
You have to add that property to your controller and set it to YES before popping programmatically:
self.isPoppingProgrammatically = YES;
[self.navigationController popViewControllerAnimated:YES];
Thanks for your help!
I have solved this problem by adding a UIControl to the navigationBar on the left side .
UIControl *leftBarItemControl = [[UIControl alloc] initWithFrame:CGRectMake(0, 0, 90, 44)];
[leftBarItemControl addTarget:self action:#selector(onLeftItemClick:) forControlEvents:UIControlEventTouchUpInside];
self.leftItemControl = leftBarItemControl;
[self.navigationController.navigationBar addSubview:leftBarItemControl];
[self.navigationController.navigationBar bringSubviewToFront:leftBarItemControl];
And you need to remember to remove it when view will disappear:
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (self.leftItemControl) {
[self.leftItemControl removeFromSuperview];
}
}
That's all!
7ynk3r's answer was really close to what I did use in the end but it needed some tweaks:
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
UIViewController *topViewController = self.topViewController;
BOOL wasBackButtonClicked = topViewController.navigationItem == item;
if (wasBackButtonClicked) {
if ([topViewController respondsToSelector:#selector(navBackButtonPressed)]) {
// if user did press back on the view controller where you handle the navBackButtonPressed
[topViewController performSelector:#selector(navBackButtonPressed)];
return NO;
} else {
// if user did press back but you are not on the view controller that can handle the navBackButtonPressed
[self popViewControllerAnimated:YES];
return YES;
}
} else {
// when you call popViewController programmatically you do not want to pop it twice
return YES;
}
}
I used Pedro Magalhães solution, except navigationBar:shouldPop was not called when I used it in an extension like this:
extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
return self.topViewController?.navigationShouldPopOnBackButton() ?? true
}
But the same thing in a UINavigationController subclass worked fine.
class NavigationController: UINavigationController, UINavigationBarDelegate {
func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
return self.topViewController?.navigationShouldPopOnBackButton() ?? true
}
I see some other questions reporting this method not being called (but the other delegate methods being called as expected), from iOS 13?
iOS 13 and UINavigationBarDelegate::shouldPop()
self.navigationController.isMovingFromParentViewController is not working anymore on iOS8 and 9 I use :
-(void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (self.navigationController.topViewController != self)
{
// Is Popping
}
}
(SWIFT)
finaly found solution.. method we were looking for is "willShowViewController" which is delegate method of UINavigationController
//IMPORT UINavigationControllerDelegate !!
class PushedController: UIViewController, UINavigationControllerDelegate {
override func viewDidLoad() {
//set delegate to current class (self)
navigationController?.delegate = self
}
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
//MyViewController shoud be the name of your parent Class
if var myViewController = viewController as? MyViewController {
//YOUR STUFF
}
}
}

How to detect keyboard events on hardware keyboard on iPhone (iOS)

I have an app with a login page that has three fields for three different random characters. When the user leaves the last field, the soft keyboard disappears and the user can touch a "login" button on screen.
When there is a hardware keyboard (bluetooth or physical) attached, I'd like to be able to hit "enter" on it. However because the user is not in a field, I can't see how to detect this key being pressed.
Would anyone have advice on which class handles key press events? Presumably there is a delegate that I can use to receive these but my searches through the SDK haven't found anything.
Thanks
For iOS 7.0 or later, you can return UIKeyCommands for the keyCommands property from any UIResponder, such as UIViewController:
Objective-C
// In a view or view controller subclass:
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (NSArray *)keyCommands
{
return #[ [UIKeyCommand keyCommandWithInput:#"\r" modifierFlags:0 action:#selector(enterPressed)] ];
}
- (void)enterPressed
{
NSLog(#"Enter pressed");
}
Swift
// In a UIView/UIViewController subclass:
override var canBecomeFirstResponder: Bool {
true
}
override var keyCommands: [UIKeyCommand]? {
return [ UIKeyCommand(input: "\r", modifierFlags: [], action: #selector(enterPressed)) ]
}
#objc func enterPressed() {
print("Enter pressed")
}
One way to accomplish this is to have a hidden extra (4th in your case) text field. Make it 1x1 px in size and transparent. Then make it the first responder when any of your other 3 text fields are not, and look for text change events in that hidden field to trigger your key input event.
You might also want to check the notification for a software keyboard appearing if you don't want it to stay visible as well.
As a followup to the response by #Patrick, here is how you do it in Xamarin.iOS:
public override bool CanBecomeFirstResponder
{
get { return true; }
}
public override UIKeyCommand[] KeyCommands
{
get
{
return new[]{ UIKeyCommand.Create((NSString)"\r", (UIKeyModifierFlags)0, new ObjCRuntime.Selector("enterPressed")) };
}
}
[Export("enterPressed")]
private void OnEnterPressed()
{
// Handle Enter Key
}