Objective C, crash when dismissViewControllerAnimated - ios

my apps is crashing when i dismiss from modal view. Here is my code
BaseViewController -
- (MFSideMenuContainerViewController *)menuContainerViewController {
return (MFSideMenuContainerViewController *)self.navigationController.parentViewController;
}
-(void) setup
{
BaseViewController *baseViewController = [[BaseViewController alloc] initWithNibName:#"BaseViewController" bundle:nil];
UINavigationController *navigationController = self.menuContainerViewController.centerViewController;
NSArray *controllers = [NSArray arrayWithObject:baseViewController];
navigationController.viewControllers = controllers;
}
- (void)call
{
PopUpViewController *popUpViewController = [[PopUpViewController alloc] init];
[popUpViewController setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
[self presentViewController:popUpViewController animated:NO completion:nil];
}
PopUpViewController -
-(void) close {
if([NSThread isMainThread]) {
[self dismissViewControllerAnimated:YES completion:nil];
}
else
{
[self performSelectorOnMainThread:#selector(close)
withObject:nil
waitUntilDone:YES];
}
}
When execute "setup" > "call" function, it's switch to PopUpViewController. When i try to trigger "close" function from PopUpViewController and it crash. It only happen on IOS7 other than that is just fine.
i got this error message from Zombie Object
-[BaseViewController respondsToSelector:]: message sent to deallocated instance 0x15e8e050
UPDATE **
I had changed my mind, instead of create a new instance for navigationController, i just setup a new View for baseViewController and it won't crash anymore.

Related

Passing Data between ViewController without using Segue

I know the question is repeated, but requirement is little different so posting here. I know how to pass value from one ViewController to other by defining property to hold the value passed from first ViewController. I am attaching the ScreenShot for better understanding. What I did is embed a UIPageViewControllerinto NavigationController(SwipeBetweenViewController). From UIPageViewController calling UIViewController(ProfileViewController) programmatically. After clicking LOG IN button, getting some response, storing it in a variable. Now what I have to do is pass that variable to ProfileViewController.I have defined a property in ProfileViewController.h, imported ProfileViewController.h into LoginViewController.m. I am passing data directly between LoginViewController to ProfileViewController, should it be passed from UiPageViewController. Here is the code, I have tried but its not working. Execution control remains on the same page, no navigation.
ProfileViewController.h
#interface KKProfileViewController : UIViewController
#property(copy, nonatomic) NSString *userEmailId;
#end
LoginViewController.m
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error) {
// Handle error
}
else {
NSError *tempError;
NSDictionary* response=(NSDictionary*)[NSJSONSerialization JSONObjectWithData:receivedData options:kNilOptions error:&tempError];
NSString *loginResponse =response[#"message"];
_emailId =response[#"email"];
if ([loginResponse isEqualToString:#"Welcome"])
{
[self passLoginDataForward];
[self performSegueWithIdentifier:#"loginSuccess" sender:self];
}
else
{
//code for error alert
}
NSLog(#"Response is :%#", response);
}
}
-(void)passLoginDataForward
{
ProfileViewController *viewControllerProfile =[self.storyboard instantiateViewControllerWithIdentifier:#"profileViewController"];
viewControllerProfile.userEmailId = _emailId;
NSLog(#"user Email %#", viewControllerProfile.userEmailId);
[self.navigationController pushViewController:viewControllerProfile animated:YES];
}
SwipeViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[[UINavigationBar appearance] setBarTintColor:[UIColor whiteColor]];
self.navigationBar.translucent = YES;
firstVC = [self.storyboard instantiateViewControllerWithIdentifier:#"profileViewController"];
secondVC = [self.storyboard instantiateViewControllerWithIdentifier:#"dashboardViewController"];
thirdVC = [self.storyboard instantiateViewControllerWithIdentifier:#"newsViewController"];
viewControllerArray = [[NSMutableArray alloc]init];
viewControllerArray = #[firstVC,secondVC,thirdVC];
self.currentPageIndex = 0;
self.isPageScrollingFlag = NO;
self.hasAppearedFlag = NO;
}
-(void)setupPageViewController
{
pageController = (UIPageViewController*)self.topViewController;
pageController.delegate = self;
pageController.dataSource = self;
[pageController setViewControllers:#[[viewControllerArray objectAtIndex:0]] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
[self syncScrollView];
}
The problem is because your login view controller may not have a navigation controller and you are trying to push view controller. Nothing will happen in this case.
If you want to push the page view controller to login views navigation stack, embed your login view controller in a navigation controller(Select login view controller Editor>Ember>Navigation controller) And add a segue to pageviewcontroller(directly from login view controller, not from any button). Add an identifier for the segue(say yourSegueID) Then implement the following method
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"yourSegueID"]) {
UIPageViewController *pageViewController = [segue destinationViewController];
ProfileViewController *viewControllerProfile =[self.storyboard instantiateViewControllerWithIdentifier:#"profileViewController"];
viewControllerProfile.userEmailId = _emailId;
[pageViewController setViewControllers:#[viewControllerProfile] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
}
}
Then call
[self performSegueWithIdentifier:#"yourSegueID" sender:nil];
Second option
If you want to create new navigation stack, as in your current storyboard implementation, make the segue from login view controller to navigation controller a present modal segue then change following line in prepareForSegue
UIPageViewController *pageViewController = [segue destinationViewController];
to
UINavigationController *navController = [segue destinationViewController];
UIPageViewController *pageViewController = navController.viewControllers[0];
Update
Updating as per your new code for swipeviewcontroller
In this case, You have to add email property in swipe view controller too. Then set it in prepare for segue method. Then set profile view controllers property in the swipe view controller viewdidload
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error)
{
// Handle error
}
else
{
NSError *tempError;
NSDictionary* response=(NSDictionary*)[NSJSONSerialization JSONObjectWithData:receivedData options:kNilOptions error:&tempError];
NSString *loginResponse =response[#"message"];
_emailId =response[#"email"];
///////////////////////
//set your Email in nsuserdefaults
[NSUserDefaults standardUserDefaults][setObject:_emailId forKey:#"email"];
[[NSUserDefaults standardUserDefaults]synchronize];
///////////////////////
if ([loginResponse isEqualToString:#"Welcome"])
{
[self passLoginDataForward];
}
else
{
//code for error alert
}
NSLog(#"Response is :%#", response);
}
}
-(void)passLoginDataForward
{
ProfileViewController *viewControllerProfile =[self.storyboard instantiateViewControllerWithIdentifier:#"profileViewController"];
[self.navigationController pushViewController:viewControllerProfile animated:YES];
}
Get Value in ProfileViewController.m
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
userEmailId = [[NSUserDefaults standardUserDefaults]objectForKey:#"email"];
}
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:nil];
YourViewController * vc = [storyboard instantiateViewControllerWithIdentifier:#"identifier"];
vc.something = something;
[self.navigationController pushViewController:vc animated:YES];
Use this instead of self.storyboard.

Hide LoginViewController or dismiss to parentView

This is a normal LoginView calling on a specific action in my app
sourceViewVontroller
if ([password length] == 0) {
loginViewController *seatView = [mainStory instantiateViewControllerWithIdentifier:#"loggingView"];
[self presentViewController:login animated:YES completion:nil];
}
it checks if user not signed in yet when he's calling this action, so it redirects him to the loginViewController, then
loginViewController
UIStoryboard *mainStory = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
DestinationViewController *Dest = [mainStory instantiateViewControllerWithIdentifier:#"destView"];
[self presentViewController:Dest animated:YES completion:nil];
it goes to the destinationView that needs a login, now my problem is how to go back (dismiss not presentmodalView) to the sourceViewController, or simply, how to remove the loginViewController from the queue on success and dismiss directly to the source?
destinationViewController
// Tried to use presentView .. but i dont need to reload sourceView, just dismiss to it !
//[self presentViewController:srcView animated:YES completion:NULL];
//This is what am doing ..
[self dismissViewControllerAnimated:NO completion:nil];
Or just let me know if there is other professional way to do this login flow
I suggest for you another way:
First in - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
You will check user login or not. If login you will set rootView to destinationViewController. If not set rootView to loginViewController
SampleCode:
UIStoryboard *mainStory = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
if (logged) {
DestinationViewController *Dest = [mainStory instantiateViewControllerWithIdentifier:#"destView"];
self.window.rootViewController = Dest;
} else {
LoginViewController *seatView = [mainStory instantiateViewControllerWithIdentifier:#"loggingView"];
self.window.rootViewController = seatView;
}
When logout you just call notification or delegate set rootViewController of window to loginViewController.
Do not use presentViewController for bringing login controller.
Complete Process :
In Singleton class. set a global property.
#property (assign)BOOL isUserLoggedIn;
When user logs in or logs out, set this variable to true or false.
Set an enum in LoginViewController.
typedef enum {
DestViewControllerOne =1,
DestViewControllerTwo
} SignInType;
In this enum, put all your view controller where you wanna put that login check.
Set a property in login view controller to hold source controller value -
#property (nonatomic, assign) NSInteger signInType;
Set up a delegate in login controller to redirect after successfull login -
#protocol SignInProtocolDelegate <NSObject>
#optional
-(void) signInSuccess:(NSInteger) signInType;
#end
Create a property with that delegate in login controller -
#property (nonatomic, assign) NSObject<SignInProtocolDelegate>* delegate;
Now before proceeding to required view controller, perform below check -
if(![[TESingleton shareData] isUserLoggedIn]){
[self funcNavigateToSignInWithAlert:YES withSignInType:proceedToDestViewControllerOne];
}
else
{
[self proceedToDestViewControllerOne];
}
-(void)funcNavigateToSignInWithAlert:(BOOL)showAlert withSignInType:(NSInteger) signIntype
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:STORYBOARDNAME bundle:nil];
LoginViewController *viewController = (LoginViewController *)[storyboard instantiateViewControllerWithIdentifier:#"LoginViewController"];
[viewController setSignInType:signIntype];
[viewController setDelegate:self];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
navController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[[SlideNavigationController sharedInstance] presentViewController:navController animated:YES completion:nil];
}
By this, it will bring login page.
Next -
After successfull login, Do this in login controller :
[self dismissViewControllerAnimated:YES completion:nil];
[[self delegate] signInSuccess:self.signInType];
Import login delegate method in source controller that we have written above -
-(void) signInSuccess:(NSInteger) signInType
{
switch (signInType)
{
case DestViewControllerOne:
[self performSelector:#selector(proceedToDestViewControllerOne) withObject:nil afterDelay:0.5];
break;
case DestViewControllerTwo:
[self performSelector:#selector(proceedToDestViewControllerTwo) withObject:nil afterDelay:0.5];
break;
default:
break;
}
}
Implement these methods in source view controller -
-(void) proceedToDestViewControllerOne
{
//Restricting navigation to signin
//Addded by vikas Jul 8,2015
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:STORYBOARDNAME bundle:nil];
DestViewcontrollerOne *objDestViewcontrollerOne = (DestViewcontrollerOne *)[storyboard instantiateViewControllerWithIdentifier:#"DestViewcontrollerOne"];
[self.navigationController objDestViewcontrollerOne animated:YES];
}
This is complete process.
Use Unwind Segues
Add a method in SourceViewController
For Swift
#IBAction func unwindToThisViewController(segue: UIStoryboardSegue) {
}
For Objective-C
- (IBAction)unwindToThisViewController:(UIStoryboardSegue *)unwindSegue {
}
You can come back to the SourceViewController in two ways.
Using Storyboard e.g when an UIButton action fires.
Using performSegueWithIdentifier in code.
Storyboard:
Control drag form an UIButton in DestinationViewController to Exit segue inSourceViewController
In Code:
Control drag form an UIButton in DestinationViewController to Exit segue inSourceViewController
Call performSegueWithIdentifier:#"ExitToSourceViewController" when need to come back to SourceViewController

Inherited view controller will not dismiss itself in a block #ICETutorial

I'm using ICETutorial with cocoapods.
I'm using it in a SettingsViewController where you can view the tutorial in settings.
// SettingsViewController.m
Tutorial2ViewController *vc = [[Tutorial2ViewController alloc] init];
[self.navigationController setNavigationBarHidden:YES animated:NO];
[self.navigationController pushViewController:vc animated:NO];
And Tutorial2ViewController inherits from ICETutorialController
#interface Tutorial2ViewController : ICETutorialController
ICETutorialPages have buttons that will trigger a callback. It takes in a block. So in my implementation, I have this:
- (id)init
{
ICETutorialPage *layer1 = [[ICETutorialPage alloc] initWithSubTitle:#"Page 1" description:#"Page 1" pictureName:#"Tutorial1_640x1136.png"];
NSArray *tutorialLayers = #[layer1];
self = [super initWithNibName:#"ICETutorialController_iPhone" bundle:nil andPages:tutorialLayers];
__weak Tutorial2ViewController *vc = self;
[self setButton1Block:^(UIButton *button){
NSLog(#"Button 1 pressed.");
[[vc.navigationController topViewController] dismissViewControllerAnimated:NO completion:nil];
}];
if (self != nil)
{
}
return self;
}
The reason why I put all the code in init is that I don't want SettingsViewController to know anything about how the Tutorial2ViewController works. Settings should alloc and init, push to the navigation controller stack and the Tutorial2ViewController should know how to handle itself.
I do get the NSLog that button1 is pressed but the view controller does not dismiss itself and return me to the SettingsViewController.
I will contact the creator of the library and ask him/her to see this question also. I feel that this is me not misunderstanding blocks, navigation controllers, cocoapods, etc...
Thanks
Just try [self.navigationController popViewControllerAnimated:YES];

Attempting to begin modal transition while transition is in progress

I'm trying to check if a user is logged into Facebook. If they are, I want to transfer them to another view.
The issue I'm having is that loginViewFetchedUserInfo and loginViewShowingLoggedInUser are both called before the view is actually done loading.
Because of this, when [self showWelcome:self] is called, I get a "attempting to begin modal transition while transition is in progress" error.
I can't seem to figure out a way to wait until the view is done loading before sending them off to the new view.
- (void)loginViewShowingLoggedInUser:(FBLoginView *)loginView {
// Set flag
isFirstLoginDone = YES;
NSLog(#"User is logged in");
}
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView
user:(id<FBGraphUser>)user {
// Check first login
if(isFirstLoginDone) {
[self showWelcome:self];
}
// clear the flag
isFirstLoginDone = NO;
}
- (IBAction)showWelcome:(id)sender
{
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"WelcomeStoryboard" bundle:nil];
UIViewController *vc = [mainStoryboard instantiateViewControllerWithIdentifier:#"WelcomeController"];
[self presentViewController:vc animated:YES completion:nil];
}
Yes , it happens when you present some another viewController while a viewController is being present. For that create a dummy method as below code and fire it with small time intervals.
- (IBAction)showWelcome:(id)sender{
[self someMethod];
}
-(void)someMethod{
if(self.isBeingPresented){
[self performSelector:#selector(someMethod) withObject:nil afterDelay:.1];
}
else{
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"WelcomeStoryboard" bundle:nil];
UIViewController *vc = [mainStoryboard instantiateViewControllerWithIdentifier:#"WelcomeController"];
[self presentViewController:vc animated:YES completion:nil];
}
}

Presenting view not appearing properly after MFMessageComposeViewController is presented and then dismissed

I have a View Controller which displays a table view. The VC calls another VC to display the send SMS view to the user, the code for this SMS VC is:
- (void) sendSMSWithBody: (NSString*) body andRecipients: (NSArray*) recipients
{
MFMessageComposeViewController *controller = [[MFMessageComposeViewController alloc] init];
if ([MFMessageComposeViewController canSendText])
{
controller.messageComposeDelegate = self;
controller.body = body;
controller.recipients = recipients;
[[UIApplication sharedApplication].delegate.window.rootViewController addChildViewController:self];
[self presentModalViewController:controller animated:YES];
}
}
- (void) messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result
{
[self dismissModalViewControllerAnimated:YES];
[[UIApplication sharedApplication].delegate.window.rootViewController removeFromParentViewController];
}
(I know the call to sharedApplication is a bit hacky, but it will suffice for now. the rootViewController is a UINavigationController which has its root controller set to the table view controller)
I am invoking the SMS VC from the table VC like so:
- (void ) viewDidAppear:(BOOL)animated
{
static BOOL presentedSMSVC = NO;
if (!presentedSMSVC)
{
SendSMSController *sendSMS = [[SendSMSController alloc] init];
[sendSMS sendSMSWithBody:#"body"
andRecipients:[NSArray arrayWithObject:#"123456789"]];
presentedRegisterVC = YES;
}
}
The problem is that after the user sends the SMS the table view cells are not displaying.
I thought maybe I need to refresh the view/table so I added a protocol callback from the 2nd VC to the first which gets invoked when the user sends the SMS, and then within the callback call [self.tableView reloadData] But it made no difference.
So I got rid of the intermediary class and edited the table view to display the SMS view directly like this:
- (void ) viewDidAppear:(BOOL)animated
{
static BOOL presentedRegisterVC = NO;
if (!presentedRegisterVC)
{
MFMessageComposeViewController *controller = [[MFMessageComposeViewController alloc] init];
if ([MFMessageComposeViewController canSendText])
{
controller.messageComposeDelegate = self;
controller.body = #"body";
controller.recipients = [NSArray arrayWithObject:#"12345678"];
[self presentModalViewController:controller animated:NO];
}
}
}
- (void) messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result
{
[self dismissModalViewControllerAnimated:NO];
}
But now the MFMessageComposeViewController doesn't dismiss (although messageComposeViewController:didFinishWithResult: does get called)
What is the problem with both approaches? Thanks
For the second variant, I changed to:
[self presentViewController: controller animated: YES completion:nil];
[self dismissViewControllerAnimated:YES completion:nil];
And that worked, haven't tried applying to the first method.
I faced a similar UI issue.
My case was: The controller, let's say controller A, in which I had written the code to present and dismiss the MFMessageComposeController, was not being used directly as the active controller rather I was using A.view as a subview over another controller, say controller B. So writing the following was distorting the view:
[self presentViewController:composeVC animated:YES completion:nil];
But then writing the following solved my issue:
[self.customTabbarNavigation presentViewController:composeVC animated:YES completion:nil];
The customTabbarNavigation, was the controller B, ans was actually the navigation controller which was active when controller A's view was visible.
Same change had to be made for dismissing the composeVC.

Resources