Facebook ios sdk login from UINavigationBar - iphone

I am building an app that has a navigationitem in the navigation bar.
I am trying to understand how to use the facebook sdk to connect to facebook (authenticating) when the button is clicked.
This is not some special viewcontroller or something.
I have seen this:
http://developers.facebook.com/docs/tutorials/ios-sdk-tutorial/authenticate/
but in there i need to create some things in the delegate (like a UINavigationController) that i can't use because i am using a UITabBarController..
how can i implement facebook login and session creation just from pushing the UINavigationItem?
Here is my AppDelegate.h:
#import <UIKit/UIKit.h>
#import <FacebookSDK/FacebookSDK.h>
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) UITabBarController *tbc;
#property (strong, nonatomic) FBSession *session;
#end
And my AppDelegate.m:
#import "AppDelegate.h"
#import "StatusView.h"
#import "JokesView.h"
#import "HomeView.h"
#import "TopTenView.h"
#import "UploadView.h"
#implementation AppDelegate
#synthesize tbc;
#synthesize window = _window;
#synthesize session = _session;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
UINavigationController*nav1 = [[UINavigationController alloc]init];
UINavigationController*nav2 = [[UINavigationController alloc]init];
UINavigationController*nav3 = [[UINavigationController alloc]init];
UINavigationController*nav4 = [[UINavigationController alloc]init];
UINavigationController*nav5 = [[UINavigationController alloc]init];
StatusView*page1 = [[StatusView alloc]initWithNibName:#"StatusView" bundle:nil];
JokesView*page2 = [[JokesView alloc]initWithNibName:#"JokesView" bundle:nil];
HomeView*page3 = [[HomeView alloc]initWithNibName:#"HomeView" bundle:nil];
TopTenView*page4 = [[TopTenView alloc]initWithNibName:#"TopTenView" bundle:nil];
UploadView*page5 = [[UploadView alloc]initWithNibName:#"UploadView" bundle:nil];
page1.title = #"סטטוסים";
page2.title = #"תמונות";
page3.title = #"ראשי";
page4.title = #"Top 10";
page5.title = #"העלאה";
UITabBarItem *tab1 = [[UITabBarItem alloc] initWithTitle:#"Status"
image:[UIImage imageNamed:#"tbc-status.png"] tag:1];
[nav1 setTabBarItem:tab1];
UITabBarItem *tab2 = [[UITabBarItem alloc] initWithTitle:#"Jokes"
image:[UIImage imageNamed:#"tbc-jokes.png"] tag:1];
[nav2 setTabBarItem:tab2];
UITabBarItem *tab3 = [[UITabBarItem alloc] initWithTitle:#"Home"
image:[UIImage imageNamed:#"tbc-home.png"] tag:1];
[nav3 setTabBarItem:tab3];
UITabBarItem *tab4 = [[UITabBarItem alloc] initWithTitle:#"Tpp10"
image:[UIImage imageNamed:#"tbc-topten.png"] tag:1];
[nav4 setTabBarItem:tab4];
UITabBarItem *tab5 = [[UITabBarItem alloc] initWithTitle:#"Upload"
image:[UIImage imageNamed:#"tbc-upload.png"] tag:1];
[nav5 setTabBarItem:tab5];
[nav1 pushViewController:page1 animated:NO];
[nav2 pushViewController:page2 animated:NO];
[nav3 pushViewController:page3 animated:NO];
[nav4 pushViewController:page4 animated:NO];
[nav5 pushViewController:page5 animated:NO];
tbc = [[UITabBarController alloc]init];
tbc.viewControllers = [NSArray arrayWithObjects:nav5,nav4,nav3,nav2,nav1, nil];
tbc.selectedIndex = 2;
// NavBar Design
UIImage *navbarPortrait = [[UIImage imageNamed:#"topbar.jpg"]
resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
UIImage *navbarLandscape = [[UIImage imageNamed:#"topbar.jpg"]
resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
[[UINavigationBar appearance] setBackgroundImage:navbarPortrait
forBarMetrics:UIBarMetricsDefault];
[[UINavigationBar appearance] setBackgroundImage:navbarLandscape
forBarMetrics:UIBarMetricsLandscapePhone];
// NavBar Design End
// TabBar Design
UIImage *tabBackground = [[UIImage imageNamed:#"tbcb3ack.png"]
resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
[[UITabBar appearance] setBackgroundImage:tabBackground];
[[tbc tabBar] setBackgroundImage:tabBackground];
// TabBar Design End
[self.window addSubview:tbc.view];
self.window.rootViewController = self.tbc;
// Push Notifications
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeNone)];
// Push Notifications End
// Facebook Code Start
//UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Not Logged in"
// message:#"You Log in to use all the fearues in this app"
// delegate:nil
// cancelButtonTitle:#"OK"
// otherButtonTitles:nil];
if (FBSession.activeSession.state == FBSessionStateCreatedTokenLoaded) {
// To-do, show logged in view
} else {
//[alert show];
}
// Facebook Code End
[self.window makeKeyAndVisible];
return YES;
}
// Facebook sdk code Start
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// attempt to extract a token from the url
return [FBAppCall handleOpenURL:url
sourceApplication:sourceApplication
withSession:self.session];
}
// Facebook sdk code End
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
[FBAppCall handleDidBecomeActiveWithSession:self.session];
}
#pragma mark Template generated code
#end
These are after i used the facebook ios authentication tutorial, before the actual fb session changes and login button.

There really are two parts to solving this problem. You first need to define a UIButton to be a UINavigationItem which will do your login (or log out) on click. And second, your app has to be ready to accept the login event. I'm not sure which part is causing you more trouble, but here's how you set up part one, the button:
UIButton *loginButton = [UIButton buttonWithType:UIButtonTypeCustom];
loginButton.frame = CGRectMake(0.0f, 0.0f, 75.0f, 44.0f);
[loginButton setTitle:#"Login" forState:UIControlStateNormal];
[loginButton addEventHandler:^(id sender)
{
[FBSession openActiveSessionWithReadPermissions:nil
allowLoginUI:YES
completionHandler:
^(FBSession *session,
FBSessionState state, NSError *error) {
[self sessionStateChanged:session state:state error:error];
}];
}
forControlEvents:UIControlEventTouchUpInside];
[self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:loginButton];
As for part two, you need to define the sessionStateChanged function in this view controller. To save you trouble, here's the general structure that you need for the sessionStateChanged method:
- (void)sessionStateChanged:(FBSession *)session
state:(FBSessionState) state
error:(NSError *)error
{
switch (state) {
case FBSessionStateOpen:
// Connected to facebook... so go to your next view controller
break;
case FBSessionStateClosed:
case FBSessionStateClosedLoginFailed:
// Login failed
[FBSession.activeSession closeAndClearTokenInformation];
break;
default:
break;
}
if (error) {
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:#"Error"
message:error.localizedDescription
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alertView show];
}
}
You'll also need to implement a lot of other things, especially in your app delegate. I'm happy to write out more code samples for you, but help me understand what you want to see next.
UPDATE
Ok, so now that I understand you want to have this facebook button stay with you on the nav bar, I would subclass the nav controller, set the subclasss to be its own delegate, and add the facebook button to each view controller that's added. The thing to note is, the navigation bar that's drawn uses the navigationItem of each view controller that's added to the navigation controller. So technically, you need to add this same button to every view controller that's pushed or popped in and out of the navigation controller. Instead of copy-pasting code around or even setting up a utility class (or super class) that all your view controllers use to insert this facebook button, a quick and dirty way is to create a single button in a navigation controller subclass and insert that same button as each view controller is shown. So here's a very bare bones version of what the navigation controller subclass might look like this in its implementation file:
#interface MYNavigationController () <UINavigationControllerDelegate>
#property (nonatomic, strong) UIButton *facebookButton;
#end
#implementation MYNavigationController
- (void)viewDidLoad
{
[super viewDidLoad];
// Set this subclass as its own delegate to be able receive the willShowViewController: method
self.delegate = self;
// Create a shared facebook button
_facebookButton = [UIButton buttonWithType:UIButtonTypeCustom];
_facebookButton.frame = CGRectMake(0.0f, 0.0f, 75.0f, 44.0f);
[_facebookButton setTitle:#"Login" forState:UIControlStateNormal];
[_facebookButton
addTarget:self
action:#selector(onFacebookButtonClick:)
forControlEvents:UIControlEventTouchUpInside];
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
// As each view controller is about to be shown, change the view controller's
// navigationItem to have this facebook button as its right bar button
viewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.facebookButton];
}
- (void)onFacebookButtonClick:(id)sender
{
// Handle the click event when the facebook button is clicked
// You need to have logic here to know if the session is open or not
// Of course, when the session isn't open, then open a new session (ie. login)
// and when the session IS open, then close the session (ie. logout)
[FBSession
openActiveSessionWithReadPermissions:nil
allowLoginUI:YES
completionHandler: ^(FBSession *session, FBSessionState state, NSError *error)
{
[self sessionStateChanged:session state:state error:error];
}];
}
- (void)sessionStateChanged:(FBSession *)session state:(FBSessionState)state error:(NSError *)error
{
switch (state) {
case FBSessionStateOpen:
// Connected to facebook so...
// 1. change the button text to say "logout" instead of "login"
// 2. go to your next view controller
break;
case FBSessionStateClosed:
case FBSessionStateClosedLoginFailed:
// Login failed so revert everything to pre-login state
[FBSession.activeSession closeAndClearTokenInformation];
break;
default:
break;
}
if (error) {
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:#"Error"
message:error.localizedDescription
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alertView show];
}
}
And then, simply use this MYNavigationController as your navigation controller class. Don't forget to go through the facebook ios authentication tutorial for a guided tour of how to set up your app (there's plenty of frameworks to add, infoplist changes, etc).
Happy to help more if you need it!
Update #2
Because your window's root view controller is a UITabBarController, then we can use that as the owner of the facebookButton, which is shared between all UINavigationControllers. At a high level, the logic hasn't changed from my previous code sample. You're creating a single button somewhere, that's being added to every view controller that's shown by your navigation controllers. In order to do this, you need to first create a UITabBarController subclass (again, mine is called MYTabBarController, but you can call it whatever you want). And it will look something like this in MYTabBarController.m:
#import "MYTabBarController.h"
#import <FacebookSDK/FacebookSDK.h>
#implementation MYTabBarController
- (UIButton *)facebookButton
{
if (! _facebookButton)
{
// Create a shared facebook button on demand
_facebookButton = [UIButton buttonWithType:UIButtonTypeCustom];
_facebookButton.frame = CGRectMake(0.0f, 0.0f, 75.0f, 44.0f);
[_facebookButton setTitle:#"Login" forState:UIControlStateNormal];
[_facebookButton
addTarget:self
action:#selector(onFacebookButtonClick:)
forControlEvents:UIControlEventTouchUpInside];
}
return _facebookButton;
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
// As each view controller is about to be shown, change the view controller's
// navigationItem to have this facebook button as its right bar button
viewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.facebookButton];
}
- (void)onFacebookButtonClick:(id)sender
{
// Handle the click event when the facebook button is clicked
// You need to have logic here to know if the session is open or not
// Of course, when the session isn't open, then open a new session (ie. login)
// and when the session IS open, then close the session (ie. logout)
[FBSession
openActiveSessionWithReadPermissions:nil
allowLoginUI:YES
completionHandler: ^(FBSession *session, FBSessionState state, NSError *error)
{
[self sessionStateChanged:session state:state error:error];
}];
}
- (void)sessionStateChanged:(FBSession *)session state:(FBSessionState)state error:(NSError *)error
{
switch (state) {
case FBSessionStateOpen:
// Connected to facebook so...
// 1. change the button text to say "logout" instead of "login"
// eg. [self.facebookButton setTitle:#"Logout" forState:UIControlStateNormal];
// 2. go to your next view controller
break;
case FBSessionStateClosed:
case FBSessionStateClosedLoginFailed:
// Login failed so revert everything to pre-login state
[FBSession.activeSession closeAndClearTokenInformation];
break;
default:
break;
}
if (error) {
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:#"Error"
message:error.localizedDescription
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alertView show];
}
}
The corresponding MYTabBarController.h will need to have at least a button defined and the fact it conforms to the UINavigationControllerDelegate protocol:
#interface MYTabBarController: UITabBarController<UINavigationControllerDelegate>
#property (nonatomic, strong) UIButton *facebookButton;
Finally, your app delegate's application:didFinishLaunchingWithOptions: needs to make a small change. It needs to declare the tab bar as the delegate for each of your UINavigationControllers:
// Set the navigation controller delegates to be the tabbar in order to hook into the willShowViewController: method
nav1.delegate = tbc;
nav2.delegate = tbc;
nav3.delegate = tbc;
nav4.delegate = tbc;
nav5.delegate = tbc;
That should do it. Hopefully you can take it from here. Good luck!

When you push to a viewController, you should alloc viewControler before. And you can implement facebook login and session creation in alloc function of viewController.

That's posible. You can create facebook login and session in appDelegate. When you need show facebook view, you can call a function to popup facebook view.

Related

Facebook Custom Login Sample button name is not appearing in iOS

I have followed the following Facebook Custom login sample from git hub
https://github.com/fbsamples/ios-howtos/tree/master/FBLoginCustomUISample
they have used XIB instead of that i have used Storyboard.
every thing is working perfectly but the button name is not appearing.
If any one knows the solution to sort this issue please help me out
Thanks in Advance.
My code:
AppDelegate.M
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
ViewController *ViewObj = [[ViewController alloc]init];
self.ViewObj = ViewObj;
// Override point for customization after application launch.
if (FBSession.activeSession.state == FBSessionStateCreatedTokenLoaded) {
NSLog(#"Found a cached session");
// If there's one, just open the session silently, without showing the user the login UI
[FBSession openActiveSessionWithReadPermissions:#[#"basic_info"]
allowLoginUI:NO
completionHandler:^(FBSession *session, FBSessionState state, NSError *error) {
// Handler for session state changes
// This method will be called EACH time the session state changes,
// also for intermediate states and NOT just when the session open
[self sessionStateChanged:session state:state error:error];
}];
// If there's no cached session, we will show a login button
} else {
UIButton *loginButton = [self.ViewObj btn_LoginAction];
//[loginButton setTitle:#"Log in with Facebook" forState:UIControlStateNormal];
[loginButton setImage:[UIImage imageNamed:#"login_FB.png"] forState:UIControlStateNormal];
}
return YES;
}
- (void)userLoggedOut
{
// Set the button title as "Log in with Facebook"
UIButton *loginButton = [self.ViewObj btn_LoginAction];
// [loginButton setTitle:#"Log in with Facebook" forState:UIControlStateNormal];
[loginButton setImage:[UIImage imageNamed:#"login_FB.png"] forState:UIControlStateNormal];
// Confirm logout message
[self showMessage:#"You're now logged out" withTitle:#""];
}
// Show the user the logged-in UI
- (void)userLoggedIn
{
// Set the button title as "Log out"
UIButton *loginButton = self.ViewObj.btn_LoginAction;
//[loginButton setTitle:#"Log out" forState:UIControlStateNormal];
[loginButton setImage:[UIImage imageNamed:#"logout_FB.png"] forState:UIControlStateNormal];
// Welcome message
[self showMessage:#"You're now logged in" withTitle:#"Welcome!"];
}
ViewController.h
#interface ViewController : UIViewController
#property (weak, nonatomic) IBOutlet UIButton *btn_LoginAction;
ViewController.m
- (IBAction)buttonTouched:(id)sender
{
// If the session state is any of the two "open" states when the button is clicked
if (FBSession.activeSession.state == FBSessionStateOpen
|| FBSession.activeSession.state == FBSessionStateOpenTokenExtended) {
// Close the session and remove the access token from the cache
// The session state handler (in the app delegate) will be called automatically
[FBSession.activeSession closeAndClearTokenInformation];
// If the session state is not any of the two "open" states when the button is clicked
} else {
// Open a session showing the user the login UI
// You must ALWAYS ask for basic_info permissions when opening a session
[FBSession openActiveSessionWithReadPermissions:#[#"basic_info,email"]
allowLoginUI:YES
completionHandler:
^(FBSession *session, FBSessionState state, NSError *error) {
// Retrieve the app delegate
AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
// Call the app delegate's sessionStateChanged:state:error method to handle session state changes
[appDelegate sessionStateChanged:session state:state error:error];
}];
}
}
my Simulator Screen
Please Try following Code for set button image :
// Login
[loginButton setBackgroundImage:[UIImage imageNamed:#"login_FB.png"]
forState:UIControlStateNormal];
// Logout
[loginButton setBackgroundImage:[UIImage imageNamed:#"logout_FB.png"] forState:UIControlStateNormal];
We need to setBackgroundImage for set title of button not use setImage.

Frustrating UIWebView Delegate Crash issue

I've created an ARC Application that run's perfect. It's got a UINavigationController that I use to push through the views and everything runs fine.
I'm converting the Application to iPad and i've decided to show one of the views as a popover. (I don't like UIPopoverController so i've created my own basic popup). It's added to the view as follows..
MyViewController *hotelinf = [[MyViewController alloc]
initWithNibName:#"MyViewController_iPad" bundle:nil];
[self.view addSubview:hotelinf.view];
The view is added as a subview fine. The view i'm adding contains a UIWebView that has the usual delegate methods in it, but when the view tries to access one of the delegates it simply crashes.
*** -[MyViewController respondsToSelector:]: message sent to deallocated instance 0x7917b90
Specifically, it crashes on this line..
[self.webView loadHTMLString:stringResponse baseURL:nil];
I've displayed views (and UINavigationControllers) as subViews many of times without any issues, although none of them included a UIWebView delegate. I'm guessing I have to set the delegate of my UIViewController istance but i'm not sure how. It's also worth noting that if I push the view in my existing UINavigationController it calls and loads the HTML fine, which surely means it has nothing to do with the code of the view itself.
Any help would be greatly appreciated! Here is the code (in addition to above that shows the controller)..
.h
#interface MyViewController : UIViewController <UIWebViewDelegate, UIActionSheetDelegate> {
//Unrelated IBOutlets
}
#property (nonatomic, strong) UIWebView *webView;
#end
.m
#implementation MyViewController
#synthesize webView;
- (void)viewDidLoad
{
[super viewDidLoad];
self.webView = [[UIWebView alloc]initWithFrame:CGRectMake(317,283,393,354)];
self.webView.delegate = self;
[self.view addSubview:self.webView];
[NSThread detachNewThreadSelector:#selector(getHTMLString) toTarget:self withObject:nil];
}
-(void)getHTMLString {
#autoreleasepool {
//Download a valid HTML String
[self performSelectorOnMainThread:#selector(loadHTML) withObject:nil waitUntilDone:NO];
}
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
self.webView = nil;
}
-(void)loadHTML {
self.webView.opaque = NO;
self.webView.backgroundColor = [UIColor clearColor];
if ([stringResponse isEqualToString:#""]) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Could not connect to XXXXX.com. Please verify you are connected to a working 3G/WIFI Network." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
} else {
//it crashes here only when loaded as a subview - the first access to the delegate
[self.webView loadHTMLString:stringResponse baseURL:nil];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[self stopIndicator];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
if (error.code == NSURLErrorCancelled) return; // this is Error -999
[self stopIndicator];
// report the error inside the webview
NSString* errorString = [NSString stringWithFormat:
#"<html><center><font size=+10 color='black' face='Helvetica'>An error occurred:<br>%#</font></center></html>",
error.localizedDescription];
[self.webView loadHTMLString:errorString baseURL:nil];
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Cannot load URL."
message:#"You have a connection failure. Please verify you are connected to a WIFI or 3G enabled Network."
delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[alert show];
}
#end
The issue has nothing to do with the UIWebView, rather with your controller class. Indeed,
MyViewController *hotelinf = [[MyViewController alloc]
initWithNibName:#"MyViewController_iPad" bundle:nil];
[self.view addSubview:hotelinf.view];
You are allocating the controller and assigning it to a local variable; then you add the controller's view as subview to your current view. Doing that, that view is retained, but what happens to the controller object itself? Are you releasing it? Or it leaks (since it is assigned to a local variable)?
This possibly explains why when later the respondsToSelector method is called, the controller has already been deallocated...
A way to fix this is creating a new property or an ivar in your main controller class and store MyViewController in there. Don't forget to release it in dealloc.
I would also suggest another thing. In:
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
self.webView = nil;
}
set the webView delegate to nil before releasing the view:
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
self.webView.delegate = nil;
self.webView = nil;
}
And I would also possibly review the reason why you release the webView in viewDidDisappear. On the other hand you allocate it in viewDidLoad. This asymmetry is dangerous, since whenever the main view disappears (for any reason) the webView will be removed and when the view reappears, it is not there anymore.
Better add all the delegate methods. You havent added the first two. Most probably, your code is crashing when message webViewDidStartLoad is sent
– webView:shouldStartLoadWithRequest:navigationType:
– webViewDidStartLoad:
– webViewDidFinishLoad:
– webView:didFailLoadWithError:

Using UIAlertView in an NSObject

I'm having a terrible time getting a UIAlertView to work within my custom NSObject class. In the research I've done it appears it should be possible but here's what I've run into.
First, here's my code:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
NSLog(#"clickedButtonAtIndex: %d", buttonIndex);
}
-(void)testAlertView {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"List Contains Items"
message:#"List contains items. Remove all items & delete?"
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
[alertView show];
}
If I set the delegate to self this code crashes as soon as I tap a button. If I set it to nil clickedButtonAtIndex is never called. I've tried with and without using the <UIAlertViewDelegate>.
I know someone will ask 'why are you doing this in NSObject instead of in your UIViewController?'. Primarily because I want to separate this code out so I can use it from multiple places in my app. But also because this is a small piece of a larger block of logic that makes sense to be on it's own.
Any ideas what I'm doing wrong?
Thanks,
Rich
I had the same problem using ARC. The root of the problem was the same. I solved it by putting my custom NSObject into a "strong" property to make sure the object exists as long as the calling object (an UIVIewCOntroller in my case) exists, so when the delegate of my alert view is called I still have my custom object around and the delegate method works fine.
Add the NSObject as strong property:
#import "Logout.h" // is NSObject
.
.
.
#property (nonatomic, strong) Logout *logout;
Then you will get the delegatemethods called in your NSObject.
Don´t forget to register the delegate for the UIAlertView:
#interface Logout () <UIAlertViewDelegate>
and in your method:
UIAlertView *a = [[UIAlertView alloc] initWithTitle:#"title"
message:#"message" delegate:self cancelButtonTitle:#"cancel"
otherButtonTitles:#"ok", nil];
[a show];
How To Present An Alert View Using UIAlertController When You Don't Have A View Controller. Detail description.
Yes, you can only use UIAlertController only in UIViewController classes. So how can we do it in NSObject classes. If you see the description link given above you will get to the answer. To summarise in a line for the above description: Create a new window above the the current window. This new window will be our viewController where we display alert. So using this viewController you can call the method [presentViewController: animated: completion:].
Answer:
dispatch_async(dispatch_get_main_queue(), ^{
UIWindow* window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
window.rootViewController = [UIViewController new];
window.windowLevel = UIWindowLevelAlert + 1;
NSString *msg=#“Your mssg";
UIAlertController* alertCtrl = [UIAlertController alertControllerWithTitle:#“Title" message:msg preferredStyle:UIAlertControllerStyleAlert];
[alertCtrl addAction:[UIAlertAction actionWithTitle:NSLocalizedString(#"Yes",#"Generic confirm") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
// do your stuff
// very important to hide the window afterwards.
window.hidden = YES;
}]];
UIAlertAction *cancelAction= [UIAlertAction actionWithTitle:#"cancel" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
window.hidden = YES;
}];
[alertCtrl addAction:cancelAction];
//http://stackoverflow.com/questions/25260290/makekeywindow-vs-makekeyandvisible
[window makeKeyAndVisible]; //The makeKeyAndVisible message makes a window key, and moves it to be in front of any other windows on its level
[window.rootViewController presentViewController:alertCtrl animated:YES completion:nil];
});

iphone navigationController : wait for uialertview response before to quit the current view

I have a view with a back button managed with a navigation controller and I want to check if a file has been saved when the user click on the back button.
If the file has been saved you go back in the previous view, else a uialertview ask you if you want to save the file or not.
So I did that but the view disapear and the alertview appear after.
-(void)viewWillDisappear:(BOOL)animated {
if(!self.fileSaved){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"" message:#"Save the file?" delegate:self cancelButtonTitle:#"No" otherButtonTitles:#"Yes",nil];
[alert show];
[alert release];
}
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
switch (buttonIndex) {
case 0:
NSLog(#"NO");
break;
case 1:
NSLog(#"yes");
break;
default:
break;
}
}
When viewWillDisappear is called, it's already too late. You should intercept the back button earlier on. I have never done it, but my suggestion is to set the delegate on the navigationBar property in your viewDidAppear method:
// save the previous delegate (create an ivar for that)
prevNavigationBarDelegate = self.navigationController.navigationBar.delegate;
self.navigationController.navigationBar.delegate = self;
Don't forget to set it back in viewWillDisappear:
self.navigationController.navigationBar.delegate = prevNavigationBarDelegate;
Then intercept the shouldPopItem method:
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
if(!self.fileSaved) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"" message:#"Save the file?" delegate:self cancelButtonTitle:#"No" otherButtonTitles:#"Yes",nil];
[alert show];
[alert release];
return NO;
}
if ([prevNavigationBarDelegate respondsToSelector:#selector(navigationBar:shouldPopItem:)])
return [prevNavigationBarDelegate navigationBar:navigationBar shouldPopItem:item];
return YES;
}
And in the YES handler for the dialog, manually pop the controller:
[self.navigationController popViewController:YES];
You must subclass UINavigationController for this to work. Then override - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item .
You should set up a custom Delegate protocol that your view controllers adopt and, if you allow it to pop, call your [super navigationBar shouldPopItem:], else, return NO to the above method.
Wouldn't it be easier just to add a left button item as in the following:
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:#selector(saveThisDate)];
self.navigationItem.leftBarButtonItem = backButton;
[backButton release];
To follow up on nobre response and as Jon mentionned it, the best way is to subclass UINavigationController.
The easiest way and fastest way to acheive this :
Modify the class of your navigation controller in Interface Builder to inherit from CustomNavigationControllerDelegate
Implement the CustomNavigationControllerDelegate protocol in your UIViewController
#interface YourViewController <CustomNavigationControllerDelegate>
#pragma mark - UINavigationBar Delegate Methods
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:title message:message delegate:self cancelButtonTitle:cancel otherButtonTitles:ok, nil];
alert.tag = kpopup_back;
[alert show];
return NO;
}
Register your controller as the delegate
#pragma mark - viewWillAppear
- (void) viewWillAppear:(BOOL)animated
{
((CustomNavigationController*)self.navigationController).customDelegate = self;
}
Finally and important part, REMOVE the delegate (to avoid to re-trigger yourself on the pop) and pop the controller yourself in the the UIAlertViewDelegate
case kpopup_back :
{
if(buttonIndex != 0) //OK
{
((CustomNavigationController*)self.navigationController).customDelegate = nil;
[self.navigationController popViewControllerAnimated:YES];
}
}
break;
It works flawlessly on my side, hope it can help.
Here are the sources :
CustomNavigationControllerDelegate.h
#import <UIKit/UIKit.h>
#protocol CustomNavigationControllerDelegate <NSObject>
#optional
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item;
#end
#interface CustomNavigationController : UINavigationController
#property (nonatomic, retain) id<CustomNavigationControllerDelegate> customDelegate;
#end
CustomNavigationControllerDelegate.m
#import "CustomNavigationController.h"
#interface CustomNavigationController ()
#end
#implementation CustomNavigationController
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
if (_customDelegate && [_customDelegate respondsToSelector:#selector(navigationBar:shouldPopItem:)]) {
return [_customDelegate navigationBar:navigationBar shouldPopItem:item];
}
return YES;
}
#end

View loaded from nib will not update contents of UILabel while running in a method

I am having an issue with updating the contents of an "myInfoBox" object I created to be displayed while some background processes are done.
In the delegate method I am creating a new viewController:
-(void)loadMainView
{
myFirstViewController = [[MyFirstViewController alloc] initWithNibName:#"MyFirstView" bundle:nil];
navigationController = [[UINavigationController alloc] initWithRootViewController:myFirstViewController];
// myFirstViewController was retained again by the controller, release one
[myFirstViewController release];
navigationController.navigationBar.hidden = YES;
[window addSubview:navigationController.view];
[window makeKeyAndVisible];
// the next method is run after the "viewDidLoad" is finished loading
[myFirstViewController loadAlertViewForNewUser];
}
Following is my implementation of "myFirstViewController", it creates an instance of the "infoBox" class(I will show its code later):
- (void)viewDidLoad {
self.navigationController.navigationBar.frame = CGRectMake(0, 0, 0, 0);
self.navigationController.navigationBar.bounds = CGRectMake(0, 0, 0, 0);
self.myInfoBox = [[InfoBoxController alloc] initWithNibName:#"InfoBox" bundle:[NSBundle mainBundle]];
CGRect infoBoxFrame;
infoBoxFrame = CGRectMake(60, 120, 200, 200);
myInfoBox.view.frame = infoBoxFrame;
myInfoBox.i_statusLabel.text = #"Downloading Account Updates";
myInfoBox.i_titleLabel.text = #"Updating";
// disabled for testing
//myInfoBox.view.hidden = YES;
[self.view addSubview:myInfoBox.view];
[super viewDidLoad];
}
// this method is called after the view has been loaded by the delegate
- (void)loadAlertViewForNewUser
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Welcome!" message:#"Connect to download stuff from your account?"
delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"OK", nil];
alert.tag = 0;
[alert show];
}
// implementation of the alertview delegate
- (void)alertView:(UIAlertView *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{
if (actionSheet.tag == 0)
{
if (buttonIndex == 0)
{ NSLog(#"button 0 was pressed"); }
if (buttonIndex == 1)
{
// this is the button that is pressed
[actionSheet removeFromSuperview];
[actionSheet release];
// tried using this also
//[self performSelectorOnMainThread:#selector(userInitialSetupMainThread) withObject:nil waitUntilDone:NO];
// do stuff and update the infobox about it
[self loadInfoBoxInitialUserSetup];
// tried using this as well
//[self performSelectorInBackground:#selector(loadInfoBoxInitialUserSetup) withObject:nil];
}
return;
}
}
- (void)loadInfoBoxInitialUserSetup
{
[self performSelectorOnMainThread:#selector(userInitialSetupMainThread) withObject:nil waitUntilDone:NO];
}
- (void)userInitialSetupMainThread
{
// fetch JSON data
NSDictionary *responseJSON = [NSDictionary dictionaryWithDictionary:[self getUserstuff]];
self.myInfoBox.i_statusLabel.text = #"Processing Recieved information";
// breakpoint - nothing changes in the view on the simulator
[myInfoBox.view setNeedsLayout];
// breakpoint - nothing changes in the view on the simulator
[myInfoBox.view setNeedsDisplay];
// breakpoint - nothing changes in the view on the simulator
[myInfoBox.parentViewController.view setNeedsLayout];
// breakpoint - nothing changes in the view on the simulator
[myInfoBox.parentViewController.view setNeedsDisplay];
// breakpoint - nothing changes in the view on the simulator
[myInfoBox performSelectorOnMainThread:#selector(updateValuesForTitle:) withObject:#"test" waitUntilDone:YES];
// breakpoint - nothing changes in the view on the simulator
[self.view setNeedsLayout];
// breakpoint - nothing changes in the view on the simulator
[self.view setNeedsDisplay];
// breakpoint - nothing changes in the view on the simulator
self.myInfoBox.i_statusLabel.text = #"Reloading...";
// breakpoint - nothing changes in the view on the simulator
[self readStuffFromDB];
sleep(2);
//disabled view removal for testing..
//[self.myInfoBox.view removeFromSuperview];
// breakpoint - nothing changes in the view on the simulator
}
What happens for me in the testing is that the myInfoBox object is created on screen when the -(void)loadMainView method is complete, then I can see on screen the "myInfoBox" in the background while the alertView in front (for testing...) at this point the screen is responsive and I can select the YES, once I select yes the delegate method is called.
As I commented in the source file, using breakpoints I am monitoring the simulator and following the code, never the less the changed label values are not reflected while I am still in the - (void)userInitialSetupMainThread method, but once it finishes the view updates with the latest set .text value!! grrr..
Also, the source for the myInfoBox class:
#interface InfoBoxController : UIViewController {
IBOutlet UILabel* i_titleLabel;
IBOutlet UILabel* i_statusLabel;
IBOutlet UIImageView* i_loadingImage;
IBOutlet UIImageView* i_background;
IBOutlet UIActivityIndicatorView* i_activityIndicator;
}
#property(nonatomic, retain) IBOutlet UILabel* i_titleLabel;
#property(nonatomic, retain) IBOutlet UILabel* i_statusLabel;
#property(nonatomic, retain) IBOutlet UIImageView* i_loadingImage;
#property(nonatomic, retain) IBOutlet UIImageView* i_background;
#property(nonatomic, retain) IBOutlet UIActivityIndicatorView* i_activityIndicator;
//- (void)updateValuesForTitle:(NSString *)title Label:(NSString *)label;
- (void)updateValuesForTitle:(NSString *)title;
#end
#implementation InfoBoxController
#synthesize i_titleLabel, i_statusLabel, i_loadingImage, i_background;
#synthesize i_activityIndicator;
//-(void)updateValuesForTitle:(NSString *)title Label:(NSString *)label
-(void)updateValuesForTitle:(NSString *)title
{
self.i_titleLabel.text = title;
self.i_statusLabel.text = title;
[self.i_titleLabel setNeedsDisplay];
[self.i_statusLabel setNeedsDisplay];
}
Sorry for the LOONG post :)
PLEASE ASSIST!
At the risk of sounding unhelpful, that's kind of just how it works. If you have long-running code in the main event loop (i.e., you don't explicitly create a thread or similar), the operating system won't be able to update the UI.
To update the UI while your code is running, you either need to run your complex operation in the back ground using thread, NSOperationQueue, etc, or just break it into smaller steps and return control to the main loop occasionally so that the UI can be updated.