Does anyone know of a way to have a UITabBarButton that, rather than segueing to another view controller, will perform another function, e.g. call a method?
My iPad app utilises a tab bar but the client wants the right-most button to perform a check for updates on a server; however I can't seem to to figure out how to have a button that won't switch views when pressed. I tried deleting the segue but that removes the button as well.
-EDIT-
screenshot and code snippet added for clarity. The tab labelled Sync is the one I want not to open a viewController:
AppDelegate.h:
#interface AppDelegate : UIResponder <UIApplicationDelegate, UITabBarControllerDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) UIViewController *viewController;
#end
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self.window makeKeyAndVisible];
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
tabBarController.delegate = (id)self;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationDidTimeout:) name:kApplicationDidTimeoutNotification object:nil];
return YES;
}
and:
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
NSLog(#"vc = %#", viewController);
return YES;
}
Look at the UITabBarControllerDelegate method :
– tabBarController:shouldSelectViewController:
If selectViewController == your last tab bar, return NO and perform others actions
EDIT :
Look at the example I've made :
AppDelegate.h
#interface ISAppDelegate : UIResponder <UIApplicationDelegate, UITabBarControllerDelegate>
#property (strong, nonatomic) UIWindow *window;
#end
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
tabBarController.delegate = (id)self;
return YES;
}
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
// here check if "viewController" is your last tab controller, then return NO and perform some actions you need
if (viewController = self.lastTabController) {
// do some actions
return NO;
}
return YES;
}
Related
I had Implemented MFSideMenu in my Application to display left side slide in-out menu.Now I would like to recognise swipe in and swipe out (i.e. Sidemenu open or closed status)by creating delegate method in ThirdParty class MFSideMenuContainerViewController.h as
#import <UIKit/UIKit.h>
#import "MFSideMenuShadow.h"
#class MFSideMenuContainerViewController;
#protocol MFSideMenuContainerViewControllerViewDelegate <NSObject> //define delegate protocol
- (void)swipedLeftSidemenu:(BOOL)isOpen; //define delegate method to be implemented within another class
#end //end protocol
#interface MFSideMenuContainerViewController : UIViewController<UIGestureRecognizerDelegate>
//custom delegate property
#property (nonatomic, weak) id <MFSideMenuContainerViewControllerViewDelegate> delegate1;
#end
and then call delegate method from below method of MFSideMenuContainerViewController.m
- (void)openLeftSideMenuCompletion:(void (^)(void))completion
{
if(!self.leftMenuViewController) return;
//call as below
[self.delegate1 swipedLeftSidemenu:YES];
//below is default thirdparty implementation
[self.menuContainerView bringSubviewToFront:[self.leftMenuViewController view]];
[self setCenterViewControllerOffset:self.leftMenuWidth animated:YES completion:completion];
}
Delegate method used in another viewcontroller class to detect side menu is open or closed for that I had implemented below code
in DemoViewController.h file
#import "MFSideMenuContainerViewController.h"
#interface DemoViewController : UIViewController <ContactViewDelegate,MFSideMenuContainerViewControllerViewDelegate>
in DemoViewController.m file
- (void)viewDidLoad
{
[super viewDidLoad];
MFSideMenuContainerViewController *vc2 = [[MFSideMenuContainerViewController alloc] init];
vc2.delegate1 = self;
}
//DelegateMethod Implementation
-(void)swipedLeftSidemenu:(BOOL)isOpen
{
if(isOpen)
{
// code is here if side menu is open by swiping right
}
else
{
// code is here if side menu is closed by swiping left
}
}
Now I having issue is above method in DemoViewController.m file is never called although sidemenu can be swiped left or right.Can any one guide how I can detect side menu is open or close from DemoViewController class? Or Why this method is never called ?
My appdelegate.m files having below code only
#interface AppDelegate ()
#end
#implementation AppDelegate
- (DemoViewController *)demoController
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
DemoViewController *demoController = [storyboard instantiateViewControllerWithIdentifier:#"DemoViewController"];
return demoController;
}
- (UINavigationController *)navigationController
{
return [[UINavigationController alloc]
initWithRootViewController:[self demoController]];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
SideMenuViewController *leftMenuViewController = [storyboard instantiateViewControllerWithIdentifier:#"SideMenuViewController"];
MFSideMenuContainerViewController *container = [MFSideMenuContainerViewController
containerWithCenterViewController:[self navigationController]
leftMenuViewController:leftMenuViewController
rightMenuViewController:nil];
self.window.rootViewController = container;
[self.window makeKeyAndVisible];
return YES;
}
Update your code In your AppDelegate.h file
#property (strong, nonatomic) UIWindow *window;
#property(strong,nonatomic)ViewController *viewController;
#property(strong,nonatomic)LeftSideViewController *leftSideViewController;
#property(strong,nonatomic)MFSideMenuContainerViewController *container;
#property(strong,nonatomic)UINavigationController *navigationController;
#end
and in your AppDelegate.m file update your code
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.container = [MFSideMenuContainerViewController containerWithCenterViewController:self.navigationController leftMenuViewController:_leftSideViewController rightMenuViewController:nil];
self.window setRootViewController:_container];
[self.window makeKeyAndVisible];
return YES;
}
Instead of delegate method I had replaced it with NotoficationCenter Observer method and that code is as below :
STEP-1
in ThirdParty class MFSideMenuContainerViewController.m as
- (void)openLeftSideMenuCompletion:(void (^)(void))completion
{
if(!self.leftMenuViewController) return;
NSDictionary* userInfo = #{#"isOpen": #"1"};
[[NSNotificationCenter defaultCenter] postNotificationName:#"swipeClassDelegateMethod" object:nil userInfo:userInfo];
//below is default thirdparty implementation
[self.menuContainerView bringSubviewToFront:[self.leftMenuViewController view]];
[self setCenterViewControllerOffset:self.leftMenuWidth animated:YES completion:completion];
}
STEP-2 : then I called below code from my DemoViewController.m file viewDidLoad method
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(swipeClassDelegateMethod:)name:#"swipeClassDelegateMethod" object:nil];
STEP-3 : Finally I implemented this function with necessary code as below in same DemoViewController.m class
-(void)swipeClassDelegateMethod:(NSNotification*)notification
{
}
I'm trying to implement circular dependency between my AppDelegate and my ViewController to call methods from my AppDelegate to my ViewController but it's not working.
See my code as follow:
AppDelegate.h:
#class ViewController;
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong,nonatomic) ViewController *mainView;
#end;
AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
return YES;
}
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
[self.mainView doSomething];
return YES;
}
ViewController.h:
#import <UIKit/UIKit.h>
#class AppDelegate;
#interface ViewController : UIViewController
#property (strong,nonatomic) AppDelegate *delegate;
-(void)doSomething;
#end;
ViewController.m:
- (void)viewDidLoad {
[super viewDidLoad];
self.delegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
}
- (void)doSomething
{
NSLog(#"doing something");
}
I don't have any errors or warnings but the method doSomething has never been call. Any of you knows why or what I'm doing wrong?
I'll really appreciate your help.
This has nothing to do with circular dependencies.
As you've been told, the method doSomething is never called because you are saying
[self.mainView doSomething];
...at a time when self.mainView has never been given a value. Merely declaring a property
#property (strong,nonatomic) ViewController *mainView;
...does not point the variable mainView at your actual ViewController instance; it is nil, and a message to nil generates no error and causes nothing at all to happen.
You could fix this by having the ViewController set a reference to itself by adding one line to your code:
- (void)viewDidLoad {
[super viewDidLoad];
self.delegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
self.delegate.mainView = self; // <--
}
But don't! The simple truth is that your entire approach here is wrong. There should be no need whatever to keep a reference to your ViewController inside your app delegate. Your app has, at every moment, a view controller hierarchy. The app delegate should know where the ViewController is within that hierarchy.
Here we are in your app delegate when a link message comes in:
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
At that moment, it is the app delegate's job to know where the ViewController is in the view controller hierarchy, and even to arrange the view controller hierarchy so that the ViewController's scene is showing if it wasn't already, in response to the link message.
How you do that depends on the structure of your view controller hierarchy. You start at the top of the hierarchy, which, for the app delegate, is [[self window] rootViewController], and work your way down to the existing view controller you want to talk to.
You have not told us your view controller hierarchy structure, so it's impossible to help in detail. But let's say, for example, that your app revolves around a navigation controller interface. Then, for the app delegate, [[self window] rootViewController] is the navigation controller, and so you can cast to that class: (UINavigationController*)[[self window] rootViewController]. Then, if the ViewController is the navigation controller's root view controller, you take its viewControllers[0] to reach it, and again you cast as needed:
UINavigationController* nav = (UINavigationController*)[[self window] rootViewController];
ViewController* vc = (ViewController*)nav.viewControllers[0];
Now you can send the doSomething message to vc. But that's just an illustration; the precise details will depend on where the ViewController really is, within the view controller hierarchy. And of course you might also want to pop view controllers so that the ViewController's scene is actually showing, since you likely cannot guarantee that it is showing at the time the link message comes in.
Another completely different way of handling this situation is to use the NSNotificationCenter to post a notification for which the ViewController instance has registered. That is often a solution when you do not know exactly where in the view controller hierarchy your view controller is. But in this situation, you should know that.
Try the below code :
AppDelegate.m :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[[ViewController sharedInstance] doSomething];
return YES;
}
ViewController.h
+ (ViewController *)sharedInstance;
- (void)doSomething;
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
+ (ViewController *)sharedInstance {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
return [storyboard instantiateViewControllerWithIdentifier:#"ViewController"];
}
- (void)doSomething {
NSLog(#"ViewController is doing something");
}
Output :
ViewController is doing something
I don't have any errors or warnings but the method doSomething has never been call. Any of you knows why or what I'm doing wrong?
It happens because you haven't initialised an instance of ViewController. So, you have a nil at mainView. When you try to send a message "doSomething" to mainView you send message to nil. At Objective-C when you send a message to nil nothing is happens.
You should initialise an instance before you try to invoke the method. For example, at didFinishLaunchingWithOptions with such code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.mainView = [ViewController new];
return YES;
}
It will works if you create views programatically. If you use a storyboards or xib you should use another methods.
Now you should see "doing something" at console when openURL is invoked.
BTW, you have a retain cycle between app delegate and view controller. So, your mainView will never release even if you make it explicitly nil. To avoid a retain cycle you should use attribute weak at ViewController.h:
#property (nonatomic, weak) AppDelegate *delegate;
You can do that by the below code but excuse me for doing it with Swift:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var mainVC: ViewController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
mainVC = ViewController()
mainVC?.doSomething()
setupMainViewController()
return true
}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
mainVC?.doSomething()
return true
}
func setupMainViewController(){
guard window != nil else{
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = mainVC
window?.makeKeyAndVisible()
return
}
window?.rootViewController = mainVC
}
and the MainViewController will be:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
doSomething()
}
func doSomething(){
print("do something with \(self)")
}
I appreciate all the other answers, I come here with a different approach. Generally, I used in my projects.
NSNotificationCenter defaultCenter Please check the code below.
Viewcontroller.m
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//self.delegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];
}
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
// HERE REGISTER NOTIFICATION WHEN SCREEN IS APPEAR
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(doSomething:)
name:#"TestNotification"
object:nil];
}
// HERE ADDS NOTIFICAIOTN AS PARAMETERS
- (void)doSomething :(NSNotification *) notification
{
NSLog(#"doing something");
// IF YOU PASSED VALUE WITH THE NOTIFICAIONT THEN IT WILL SHOW HERE
NSDictionary *userInfo = notification.userInfo;
id myObject = [userInfo objectForKey:#"someKey"];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
// HERE REMOVE NOTIFICATION OBSERVER
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
AppDelegate.m :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
return YES;
}
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
//WITHOUT SENDING ANY DATA
[[NSNotificationCenter defaultCenter]
postNotificationName:#"TestNotification"
object:self];
// IF YOU WANT TO SEND ANY INFROMATION THEN PLEASE USE THIS METHODS
NSDictionary *userInfo =
[NSDictionary dictionaryWithObject:myObject forKey:#"someKey"];
[[NSNotificationCenter defaultCenter] postNotificationName:
#"TestNotification" object:nil userInfo:userInfo];
return YES;
}
Here you do not need to check the visible view controller and no allocation required. if your screen is in the top then your methods are called otherwise not.
Good luck! :-)
I have used the Xcode tab bar controller template and am trying to detect when a user selects a tab in my tab bar controller. In AppDelegate.h I have:
#import <UIKit/UIKit.h>
#interface AppDelegate : UIResponder <UIApplicationDelegate, UITabBarControllerDelegate> {
UITabBarController *tabBarController;
}
#property (strong, nonatomic) UIWindow *window;
#property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
#end
and in AppDelegate.m I have:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
tabBarController.delegate = self;
return YES;
}
- (void)tabBarController:(UITabBarController *)tabBarController
didSelectViewController:(UIViewController *)viewController
{
NSLog(#"Selected tab bar button!");
}
I have not connected any outlets for the Tab Bar Controller scene listed in Main.storyboard because I couldn't figure out the right connection to make, if one is even necessary. I also haven't made any references to this delegate method in my FirstViewController or anywhere else, because the various ways I tried to do that didn't work. Could someone help me understand how to get this working?
You can add tab bar controller by selecting UIViewController(FirstViewController in your case) and choose Editor >> Embed In >> Tab Bar Controller.
Eliminate the UITabBarController *tabBarController; declaration in AppDelegate.h (line 4) and replace
tabBarController.delegate = self;
with
self.tabBarController.delegate = self;
in AppDelegate.m.
I am trying to call my shouldSelectViewController Delegate function. But it doesnt seem to work.
I did the following, but still the function doesnt get called
.h
#interface myAppDelegate : UIResponder <UIApplicationDelegate, UITabBarControllerDelegate>
#property (strong, nonatomic) UIWindow *window;
#end
.m
#implementation TakeFiveCameraAppDelegate
#synthesize window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
tabBarController.delegate = (id)self;
return YES;
}
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
NSLog(#"test");
return YES;
}
#end
I am not sure what am I missing here.
add in viewDidLoad
[self setDelegate:self];
After switching to Xcode 4, I thought I would be able to build and run my application exactly like I could in Xcode 3.
Turns out I can't.
Xcode 4 has a funny way of never showing the app's view controller, which is strange.
I can tell Apple's going to eventually force us to switch, which will result in my app being inoperable.
It gets to application:didFinishLaunchingWithOptions: without any errors, before hanging. Eventually the application crashes on the device - but stays on Default.png forever in the simulator.
I thought I could go and edit the application:didFinishLaunchingWithOptions: method the instantiate an instance of the view controller itself and add it to the window - only to reveal that didn't work either.
After numerous failed attempts at this - creating separate UIWindows for the main view controller - I decided the add it to a navigation controller.
Luck then struck me - but only in the simplest of forms. I looked in the log and see that applicationDidBecomeActive: had been called.
But, as usual, no such luck with any sort of view being displayed.
I then decided to see if I could add a UIView with a blue background color and a few UI elements (buttons, labels, etc...) to the window and see if that would work.
Funnily enough it did.
But why not for the main view controller? Not once in Xcode 4 have I successfully had it run my app (even opening it after it has been built fails). I've tried changing the compiler to the same as in Xcode 3, no luck.
I am honestly really confused as to why the application's view controller won't display.
For anyone who wants to give it an attempt as to why it isn't working that would be gratefully appreciated.
Here's the code for the AppDelegate, if you need the code for the view controller I can paste it here, however it's over 2000 lines.
Anyway, here's the .m file:
#import "DocumentationAppDelegate.h"
#import "DocumentationViewController.h"
#implementation DocumentationAppDelegate
#synthesize window;
#synthesize viewController;
#synthesize navigationController;
- (void)applicationWillEnterForeground:(UIApplication *)application {
NSLog(#"In method %#, which is in class %#.", NSStringFromSelector(_cmd), NSStringFromClass([self class]));
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
NSLog(#"In method %#, which is in class %#.", NSStringFromSelector(_cmd), NSStringFromClass([self class]));
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSLog(#"In method %#, which is in class %#.", NSStringFromSelector(_cmd), NSStringFromClass([self class]));
DocumentationViewController *vc = [[DocumentationViewController alloc] init];
UINavigationController *controller = [[UINavigationController alloc] initWithRootViewController:vc];
controller.navigationBarHidden = YES;
UIWindow *win = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[win addSubview:controller.view];
[win makeKeyAndVisible];
return YES;
}
- (void)applicationWillTerminate:(UIApplication *)application {
}
- (void)dealloc {
[window release];
[super dealloc];
}
#end
and the .h
#import <UIKit/UIKit.h>
#class DocumentationViewController;
#interface DocumentationAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
DocumentationViewController *viewController;
UINavigationController *navigationController;
}
#property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet DocumentationViewController *viewController;
#end
If any one could help me here it would be tremendously appreciated.
Your app delegate already has the properties window, viewController and navigationController. So you can have the application:didFinishLaunchingWithOptions: method like this,
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
viewController = [[DocumentationViewController alloc] init];
navigationController = [[UINavigationController alloc] initWithRootViewController:vc];
navigationController.navigationBarHidden = YES;
[self.window addSubview:navigationController.view];
[self.window makeKeyAndVisible];
return YES;
}